From f0135eb0f54a3cd50680a2e000e7c5d401a149cf Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 17 Apr 2019 17:39:10 +0200 Subject: [PATCH 01/24] Add fastSRM algorithm and dependencies --- brainiak/funcalign/fastsrm.py | 824 +++++++++++++++ requirements-dev.txt | 3 + tests/eventseg/test_event.py | 155 --- tests/factoranalysis/test_htfa.py | 168 --- tests/factoranalysis/test_tfa.py | 110 -- tests/fcma/data/expected_processed_data.npy | Bin 2672 -> 0 bytes tests/fcma/data/expected_raw_data.npy | Bin 5264 -> 0 bytes .../expected_searchlight_processed_data.npy | Bin 1704016 -> 0 bytes tests/fcma/test_classification.py | 222 ---- tests/fcma/test_mvpa_voxel_selection.py | 51 - tests/fcma/test_preprocessing.py | 109 -- tests/fcma/test_util.py | 59 -- tests/fcma/test_voxel_selection.py | 116 --- tests/hyperparamopt/test_hpo.py | 86 -- tests/image/test_image.py | 172 ---- tests/io/data/epoch_labels.npy | Bin 160 -> 0 bytes tests/io/data/mask.nii.gz | Bin 634 -> 0 bytes tests/io/data/subject1_bet.nii.gz | Bin 7149 -> 0 bytes tests/io/data/subject2_bet.nii.gz | Bin 7112 -> 0 bytes tests/io/test_io.py | 106 -- tests/isc/test_isc.py | 956 ------------------ tests/reprsimil/example_design.1D | 272 ----- tests/reprsimil/test_brsa.py | 651 ------------ tests/reprsimil/test_gbrsa.py | 630 ------------ tests/searchlight/test_searchlight.py | 293 ------ tests/utils/example_design.1D | 272 ----- tests/utils/example_stimtime_1_AFNI.txt | 2 - tests/utils/example_stimtime_1_FSL.txt | 3 - tests/utils/example_stimtime_2_AFNI.txt | 1 - tests/utils/example_stimtime_2_FSL.txt | 3 - tests/utils/test_fmrisim.py | 858 ---------------- tests/utils/test_utils.py | 396 -------- 32 files changed, 827 insertions(+), 5691 deletions(-) create mode 100644 brainiak/funcalign/fastsrm.py delete mode 100644 tests/eventseg/test_event.py delete mode 100644 tests/factoranalysis/test_htfa.py delete mode 100644 tests/factoranalysis/test_tfa.py delete mode 100644 tests/fcma/data/expected_processed_data.npy delete mode 100644 tests/fcma/data/expected_raw_data.npy delete mode 100644 tests/fcma/data/expected_searchlight_processed_data.npy delete mode 100644 tests/fcma/test_classification.py delete mode 100644 tests/fcma/test_mvpa_voxel_selection.py delete mode 100644 tests/fcma/test_preprocessing.py delete mode 100644 tests/fcma/test_util.py delete mode 100644 tests/fcma/test_voxel_selection.py delete mode 100644 tests/hyperparamopt/test_hpo.py delete mode 100644 tests/image/test_image.py delete mode 100644 tests/io/data/epoch_labels.npy delete mode 100755 tests/io/data/mask.nii.gz delete mode 100644 tests/io/data/subject1_bet.nii.gz delete mode 100644 tests/io/data/subject2_bet.nii.gz delete mode 100644 tests/io/test_io.py delete mode 100644 tests/isc/test_isc.py delete mode 100755 tests/reprsimil/example_design.1D delete mode 100755 tests/reprsimil/test_brsa.py delete mode 100644 tests/reprsimil/test_gbrsa.py delete mode 100644 tests/searchlight/test_searchlight.py delete mode 100755 tests/utils/example_design.1D delete mode 100644 tests/utils/example_stimtime_1_AFNI.txt delete mode 100644 tests/utils/example_stimtime_1_FSL.txt delete mode 100644 tests/utils/example_stimtime_2_AFNI.txt delete mode 100644 tests/utils/example_stimtime_2_FSL.txt delete mode 100644 tests/utils/test_fmrisim.py delete mode 100644 tests/utils/test_utils.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py new file mode 100644 index 000000000..c093a1be3 --- /dev/null +++ b/brainiak/funcalign/fastsrm.py @@ -0,0 +1,824 @@ +"""Fast Shared Response Model (FastSRM) + +The implementations are based on the following publications: + +.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", + P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge + Advances in Neural Information Processing Systems (NIPS), 2015. + http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model + +.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging + Datasets", + Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. + Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, + Kenneth A. Norman, + IEEE International Conference on Big Data, 2016. + https://doi.org/10.1109/BigData.2016.7840719 +""" + +# Author: Hugo Richard (INRIA - Parietal) +# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) and Bertrand Thirion (Inria - Parietal) +# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience Institute) and Javier Turek (Intel Labs) + +import logging + +import numpy as np +import scipy +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.utils import assert_all_finite +from sklearn.exceptions import NotFittedError +from mpi4py import MPI +import sys +from joblib import Parallel, delayed +import os +import glob +import hashlib + +__all__ = [ + "FastSRM", +] + +logger = logging.getLogger(__name__) + + +def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir=None): + """Reduce data using given atlas + + Parameters + ---------- + + img : str + path to data. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + atlas : array, shape=[n_supervoxels, n_voxels] or None or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + inv_atlas : array, shape=[n_voxels, n_supervoxels] + Pseudo inverse of the atlas (only for probabilistic atlases) + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and number of sessions are large + + Returns + ------- + + reduced_data : array, shape=[n_timeframes, n_supervoxels] + reduced data + """ + if atlas is None and inv_atlas is None: + AssertionError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") + + if inv_atlas is None and atlas is not None: + atlas_values = np.unique(atlas) + if 0 in atlas_values: + atlas_values = atlas_values[1:] + data = np.load(img) + reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T + else: + data = np.load(img) + reduced_data = data.dot(inv_atlas) + + if low_ram: + name = hashlib.md5(img.encode()).hexdigest() + path = os.path.join(temp_dir, "reduced_data_" + name) + np.save(path, reduced_data) + return path + else: + return reduced_data + + +def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): + """Reduce data using given atlas. + Work done in parallel across subjects. + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + n_jobs : integer, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and/or sessions is large + + Returns + ------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + + if len(atlas.shape) == 2: + A = None + A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) + else: + A = atlas + A_inv = None + + n_subjects, n_sessions = imgs.shape + + reduced_data_list = Parallel(n_jobs=n_jobs)( + delayed(reduce_data_single)( + img, + atlas=A, + inv_atlas=A_inv, + low_ram=low_ram, + temp_dir=temp_dir + ) for img in imgs.flatten()) + + if low_ram: + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) + else: + n_timeframes, n_voxels = reduced_data_list[0].shape + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_voxels)) + + return reduced_data_list + + +def _reduced_space_compute_shared_response(reduced_data_list, + reduced_basis_list, + n_components=50): + """Compute shared response with basis fixed in reduced space + + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + reduced_basis_list : None or list of array, element i has shape=[n_components, n_supervoxels] + each subject's reduced basis + if None the basis will be generated on the fly + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + """ + n_subjects, n_sessions = reduced_data_list.shape[:2] + if type(reduced_data_list[0, 0]) == np.ndarray: + low_ram = False + elif type(reduced_data_list[0, 0]) == str: + low_ram = True + else: + AssertionError("Reduced data are stored using type %s which is neither an ndarray or str" + % type(reduced_data_list[0, 0])) + low_ram = False + + s = [None] * n_sessions + + for n in range(n_subjects): + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + + if reduced_basis_list is None: + n_timeframes, n_supervoxels = data_nm.shape + reduced_basis_list = [] + for subject in range(n_subjects): + q = np.eye(n_components, n_supervoxels) + reduced_basis_list.append(q) + + basis_n = reduced_basis_list[n] + if s[m] is None: + s[m] = data_nm.dot(basis_n.T) + else: + s[m] = s + data_nm.dot(basis_n.T) + + for m in range(n_sessions): + s[m] = float(s[m]) / n_subjects + + return s + + +def _compute_and_save_corr_mat(img, shared_response, temp_dir): + """computes correlation matrix and stores it + + Parameters + ---------- + img : str + path to data. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + shared_response : array, shape=[n_timeframes, n_components] + shared response + """ + data = np.load(img) + name = hashlib.md5(img.encode()).hexdigest() + path = os.path.join(temp_dir, "corr_mat_" + name) + np.save(path, shared_response.T.dot(data)) + + +def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): + """computes correlation matrix for all sessions + + Parameters + ---------- + + subject_number: int + Number that identifies the subject. Basis will be stored in [temp_dir]/basis_[subject_number].npy + + sessions : array of str + Element i of the array is a path to the data collected during session i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + Returns + ------- + + basis: array, shape=[n_component, n_voxels] or str + basis of subject [subject_number] or path to this basis + """ + corr_mat = None + for session in sessions: + name = hashlib.md5(session.encode()).hexdigest() + path = os.path.join(temp_dir, "corr_mat_" + name + ".npy") + if corr_mat is None: + corr_mat = np.load(path) + else: + corr_mat += np.load(path) + basis_i = _compute_subject_basis(corr_mat) + + if temp_dir is None: + return basis_i + else: + path = os.path.join(temp_dir, "basis_" % subject_number) + np.save(path, basis_i) + return path + + +def _compute_subject_basis(corr_mat): + """From correlation matrix between shared response and subject data, + Finds subject's basis + + Parameters + ---------- + + corr_mat: array, shape=[n_component, n_voxels] or shape=[n_components, n_supervoxels] + correlation matrix between shared response and subject data or subject reduced data + element k, v is given by S.T.dot(X_i) where S is the shared response and + X_i the data of subject i. + + Returns + ------- + + basis: array, shape=[n_components, n_voxels] or shape=[n_components, n_supervoxels] + basis of subject or reduced_basis of subject + """ + if corr_mat.shape[0] == corr_mat.shape[1]: + U, _, V = scipy.linalg.svd( + corr_mat + 1.e-18 * np.eye(corr_mat.shape[0]), + full_matrices=False + ) + else: + U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) + return U.dot(V) + + +def fast_srm(reduced_data_list, n_iter=10, n_components=None): + """Computes shared response and basis in reduced space + + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + n_iter : int + Number of iterations performed + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + + if type(reduced_data_list[0, 0]) == np.ndarray: + low_ram = False + elif type(reduced_data_list[0, 0]) == str: + low_ram = True + else: + AssertionError("Reduced data are stored using type %s which is neither np.ndarray or str" + % type(reduced_data_list[0, 0])) + low_ram = False + + + n_subjects, n_sessions = reduced_data_list.shape[:2] + shared_response = _reduced_space_compute_shared_response( + reduced_data_list, + None, + n_components + ) + + reduced_basis = [None] * n_subjects + for _ in range(n_iter): + for n in range(n_subjects): + cov = None + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + if cov is None: + cov = shared_response[m].T.dot(data_nm) + else: + cov += shared_response[m].T.dot(data_nm) + reduced_basis[n] = _compute_subject_basis(cov) + + shared_response = _reduced_space_compute_shared_response( + reduced_data_list, + reduced_basis, + n_components + ) + + return shared_response + + +def _compute_basis_subject_online(sessions, shared_response_list): + """Computes subject's basis with shared response fixed + + Parameters + ---------- + + sessions : array of str + Element i of the array is a path to the data collected during session i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + Returns + ------- + + basis: array, shape=[n_components, n_voxels] + basis + """ + + basis_i = None + i = 0 + for session in sessions: + data = np.load(session) + if basis_i is None: + basis_i = shared_response_list[i].T.dot(data) + else: + basis_i += shared_response_list[i].T.dot(data) + i += 1 + del data + return _compute_subject_basis(basis_i) + + +def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subjects_indexes): + """Computes shared response during one session with basis fixed + + Parameters + ---------- + + subjects : array of str + Element i of the array is a path to the data of subject i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis of all subjects, element i is the basis of subject i + + temp_dir : None or str + path to basis folder where file basis_%i.npy contains the basis of subject i + + subjects_indexes : list of int or None + list of indexes corresponding to the subjects to use to compute shared response + + Returns + ------- + + shared_response : array, shape=[n_timeframes, n_components] + shared response + """ + n = 0 + shared_response = None + for k, i in enumerate(subjects_indexes): + subject = subjects[k] + data = np.load(subject) + if temp_dir is None: + basis_i = basis_list[i] + else: + basis_i = np.load(os.path.join(temp_dir, "basis_%i.npy" % i)) + + if shared_response is None: + shared_response = data.dot(basis_i.T) + else: + shared_response += data.dot(basis_i.T) + + n += 1 + return shared_response / float(n) + + +def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects_indexes): + """Computes shared response with basis fixed + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis of all subjects, element i is the basis of subject i + + temp_dir : None or str + path to basis folder where file basis_%i.npy contains the basis of subject i + + n_jobs : integer, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + subjects_indexes : list or None + list of indexes corresponding to the subjects to use to compute shared response + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + shared_response_list = Parallel(n_jobs=n_jobs)( + delayed(_compute_shared_response_online_single)( + subjects, + basis_list, + temp_dir, + subjects_indexes + ) for subjects in imgs.T) + + return shared_response_list + + +class FastSRM(BaseEstimator, TransformerMixin): + """SRM decomposition using a very low amount of memory and computational power + + Given multi-subject data, factorize it as a shared response S among all + subjects and an orthogonal transform (basis) W per subject: + + .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N + + Parameters + ---------- + + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + n_components : int + Number of timecourses of the shared coordinates + + n_iter : int + Number of iterations to perform + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and / or sessions is large + + random_state : int or RandomState + Pseudo number generator state used for random sampling. + + n_jobs : int, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + verbose : bool or "warn" + if True, logs are enabled. + if False, logs are disabled. + if "warn" only warnings are printed. + + Attributes + ---------- + + `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str + basis of all subjects, element i is the basis of subject i + or path to basis of all subjects, element i is the path to the basis of subject i + """ + def __init__(self, + atlas, + n_components=20, + n_iter=100, + temp_dir=None, + low_ram=False, + random_state=None, + n_jobs=1, + verbose="warn",): + + self.random_state = random_state + self.n_jobs = n_jobs + self.verbose = verbose + self.n_components = n_components + self.n_iter = n_iter + self.atlas = atlas + + self.basis_list = None + + if temp_dir is None: + if self.verbose == "warn" or self.verbose is True: + logger.warning("temp_dir has value None. All basis (spatial maps) and " + "reconstructed data will therefore be kept in memory." + "This can lead to memory errors when the number of subjects " + "and/or sessions is large.") + + if temp_dir is not None: + if not os.path.exists(os.path.join(temp_dir, "fastsrm")): + os.mkdir(os.path.join(temp_dir, "fastsrm")) + self.temp_dir = os.path.join(temp_dir, "fastsrm") + + # Remove files in temp folder + paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) + for path in paths: + os.remove(path) + + self.low_ram = low_ram + + def fit(self, imgs): + """Computes basis across subjects from input imgs + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + ------- + self : object + Returns the instance itself. Contains attributes listed + at the object level. + """ + + if self.temp_dir is not None: + # Remove former basis in temp folder + paths = glob.glob(os.path.join(self.temp_dir, "basis") + "*") + for path in paths: + os.remove(path) + + if type(imgs) != np.ndarray: + AssertionError("imgs should be of type np.ndarray but is of type %s" + % type(imgs)) + + if len(imgs.shape) != 2: + AssertionError("imgs should be an array of shape [n_subjects, n_sessions] " + "but its shape is of size %i" + % len(imgs.shape)) + + if self.verbose is True: + n_subjects, n_sessions = imgs.shape + logger.info("Fitting using %i subjects and %i sessions per subject" + % (n_subjects, n_sessions)) + + if self.verbose is True: + logger.info("[FastSRM.fit] Reducing data") + + reduced_data = reduce_data( + imgs, + atlas=self.atlas, + n_jobs=self.n_jobs, + low_ram=self.low_ram, + temp_dir=self.temp_dir + ) + + if self.verbose: + logger.info("[FastSRM.fit] Finds shared response using reduced data") + + shared_response_list = fast_srm( + reduced_data, + n_iter=self.n_iter, + n_components=self.n_components, + ) + + if self.verbose: + print("[FastSRM.fit] Finds basis using full data and shared response") + + if self.n_jobs == 1: + basis = [] + for i, sessions in enumerate(imgs): + basis_i = _compute_basis_subject_online(sessions, shared_response_list) + if self.temp_dir is None: + basis.append(basis_i) + else: + path = os.path.join(self.temp_dir, "basis_%i" % i) + np.save(path, basis_i) + basis.append(path) + del basis_i + else: + Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_corr_mat)( + subject, + shared_response_list[m], + self.temp_dir + ) + for m, subjects in enumerate(imgs.T) + for subject in subjects + ) + + basis = Parallel(n_jobs=self.n_jobs)(delayed(_compute_and_save_subject_basis)(i, sessions, self.temp_dir) + for i, sessions in enumerate(imgs)) + + self.basis_list = basis + return self + + def fit_transform(self, imgs, **fit_params): + """Computes basis across subjects and shared response from input imgs + return shared response. + + Parameters + ---------- + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + -------- + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + self.fit(imgs) + return self.transform(imgs) + + def transform(self, imgs, subjects_indexes=None): + """From data in imgs and basis from training data, + computes shared response. + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + + subjects_indexes : list or None: + if None imgs[i] will be transformed using basis[i] + otherwise imgs[i] will be transformed using basis[subjects_index[i]] + + Returns + ------- + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + if subjects_indexes is None: + subjects_indexes = np.arange(len(imgs)) + else: + subjects_indexes = np.array(subjects_indexes) + + shared_response = _compute_shared_response_online( + imgs, + self.basis_list, + self.temp_dir, + self.n_jobs, + subjects_indexes + ) + + return shared_response + + def inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None): + """From shared response and basis from training data reconstruct subject's data + + Parameters + ---------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + subjects_indexes: list or None: + if None reconstructs data of all subjects' used during train + otherwise reconstructs data using subject's specified by subjects_indexes + + sessions_indexes: list or None: + if None reconstructs data using all sessions + otherwise uses only specified sessions + + Returns + ------- + reconstructed_data: array shape=[len(subjects_indexes), len(sessions_indexes), n_timeframes, n_voxels] + Reconstructed data for chosen subjects and sessions + """ + n_subjects = len(self.basis_list) + + if subjects_indexes is None: + subjects_indexes = np.arange(n_subjects) + else: + subjects_indexes = np.array(subjects_indexes) + + if sessions_indexes is None: + sessions_indexes = np.arange(len(shared_response_list)) + else: + sessions_indexes = np.array(sessions_indexes) + + data = [] + for i in subjects_indexes: + data_ = [] + if self.temp_dir is None: + basis_i = self.basis_list[i] + else: + basis_i = np.load(os.path.join(self.temp_dir, "basis_%i.npy" % i)) + + for j in sessions_indexes: + data_.append(shared_response_list[j].dot(basis_i)) + + data.append(np.array(data_)) + return np.array(data) diff --git a/requirements-dev.txt b/requirements-dev.txt index bf2baa3eb..9132617e1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,6 +8,9 @@ setuptools_scm sphinx sphinx_rtd_theme towncrier +joblib +os +glob # testing # Also add to .conda/meta.yaml diff --git a/tests/eventseg/test_event.py b/tests/eventseg/test_event.py deleted file mode 100644 index a82de27bd..000000000 --- a/tests/eventseg/test_event.py +++ /dev/null @@ -1,155 +0,0 @@ -from brainiak.eventseg.event import EventSegment -from scipy.special import comb -import numpy as np -import pytest -from sklearn.exceptions import NotFittedError - - -def test_create_event_segmentation(): - es = EventSegment(5) - assert es, "Invalid EventSegment instance" - - -def test_fit_shapes(): - K = 5 - V = 3 - T = 10 - es = EventSegment(K, n_iter=2) - sample_data = np.random.rand(V, T) - es.fit(sample_data.T) - - assert es.segments_[0].shape == (T, K), "Segmentation from fit " \ - "has incorrect shape" - assert np.isclose(np.sum(es.segments_[0], axis=1), np.ones(T)).all(), \ - "Segmentation from learn_events not correctly normalized" - - T2 = 15 - sample_data2 = np.random.rand(V, T2) - test_segments, test_ll = es.find_events(sample_data2.T) - - assert test_segments.shape == (T2, K), "Segmentation from find_events " \ - "has incorrect shape" - assert np.isclose(np.sum(test_segments, axis=1), np.ones(T2)).all(), \ - "Segmentation from find_events not correctly normalized" - - es_invalid = EventSegment(K) - with pytest.raises(ValueError, message="T < K should cause error"): - es_invalid.model_prior(K-1) - with pytest.raises(ValueError, message="#Events < K should cause error"): - es_invalid.set_event_patterns(np.zeros((V, K-1))) - - -def test_simple_boundary(): - es = EventSegment(2) - random_state = np.random.RandomState(0) - - sample_data = np.array([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + \ - random_state.rand(2, 7) * 10 - es.fit(sample_data.T) - - events = np.argmax(es.segments_[0], axis=1) - assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ - "Failed to correctly segment two events" - - events_predict = es.predict(sample_data.T) - assert np.array_equal(events_predict, [0, 0, 0, 1, 1, 1, 1]), \ - "Error in predict interface" - - -def test_event_transfer(): - es = EventSegment(2) - sample_data = np.asarray([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) - - with pytest.raises(NotFittedError, message="Should need to set variance"): - seg = es.find_events(sample_data.T)[0] - - with pytest.raises(NotFittedError, message="Should need to set patterns"): - seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] - - es.set_event_patterns(np.asarray([[1, 0], [0, 1]])) - seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] - - events = np.argmax(seg, axis=1) - assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ - "Failed to correctly transfer two events to new data" - - -def test_weighted_var(): - es = EventSegment(2) - - D = np.zeros((8, 4)) - for t in range(4): - D[t, :] = (1/np.sqrt(4/3)) * np.array([-1, -1, 1, 1]) - for t in range(4, 8): - D[t, :] = (1 / np.sqrt(4 / 3)) * np.array([1, 1, -1, -1]) - mean_pat = D[[0, 4], :].T - - weights = np.zeros((8, 2)) - weights[:, 0] = [1, 1, 1, 1, 0, 0, 0, 0] - weights[:, 1] = [0, 0, 0, 0, 1, 1, 1, 1] - assert np.array_equal( - es.calc_weighted_event_var(D, weights, mean_pat), [0, 0]),\ - "Failed to compute variance with 0/1 weights" - - weights[:, 0] = [1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5] - weights[:, 1] = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] - true_var = (4 * 0.5 * 12)/(6 - 5/6) * np.ones(2) / 4 - assert np.allclose( - es.calc_weighted_event_var(D, weights, mean_pat), true_var),\ - "Failed to compute variance with fractional weights" - - -def test_sym(): - es = EventSegment(4) - - evpat = np.repeat(np.arange(10).reshape(-1, 1), 4, axis=1) - es.set_event_patterns(evpat) - - D = np.repeat(np.arange(10).reshape(1, -1), 20, axis=0) - ev = es.find_events(D, var=1)[0] - - # Check that events 1-4 and 2-3 are symmetric - assert np.all(np.isclose(ev[:, :2], np.fliplr(np.flipud(ev[:, 2:])))),\ - "Fit with constant data is not symmetric" - - -def test_chains(): - es = EventSegment(5, event_chains=np.array(['A', 'A', 'B', 'B', 'B'])) - - es.set_event_patterns(np.array([[1, 1, 0, 0, 0], - [0, 0, 1, 1, 1]])) - sample_data = np.array([[0, 0, 0], [1, 1, 1]]) - seg = es.find_events(sample_data.T, 0.1)[0] - - ev = np.nonzero(seg > 0.99)[1] - assert np.array_equal(ev, [2, 3, 4]),\ - "Failed to fit with multiple chains" - - -def test_prior(): - K = 10 - T = 100 - - es = EventSegment(K) - mp = es.model_prior(T)[0] - - p_bound = np.zeros((T, K-1)) - norm = comb(T-1, K-1) - for t in range(T-1): - for k in range(K-1): - # See supplementary material of Neuron paper - # https://doi.org/10.1016/j.neuron.2017.06.041 - p_bound[t+1, k] = comb(t, k) * comb(T-t-2, K-k-2) / norm - p_bound = np.cumsum(p_bound, axis=0) - - mp_gt = np.zeros((T, K)) - for k in range(K): - if k == 0: - mp_gt[:, k] = 1 - p_bound[:, 0] - elif k == K - 1: - mp_gt[:, k] = p_bound[:, k-1] - else: - mp_gt[:, k] = p_bound[:, k-1] - p_bound[:, k] - - assert np.all(np.isclose(mp, mp_gt)),\ - "Prior does not match analytic solution" diff --git a/tests/factoranalysis/test_htfa.py b/tests/factoranalysis/test_htfa.py deleted file mode 100644 index 58591b130..000000000 --- a/tests/factoranalysis/test_htfa.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_R(): - from brainiak.factoranalysis.htfa import HTFA - with pytest.raises(TypeError) as excinfo: - HTFA() - assert "missing 2 required positional arguments" in str(excinfo.value) - - -def test_X(): - from brainiak.factoranalysis.htfa import HTFA - import numpy as np - - n_voxel = 100 - n_tr = 20 - K = 5 - max_global_iter = 3 - max_local_iter = 3 - max_voxel = n_voxel - max_tr = n_tr - - R = [] - n_subj = 2 - for s in np.arange(n_subj): - R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) - - htfa = HTFA( - K, - n_subj=n_subj, - max_global_iter=max_global_iter, - max_local_iter=max_local_iter, - max_voxel=max_voxel, - max_tr=max_tr) - - X = np.random.rand(n_voxel, n_tr) - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "Input data should be a list" in str(excinfo.value) - - X = [] - # Check that does NOT run with wrong array dimension - with pytest.raises(ValueError) as excinfo: - htfa.fit(X, R=R) - assert "Need at leat one subject to train the model" in str(excinfo.value) - - X = [] - X.append([1, 2, 3]) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "data should be an array" in str(excinfo.value) - - X = [] - X.append(np.random.rand(n_voxel)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "subject data should be 2D array" in str(excinfo.value) - - X = [] - for s in np.arange(n_subj): - X.append(np.random.rand(n_voxel, n_tr)) - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "Coordinates should be a list" in str(excinfo.value) - - R = [] - R.append([1, 2, 3]) - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("Each scanner coordinate matrix should be an array" - in str(excinfo.value)) - - R = [] - R.append(np.random.rand(n_voxel)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("Each scanner coordinate matrix should be 2D array" - in str(excinfo.value)) - - R = [] - for s in np.arange(n_subj): - R.append(np.random.rand(n_voxel - 1, 3)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("n_voxel should be the same in X[idx] and R[idx]" - in str(excinfo.value)) - - -def test_can_run(): - import numpy as np - from brainiak.factoranalysis.htfa import HTFA - from mpi4py import MPI - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - - n_voxel = 100 - n_tr = 20 - K = 5 - max_global_iter = 3 - max_local_iter = 3 - max_voxel = n_voxel - max_tr = n_tr - R = [] - n_subj = 2 - for s in np.arange(n_subj): - R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) - my_R = [] - for idx in np.arange(n_subj): - if idx % size == rank: - my_R.append(R[idx]) - - htfa = HTFA( - K, - n_subj=n_subj, - max_global_iter=max_global_iter, - max_local_iter=max_local_iter, - max_voxel=max_voxel, - max_tr=max_tr, - verbose=True) - assert htfa, "Invalid HTFA instance!" - - X = [] - for s in np.arange(n_subj): - X.append(np.random.rand(n_voxel, n_tr)) - my_data = [] - for idx in np.arange(n_subj): - if idx % size == rank: - my_data.append(X[idx]) - - if rank == 0: - htfa.fit(my_data, R=my_R) - assert True, "Root successfully running HTFA" - assert htfa.global_prior_.shape[0] == htfa.prior_bcast_size,\ - "Invalid result of HTFA! (wrong # element in global_prior)" - assert htfa.global_posterior_.shape[0] == htfa.prior_bcast_size,\ - "Invalid result of HTFA! (wrong # element in global_posterior)" - - else: - htfa.fit(my_data, R=my_R) - assert True, "worker successfully running HTFA" - print(htfa.local_weights_.shape) - assert htfa.local_weights_.shape[0] == n_tr * K,\ - "Invalid result of HTFA! (wrong # element in local_weights)" - assert htfa.local_posterior_.shape[0] == htfa.prior_size,\ - "Invalid result of HTFA! (wrong # element in local_posterior)" diff --git a/tests/factoranalysis/test_tfa.py b/tests/factoranalysis/test_tfa.py deleted file mode 100644 index 041d03cbd..000000000 --- a/tests/factoranalysis/test_tfa.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_tfa(): - from brainiak.factoranalysis.tfa import TFA - import numpy as np - - n_voxel = 100 - n_tr = 20 - K = 5 - max_iter = 5 - max_num_voxel = n_voxel - max_num_tr = n_tr - tfa = TFA( - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - assert tfa, "Invalid TFA instance!" - - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - X = [1, 2, 3] - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "Input data should be an array" in str(excinfo.value) - - X = np.random.rand(n_voxel) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "Input data should be 2D array" in str(excinfo.value) - - X = np.random.rand(n_voxel, n_tr) - R = [1, 2, 3] - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "coordinate matrix should be an array" in str(excinfo.value) - - R = np.random.rand(n_voxel) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "coordinate matrix should be 2D array" in str(excinfo.value) - - R = np.random.randint(2, high=102, size=(n_voxel - 1, 3)) - # Check that does NOT run if n_voxel in X and R does not match - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "The number of voxels should be the same in X and R" in str( - excinfo.value) - - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - tfa.fit(X, R=R) - assert True, "Success running TFA with one subject!" - posterior_size = K * (tfa.n_dim + 1) - assert tfa.local_posterior_.shape[ - 0] == posterior_size,\ - "Invalid result of TFA! (wrong # element in local_posterior)" - - weight_method = 'ols' - tfa = TFA( - weight_method=weight_method, - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - assert tfa, "Invalid TFA instance!" - - X = np.random.rand(n_voxel, n_tr) - tfa.fit(X, R=R) - assert True, "Success running TFA with one subject!" - - template_prior, _, _ = tfa.get_template(R) - tfa.set_K(K) - tfa.set_seed(200) - tfa.fit(X, R=R, template_prior=template_prior) - assert True, "Success running TFA with one subject and template prior!" - assert tfa.local_posterior_.shape[ - 0] == posterior_size,\ - "Invalid result of TFA! (wrong # element in local_posterior)" - - weight_method = 'odd' - tfa = TFA( - weight_method=weight_method, - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - with pytest.raises(ValueError) as excinfo: - tfa.fit(X, R=R) - assert "'rr' and 'ols' are accepted as weight_method!" in str( - excinfo.value) diff --git a/tests/fcma/data/expected_processed_data.npy b/tests/fcma/data/expected_processed_data.npy deleted file mode 100644 index 460553dff13f0839b8571f9a0943cfb4eb237fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2672 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 Niah$^!R=(7{0Gj{kZb?| diff --git a/tests/fcma/data/expected_searchlight_processed_data.npy b/tests/fcma/data/expected_searchlight_processed_data.npy deleted file mode 100644 index e23174f2c7f6c3458e2fcb6f9bee6b5ecf7970c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1704016 zcmeI*F>BjE6aZj%?XS@07CaS`p%k)ZYsu1~Q!$N0htd?gTM6Vp^auH`RZj06_!&i& zX%c}xfq8zXJL&vApGMv94v{Bli0hZA z4i5_))gk#+-)P?*?zdIPceTg3j`;BGoUQ&HzI{TqKH2}o>-F8w`9pzPzwFt)4mm*`;g>*DSwhl zPxvOC^(uAfqlvmkzsS3;8~wz86UdG+sygq=eoQ&WI*RtlBX+dM^~L`qkn1-zk6Cn$ zi}T{+2xRwsm%0vPU8R`&%Lx!5K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfWR08>ihWZUonPq9L=HDr|g4}JM^AUb16VFKAZ=fke1Hvs|!2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF c5FkK+009C72oNAZfB*pk1PBlyKwzlAA8SKUk^lez diff --git a/tests/fcma/test_classification.py b/tests/fcma/test_classification.py deleted file mode 100644 index 3225fa80d..000000000 --- a/tests/fcma/test_classification.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.classifier import Classifier -from scipy.stats.mstats import zscore -from sklearn import svm -from sklearn.linear_model import LogisticRegression -import numpy as np -import math -from numpy.random import RandomState -from scipy.spatial.distance import hamming - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def create_epoch(idx, num_voxels): - row = 12 - col = num_voxels - mat = prng.rand(row, col).astype(np.float32) - # impose a pattern to even epochs - if idx % 2 == 0: - mat = np.sort(mat, axis=0) - mat = zscore(mat, axis=0, ddof=0) - # if zscore fails (standard deviation is zero), - # set all values to be zero - mat = np.nan_to_num(mat) - mat = mat / math.sqrt(mat.shape[0]) - return mat - - -def test_classification(): - fake_raw_data = [create_epoch(i, 5) for i in range(20)] - labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - # 5 subjects, 4 epochs per subject - epochs_per_subj = 4 - # svm - svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - training_data = fake_raw_data[0:12] - clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data)), labels[0:12]) - expected_confidence = np.array([-1.18234421, 0.97403604, -1.04005679, - 0.92403019, -0.95567738, 1.11746593, - -0.83275891, 0.9486868]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM does not provide correct results' - confidence = clf.decision_function(list(zip(fake_raw_data[12:], - fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence) - ) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM without recomputation ' \ - 'does not provide correct results' - y = [0, 1, 0, 1, 0, 1, 0, 1] - score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data[12:])), y) - assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ - 'the prediction score is incorrect' - # svm with partial similarity matrix computation - clf = Classifier(svm_clf, num_processed_voxels=2, - epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(fake_raw_data, fake_raw_data)), - labels, - num_training_samples=12) - y_pred = clf.predict() - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM (partial sim) does not ' \ - 'provide correct results' - confidence = clf.decision_function() - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM (partial sim) without recomputation ' \ - 'does not provide correct results' - # logistic regression - lr_clf = LogisticRegression() - clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data)), labels[0:12]) - expected_confidence = np.array([-4.49666484, 3.73025553, -4.04181695, - 3.73027436, -3.77043872, 4.42613412, - -3.35616616, 3.77716609]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via logistic regression ' \ - 'does not provide correct results' - confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence) - ) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression without precomputation ' \ - 'does not provide correct results' - - -def test_classification_with_two_components(): - fake_raw_data = [create_epoch(i, 5) for i in range(20)] - fake_raw_data2 = [create_epoch(i, 6) for i in range(20)] - labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - # 5 subjects, 4 epochs per subject - epochs_per_subj = 4 - # svm - svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - training_data = fake_raw_data[0: 12] - training_data2 = fake_raw_data2[0: 12] - clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data2)), labels[0:12]) - expected_confidence = np.array([-1.23311606, 1.02440964, -0.93898336, - 1.07028798, -1.04420007, 0.97647772, - -1.0498268, 1.04970111]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM does not provide correct results' - confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM without recomputation ' \ - 'does not provide correct results' - y = [0, 1, 0, 1, 0, 1, 0, 1] - score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data2[12:])), y) - assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ - 'the prediction score is incorrect' - # svm with partial similarity matrix computation - clf = Classifier(svm_clf, num_processed_voxels=2, - epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(fake_raw_data, fake_raw_data2)), - labels, - num_training_samples=12) - y_pred = clf.predict() - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM (partial sim) does not ' \ - 'provide correct results' - confidence = clf.decision_function() - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM (partial sim) without recomputation ' \ - 'does not provide correct results' - # logistic regression - lr_clf = LogisticRegression() - clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) - # specifying num_training_samples is for coverage - clf.fit(list(zip(training_data, training_data2)), - labels[0:12], - num_training_samples=12) - expected_confidence = np.array([-4.90819848, 4.22548132, -3.76255726, - 4.46505975, -4.19933099, 4.08313584, - -4.23070437, 4.31779758]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via logistic regression ' \ - 'does not provide correct results' - confidence = clf.decision_function(list(zip(fake_raw_data[12:], - fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression without precomputation ' \ - 'does not provide correct results' - - -if __name__ == '__main__': - test_classification() - test_classification_with_two_components() diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py deleted file mode 100644 index d551ff5ee..000000000 --- a/tests/fcma/test_mvpa_voxel_selection.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector -from brainiak.searchlight.searchlight import Searchlight -from sklearn import svm -import numpy as np -from mpi4py import MPI -from numpy.random import RandomState - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def test_mvpa_voxel_selection(): - data = prng.rand(5, 5, 5, 8).astype(np.float32) - # all MPI processes read the mask; the mask file is small - mask = np.ones([5, 5, 5], dtype=np.bool) - mask[0, 0, :] = False - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - sl = Searchlight(sl_rad=1) - mvs = MVPAVoxelSelector(data, mask, labels, 2, sl) - # for cross validation, use SVM with precomputed kernel - - clf = svm.SVC(kernel='rbf', C=10) - result_volume, results = mvs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [] - for tuple in results: - if tuple[1] > 0: - output.append(int(8*tuple[1])) - expected_output = [6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, - 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - - -if __name__ == '__main__': - test_mvpa_voxel_selection() diff --git a/tests/fcma/test_preprocessing.py b/tests/fcma/test_preprocessing.py deleted file mode 100644 index 0ee38db49..000000000 --- a/tests/fcma/test_preprocessing.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path - -import numpy as np - -from brainiak.fcma.preprocessing import (prepare_fcma_data, prepare_mvpa_data, - prepare_searchlight_mvpa_data) -from brainiak import io - -data_dir = Path(__file__).parents[1] / 'io' / 'data' -expected_dir = Path(__file__).parent / 'data' -suffix = 'bet.nii.gz' -mask_file = data_dir / 'mask.nii.gz' -epoch_file = data_dir / 'epoch_labels.npy' -expected_labels = np.array([0, 1, 0, 1]) - - -def test_prepare_fcma_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - mask = io.load_boolean_mask(mask_file) - conditions = io.load_labels(epoch_file) - raw_data, _, labels = prepare_fcma_data(images, conditions, mask) - expected_raw_data = np.load(expected_dir / 'expected_raw_data.npy') - assert len(raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - for idx in range(len(raw_data)): - assert np.allclose(raw_data[idx], expected_raw_data[idx]), \ - 'raw data do not match in test_prepare_fcma_data' - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_fcma_data' - from brainiak.fcma.preprocessing import RandomType - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, - random=RandomType.REPRODUCIBLE) - assert len(random_raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, - random=RandomType.UNREPRODUCIBLE) - assert len(random_raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - - -def test_prepare_mvpa_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - mask = io.load_boolean_mask(mask_file) - conditions = io.load_labels(epoch_file) - processed_data, labels = prepare_mvpa_data(images, conditions, mask) - expected_processed_data = np.load(expected_dir - / 'expected_processed_data.npy') - assert len(processed_data) == len(expected_processed_data), \ - 'numbers of epochs do not match in test_prepare_mvpa_data' - for idx in range(len(processed_data)): - assert np.allclose(processed_data[idx], - expected_processed_data[idx]), ( - 'raw data do not match in test_prepare_mvpa_data') - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_mvpa_data' - - -def test_prepare_searchlight_mvpa_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - conditions = io.load_labels(epoch_file) - processed_data, labels = prepare_searchlight_mvpa_data(images, - conditions) - expected_searchlight_processed_data = np.load( - expected_dir / 'expected_searchlight_processed_data.npy') - for idx in range(len(processed_data)): - assert np.allclose(processed_data[idx], - expected_searchlight_processed_data[idx]), ( - 'raw data do not match in test_prepare_searchlight_mvpa_data') - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_searchlight_mvpa_data' - from brainiak.fcma.preprocessing import RandomType - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_processed_data, _ = prepare_searchlight_mvpa_data( - images, - conditions, - random=RandomType.REPRODUCIBLE) - assert (len(random_processed_data) - == len(expected_searchlight_processed_data)), ( - 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_processed_data, _ = prepare_searchlight_mvpa_data( - images, - conditions, - random=RandomType.UNREPRODUCIBLE) - assert (len(random_processed_data) - == len(expected_searchlight_processed_data)), ( - 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') - - -if __name__ == '__main__': - test_prepare_fcma_data() - test_prepare_mvpa_data() - test_prepare_searchlight_mvpa_data() diff --git a/tests/fcma/test_util.py b/tests/fcma/test_util.py deleted file mode 100644 index 6508d49b1..000000000 --- a/tests/fcma/test_util.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.random import RandomState -from brainiak.fcma.util import compute_correlation - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def test_correlation_computation(): - row1 = 5 - col = 10 - row2 = 6 - mat1 = prng.rand(row1, col).astype(np.float32) - mat2 = prng.rand(row2, col).astype(np.float32) - corr = compute_correlation(mat1, mat1) - expected_corr = np.corrcoef(mat1) - assert np.allclose(corr, expected_corr, atol=1e-5), ( - "high performance correlation computation does not provide correct " - "correlation results within the same set") - corr = compute_correlation(mat1, mat2) - mat = np.concatenate((mat1, mat2), axis=0) - expected_corr = np.corrcoef(mat)[0:row1, row1:] - assert np.allclose(corr, expected_corr, atol=1e-5), ( - "high performance correlation computation does not provide correct " - "correlation results between two sets") - - -def test_correlation_nans(): - row1 = 5 - col = 10 - row2 = 6 - mat1 = prng.rand(row1, col).astype(np.float32) - mat2 = prng.rand(row2, col).astype(np.float32) - mat1[0, 0] = np.nan - corr = compute_correlation(mat1, mat2, return_nans=False) - assert np.all(corr == 0, axis=1)[0] - assert np.sum(corr == 0) == row2 - corr = compute_correlation(mat1, mat2, return_nans=True) - assert np.all(np.isnan(corr), axis=1)[0] - assert np.sum(np.isnan(corr)) == row2 - - -if __name__ == '__main__': - test_correlation_computation() - test_correlation_nans() diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py deleted file mode 100644 index 98fadc4ea..000000000 --- a/tests/fcma/test_voxel_selection.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.voxelselector import VoxelSelector -from scipy.stats.mstats import zscore -from sklearn import svm -from sklearn.linear_model import LogisticRegression -import numpy as np -import math -from mpi4py import MPI -from numpy.random import RandomState - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def create_epoch(): - row = 12 - col = 5 - mat = prng.rand(row, col).astype(np.float32) - mat = zscore(mat, axis=0, ddof=0) - # if zscore fails (standard deviation is zero), - # set all values to be zero - mat = np.nan_to_num(mat) - mat = mat / math.sqrt(mat.shape[0]) - return mat - - -def test_voxel_selection(): - fake_raw_data = [create_epoch() for i in range(8)] - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - vs = VoxelSelector(labels, 4, 2, fake_raw_data, voxel_unit=1) - # test scipy normalization - fake_corr = prng.rand(1, 4, 5).astype(np.float32) - fake_corr = vs._correlation_normalization(fake_corr) - if MPI.COMM_WORLD.Get_rank() == 0: - expected_fake_corr = [[[1.06988919, 0.51641309, -0.46790636, - -1.31926763, 0.2270218], - [-1.22142744, -1.39881694, -1.2979387, - 1.05702305, -0.6525566], - [0.89795232, 1.27406132, 0.36460185, - 0.87538344, 1.5227468], - [-0.74641371, -0.39165771, 1.40124381, - -0.61313909, -1.0972116]]] - assert np.allclose(fake_corr, expected_fake_corr), \ - 'within-subject normalization does not provide correct results' - # for cross validation, use SVM with precomputed kernel - # no shrinking, set C=1 - clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [7, 4, 6, 4, 4] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - # for cross validation, use logistic regression - clf = LogisticRegression() - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [6, 3, 6, 4, 4] - assert np.allclose(output, expected_output, atol=1), ( - "voxel selection via logistic regression does not provide correct " - "results") - - -def test_voxel_selection_with_two_masks(): - fake_raw_data1 = [create_epoch() for i in range(8)] - fake_raw_data2 = [create_epoch() for i in range(8)] - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - vs = VoxelSelector(labels, 4, 2, fake_raw_data1, - raw_data2=fake_raw_data2, voxel_unit=1) - # for cross validation, use SVM with precomputed kernel - # no shrinking, set C=1 - clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [3, 3, 3, 6, 6] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - # for cross validation, use logistic regression - clf = LogisticRegression() - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [3, 4, 4, 6, 6] - assert np.allclose(output, expected_output, atol=1), ( - "voxel selection via logistic regression does not provide correct " - "results") - - -if __name__ == '__main__': - test_voxel_selection() - test_voxel_selection_with_two_masks() diff --git a/tests/hyperparamopt/test_hpo.py b/tests/hyperparamopt/test_hpo.py deleted file mode 100644 index 3904458ee..000000000 --- a/tests/hyperparamopt/test_hpo.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest -import numpy as np -import scipy.stats as st -from brainiak.hyperparamopt.hpo import gmm_1d_distribution, fmin - - -def test_simple_gmm(): - x = np.array([1., 1., 2., 3., 1.]) - d = gmm_1d_distribution(x, min_limit=0., max_limit=4.) - assert d(1.1) > d(3.5), "GMM distribution not behaving correctly" - assert d(2.0) > d(3.0), "GMM distribution not behaving correctly" - assert d(-1.0) == 0, "GMM distribution out of bounds error" - assert d(9.0) == 0, "GMM distribution out of bounds error" - - samples = d.get_samples(n=25) - np.testing.assert_array_less(samples, 4.) - np.testing.assert_array_less(0., samples) - - -def test_simple_gmm_weights(): - x = np.array([1., 1., 2., 3., 1., 3.]) - d = gmm_1d_distribution(x) - - x2 = np.array([1., 2., 3.]) - w = np.array([3., 1., 2.]) - d2 = gmm_1d_distribution(x2, weights=w) - y2 = d2(np.array([1.1, 2.0])) - - assert d2(1.1) == y2[0],\ - "GMM distribution array & scalar results don't match" - assert np.abs(d(1.1) - d2(1.1)) < 1e-5,\ - "GMM distribution weights not handled correctly" - assert np.abs(d(2.0) - d2(2.0)) < 1e-5,\ - "GMM distribution weights not handled correctly" - - -def test_simple_hpo(): - - def f(args): - x = args['x'] - return x*x - - s = {'x': {'dist': st.uniform(loc=-10., scale=20), 'lo': -10., 'hi': 10.}} - trials = [] - - # Test fmin and ability to continue adding to trials - best = fmin(loss_fn=f, space=s, max_evals=40, trials=trials) - best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) - - assert len(trials) == 50, "HPO continuation trials not working" - - # Test verbose flag - best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) - - yarray = np.array([tr['loss'] for tr in trials]) - np.testing.assert_array_less(yarray, 100.) - - xarray = np.array([tr['x'] for tr in trials]) - np.testing.assert_array_less(np.abs(xarray), 10.) - - assert best['loss'] < 100., "HPO out of range" - assert np.abs(best['x']) < 10., "HPO out of range" - - # Test unknown distributions - s2 = {'x': {'dist': 'normal', 'mu': 0., 'sigma': 1.}} - trials2 = [] - with pytest.raises(ValueError) as excinfo: - fmin(loss_fn=f, space=s2, max_evals=40, trials=trials2) - assert "Unknown distribution type for variable" in str(excinfo.value) - - s3 = {'x': {'dist': st.norm(loc=0., scale=1.)}} - trials3 = [] - fmin(loss_fn=f, space=s3, max_evals=40, trials=trials3) diff --git a/tests/image/test_image.py b/tests/image/test_image.py deleted file mode 100644 index 4796bc3f1..000000000 --- a/tests/image/test_image.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Iterable, Sequence - -import numpy as np -import pytest - -from nibabel.nifti1 import Nifti1Pair -from nibabel.spatialimages import SpatialImage - -from brainiak.image import (mask_image, mask_images, MaskedMultiSubjectData, - multimask_images, SingleConditionSpec) - - -@pytest.fixture -def masked_multi_subject_data(masked_images): - return np.stack(masked_images, axis=-1) - - -class TestMaskedMultiSubjectData: - def test_from_masked_images(self, masked_images, - masked_multi_subject_data): - result = MaskedMultiSubjectData.from_masked_images(masked_images, - len(masked_images)) - assert np.array_equal(np.moveaxis(result, 1, 0), - masked_multi_subject_data) - - -@pytest.fixture -def condition_spec() -> SingleConditionSpec: - return np.array([[[1, 1, 1, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]]], - dtype=np.int8).view(SingleConditionSpec) - - -class TestUniqueLabelConditionSpec: - def test_extract_labels(self, condition_spec: SingleConditionSpec - ) -> None: - assert np.array_equal(condition_spec.extract_labels(), - np.array([0, 1])) - - -@pytest.fixture -def spatial_image() -> SpatialImage: - return Nifti1Pair(np.array([[[[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 1, 0], - [0, 1, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]]]).reshape(4, 4, 4, 1), - np.eye(4)) - - -@pytest.fixture -def mask() -> np.ndarray: - return np.array([[[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 1, 0], - [0, 1, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 1, 0], - [0, 1, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]], dtype=np.bool) - - -@pytest.fixture -def masked_data() -> np.ndarray: - return np.array([[1, 0, 0, 1, 0, 1, 1, 0]]).reshape(8, 1) - - -@pytest.fixture -def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]: - images = [spatial_image] - image_data = spatial_image.get_data().copy() - image_data[1, 1, 1, 0] = 2 - images.append(Nifti1Pair(image_data, np.eye(4))) - return images - - -@pytest.fixture -def masks(mask: np.ndarray) -> Sequence[np.ndarray]: - masks = [mask] - mask2 = mask.copy() - mask2[0, 0, 0] = 1 - masks.append(mask2) - mask3 = mask.copy() - mask3[2, 2, 2] = 0 - masks.append(mask3) - return masks - - -@pytest.fixture -def multimasked_images(masked_data) -> Iterable[Iterable[np.ndarray]]: - masked_data_2 = np.concatenate(([[2]], masked_data[1:, :])) - return [[masked_data, np.concatenate(([[0]], masked_data)), - masked_data[:-1, :]], - [masked_data_2, np.concatenate(([[0]], masked_data_2)), - masked_data_2[:-1, :]]] - - -@pytest.fixture -def masked_images(multimasked_images) -> Iterable[np.ndarray]: - return [multimasked_image[0] for multimasked_image in multimasked_images] - - -def test_mask_image(spatial_image: SpatialImage, mask: np.ndarray, - masked_data: np.ndarray) -> None: - result = mask_image(spatial_image, mask) - assert np.array_equal(result, masked_data) - - -def test_mask_image_with_type(spatial_image: SpatialImage, mask: np.ndarray, - masked_data: np.ndarray) -> None: - masked_data_type = np.float32 - result = mask_image(spatial_image, mask, masked_data_type) - assert result.dtype == masked_data_type - assert np.allclose(result, masked_data) - - -def test_multimask_images( - images: Iterable[SpatialImage], - masks: Sequence[np.ndarray], - multimasked_images: Iterable[Iterable[np.ndarray]] - ) -> None: - result = multimask_images(images, masks) - for result_images, expected_images in zip(result, - multimasked_images): - for result_image, expected_image in zip(result_images, - expected_images): - assert np.array_equal(result_image, expected_image) - - -def test_mask_images( - images: Iterable[SpatialImage], - mask: np.ndarray, - masked_images: Iterable[np.ndarray] - ) -> None: - result = mask_images(images, mask) - for result_image, expected_image in zip(result, masked_images): - assert np.array_equal(result_image, expected_image) diff --git a/tests/io/data/epoch_labels.npy b/tests/io/data/epoch_labels.npy deleted file mode 100644 index 50b0a4304f8d755e5ba6147e7c2ebe6021326848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7ItmbEXrQU1P^+Mzz{Lmz4EO<939;$`jT{x$ diff --git a/tests/io/data/mask.nii.gz b/tests/io/data/mask.nii.gz deleted file mode 100755 index 687ab69749ac9e33b4aa23f6e3bf269c37b6d2c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmb2|=3oE==C^kYvzQzuSRY7UV(CwqYA3*PLz^Y&=2G8@+%padSBUgK5L~<>QHRC) z|C8GLMjPH7dbe+W?){o?WzD748Qp&kbN`mC348qa__r_B|F&AJds~99kSj9e&V_GzaCYpfvaTyQ~&ez z32dn14y-5dKEL;-al)M16f~)V`jdO_e|h)Y@fXje&GVN&XGhW5pmk~Y{+B;DvO5%& Vp-9kx)6iAN5cDRYH=&V@0RSu0fKUJc diff --git a/tests/io/data/subject1_bet.nii.gz b/tests/io/data/subject1_bet.nii.gz deleted file mode 100644 index f34b35c1d201cfae247bc3e4857f17eaee53a5f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7149 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 P=cU0(r*}}F+er8i>K-AX diff --git a/tests/io/test_io.py b/tests/io/test_io.py deleted file mode 100644 index 9f72550f6..000000000 --- a/tests/io/test_io.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path -from typing import Iterable, Sequence - -import nibabel as nib -import numpy as np -import pytest - -from brainiak import io - - -@pytest.fixture -def in_dir() -> Path: - return Path(__file__).parent / "data" - - -@pytest.fixture -def expected_image_data_shape() -> Sequence[int]: - return (64, 64, 26, 10) - - -@pytest.fixture -def mask_path(in_dir: Path) -> Path: - return in_dir / "mask.nii.gz" - - -@pytest.fixture -def labels_path(in_dir: Path) -> Path: - return in_dir / "epoch_labels.npy" - - -@pytest.fixture -def expected_condition_spec_shape() -> Sequence[int]: - return (2, 2, 10) - - -@pytest.fixture -def expected_n_subjects() -> int: - return 2 - - -@pytest.fixture -def image_paths(in_dir: Path) -> Iterable[Path]: - return (in_dir / "subject1_bet.nii.gz", in_dir / "subject2_bet.nii.gz") - - -def test_load_images_from_dir_data_shape( - in_dir: Path, - expected_image_data_shape: Sequence[int], - expected_n_subjects: int - ) -> None: - for i, image in enumerate(io.load_images_from_dir(in_dir, "bet.nii.gz")): - assert image.get_data().shape == (64, 64, 26, 10) - assert i + 1 == expected_n_subjects - - -def test_load_images_data_shape( - image_paths: Iterable[Path], - expected_image_data_shape: Sequence[int], - expected_n_subjects: int - ) -> None: - for i, image in enumerate(io.load_images(image_paths)): - assert image.get_data().shape == (64, 64, 26, 10) - assert i + 1 == expected_n_subjects - - -def test_load_boolean_mask(mask_path: Path) -> None: - mask = io.load_boolean_mask(mask_path) - assert mask.dtype == np.bool - - -def test_load_boolean_mask_predicate(mask_path: Path) -> None: - mask = io.load_boolean_mask(mask_path, lambda x: np.logical_not(x)) - expected_mask = np.logical_not(io.load_boolean_mask(mask_path)) - assert np.array_equal(mask, expected_mask) - - -def test_load_labels(labels_path: Path, - expected_condition_spec_shape: Sequence[int], - expected_n_subjects: int) -> None: - condition_specs = io.load_labels(labels_path) - i = 0 - for condition_spec in condition_specs: - assert condition_spec.shape == expected_condition_spec_shape - i += 1 - assert i == expected_n_subjects - - -def test_save_as_nifti_file(tmpdir) -> None: - out_file = str(tmpdir / "nifti.nii") - shape = (4, 4, 4) - io.save_as_nifti_file(np.ones(shape), np.eye(4), out_file) - assert nib.load(out_file).get_data().shape == shape diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py deleted file mode 100644 index fc4ea17c1..000000000 --- a/tests/isc/test_isc.py +++ /dev/null @@ -1,956 +0,0 @@ -import numpy as np -import logging -import pytest -from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, - squareform_isfc, timeshift_isc, - phaseshift_isc) -from scipy.spatial.distance import squareform - -logger = logging.getLogger(__name__) - - -# Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, - noise=1, data_type='array', - random_state=None): - prng = np.random.RandomState(random_state) - if n_voxels: - signal = prng.randn(n_TRs, n_voxels) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs, n_voxels) * noise - for subject in np.arange(n_subjects)] - elif not n_voxels: - signal = prng.randn(n_TRs) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs) * noise - for subject in np.arange(n_subjects)] - if data_type == 'array': - if n_voxels: - data = np.dstack(data) - elif not n_voxels: - data = np.column_stack(data) - return data - - -# Create 3 voxel simulated data with correlated time series -def correlated_timeseries(n_subjects, n_TRs, noise=0, - random_state=None): - prng = np.random.RandomState(random_state) - signal = prng.randn(n_TRs) - correlated = True - while correlated: - uncorrelated = np.random.randn(n_TRs, - n_subjects)[:, np.newaxis, :] - unc_max = np.amax(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - unc_mean = np.mean(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - if unc_max < .3 and np.abs(unc_mean) < .001: - correlated = False - data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], - 20, axis=2) - data = np.concatenate((data, uncorrelated), axis=1) - data = data + np.random.randn(n_TRs, 3, n_subjects) * noise - return data - - -# Compute ISCs using different input types -# List of subjects with one voxel/ROI -def test_isc_input(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC inputs") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with one voxel/ROI - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - # List of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - logger.info("Finished testing ISC inputs") - - -# Check pairwise and leave-one-out, and summary statistics for ISC -def test_isc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs_loo = isc(data, pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_subjects, n_voxels) - - # Just two subjects - iscs_loo = isc(data[..., :2], pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_voxels,) - - iscs_pw = isc(data, pairwise=True, summary_statistic=None) - assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - - # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic='mean') - assert isc_mean.shape == (n_voxels,) - - isc_median = isc(data, pairwise=False, summary_statistic='median') - assert isc_median.shape == (n_voxels,) - - with pytest.raises(ValueError): - isc(data, pairwise=False, summary_statistic='min') - - logger.info("Finished testing ISC options") - - -# Make sure ISC recovers correlations of 1 and less than 1 -def test_isc_output(): - - logger.info("Testing ISC outputs") - - data = correlated_timeseries(20, 60, noise=0, - random_state=42) - iscs = isc(data, pairwise=False) - assert np.allclose(iscs[:, :2], 1., rtol=1e-05) - assert np.all(iscs[:, -1] < 1.) - - iscs = isc(data, pairwise=True) - assert np.allclose(iscs[:, :2], 1., rtol=1e-05) - assert np.all(iscs[:, -1] < 1.) - - logger.info("Finished testing ISC outputs") - - -# Check for proper handling of NaNs in ISC -def test_isc_nans(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Inject NaNs into data - data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - iscs_loo = isc(data, pairwise=False, tolerate_nans=False) - assert np.sum(np.isnan(iscs_loo)) == n_subjects - - # Tolerate all NaNs, only subject with NaNs yields NaN - iscs_loo = isc(data, pairwise=False, tolerate_nans=True) - assert np.sum(np.isnan(iscs_loo)) == 1 - - # Pairwise approach shouldn't care - iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) - iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) - assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(iscs_pw_T)) == - np.sum(np.isnan(iscs_pw_F)) == - n_subjects - 1) - - # Set proportion of nans to reject (70% and 90% non-NaN) - data[0, 0, :] = np.nan - data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan - data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan - - iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) - iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) - iscs_loo_95 = isc(data, pairwise=False, tolerate_nans=.95) - iscs_loo_90 = isc(data, pairwise=False, tolerate_nans=.90) - iscs_loo_80 = isc(data, pairwise=False, tolerate_nans=.8) - iscs_loo_70 = isc(data, pairwise=False, tolerate_nans=.7) - iscs_loo_60 = isc(data, pairwise=False, tolerate_nans=.6) - - assert (np.sum(np.isnan(iscs_loo_F)) == - np.sum(np.isnan(iscs_loo_95)) == 60) - assert (np.sum(np.isnan(iscs_loo_80)) == - np.sum(np.isnan(iscs_loo_90)) == 42) - assert (np.sum(np.isnan(iscs_loo_T)) == - np.sum(np.isnan(iscs_loo_60)) == - np.sum(np.isnan(iscs_loo_70)) == 28) - assert np.array_equal(np.sum(np.isnan(iscs_loo_F), axis=0), - np.sum(np.isnan(iscs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(iscs_loo_80), axis=0), - np.sum(np.isnan(iscs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(iscs_loo_T), axis=0), - np.sum(np.isnan(iscs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(iscs_loo_T), axis=0), - np.sum(np.isnan(iscs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(iscs_loo_60), axis=0), - np.sum(np.isnan(iscs_loo_70), axis=0)))) - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Make sure voxel with NaNs across all subjects is always removed - data[0, 0, :] = np.nan - iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) - iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) - assert np.allclose(iscs_loo_T, iscs_loo_F, equal_nan=True) - assert (np.sum(np.isnan(iscs_loo_T)) == - np.sum(np.isnan(iscs_loo_F)) == - n_subjects) - - iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) - iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) - assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(iscs_pw_T)) == - np.sum(np.isnan(iscs_pw_F)) == - n_subjects * (n_subjects - 1) / 2) - - -# Test one-sample bootstrap test -def test_bootstrap_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - n_bootstraps = 10 - - logger.info("Testing bootstrap hypothesis test") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs = isc(data, pairwise=False, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Test one-sample bootstrap test with pairwise approach - n_bootstraps = 10 - - iscs = isc(data, pairwise=True, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Check random seeds - iscs = isc(data, pairwise=False, summary_statistic=None) - distributions = [] - for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc( - iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95, - random_state=random_state) - distributions.append(distribution) - assert np.array_equal(distributions[0], distributions[1]) - assert not np.array_equal(distributions[1], distributions[2]) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='median')) - - logger.info("Finished testing bootstrap hypothesis test") - - -# Test permutation test with group assignments -def test_permutation_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - group_assignment = [1] * 10 + [2] * 10 - - logger.info("Testing permutation test") - - # Create dataset with two groups in pairwise approach - data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=5, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=200) - - # Create data with two groups in leave-one-out approach - data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=10, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=200) - - # One-sample leave-one-out permutation test - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=False, - summary_statistic='median', - n_permutations=200) - - # One-sample pairwise permutation test - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=True, - summary_statistic='median', - n_permutations=200) - - # Small one-sample pairwise exact test - data = simulated_timeseries(12, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median', - n_permutations=10000) - - # Small two-sample pairwise exact test (and unequal groups) - data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=10000) - - # Small two-sample leave-one-out exact test (and unequal groups) - data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=10000) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing permutaton test") - - -def test_timeshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing circular time-shift") - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, pairwise - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing circular time-shift") - - -# Phase randomization test -def test_phaseshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing phase randomization") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Phase randomization one-sample test, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing phase randomization") - - -# Test ISFC -def test_isfc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing ISFC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - isfcs, iscs = isfc(data, pairwise=False, summary_statistic=None) - assert isfcs.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) - assert iscs.shape == (n_subjects, n_voxels) - - # Without vectorized upper triangle - isfcs = isfc(data, pairwise=False, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_voxels) - - # Just two subjects - isfcs, iscs = isfc(data[..., :2], pairwise=False, summary_statistic=None) - assert isfcs.shape == (n_voxels * (n_voxels - 1) / 2,) - assert iscs.shape == (n_voxels,) - - isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_voxels, n_voxels) - - # ISFC with pairwise approach - isfcs, iscs = isfc(data, pairwise=True, summary_statistic=None) - assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels * (n_voxels - 1) / 2) - assert iscs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels) - - isfcs = isfc(data, pairwise=True, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels, n_voxels) - - # ISFC with summary statistics - isfcs, iscs = isfc(data, pairwise=True, summary_statistic='mean') - isfcs, iscs = isfc(data, pairwise=True, summary_statistic='median') - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) - assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) - assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) - - isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) - assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) - assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=False) - isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) - isfcs, iscs_v = isfc(data, pairwise=False) - assert np.allclose(iscs, iscs_v, rtol=1e-03) - - # Check that ISC and ISFC diagonal are identical (pairwise) - iscs = isc(data, pairwise=True) - isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) - isfcs, iscs_v = isfc(data, pairwise=True) - assert np.allclose(iscs, iscs_v, rtol=1e-03) - - # Generate 'targets' data and use for ISFC - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - n_targets = 15 - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Ensure 'square' output enforced - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=True) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Check list input for targets - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='list') - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Check that mismatching subjects / TRs breaks targets - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - - with pytest.raises(ValueError): - isfcs = isfc(data, targets=targets_data[..., :-1], - pairwise=False, vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - with pytest.raises(ValueError): - isfcs = isfc(data, targets=targets_data[:-1, ...], - pairwise=False, vectorize_isfcs=False) - - # Check targets for only 2 subjects - isfcs = isfc(data[..., :2], targets=targets_data[..., :2], - pairwise=False, summary_statistic=None) - assert isfcs.shape == (2, n_voxels, n_targets) - - isfcs = isfc(data[..., :2], targets=targets_data[..., :2], - pairwise=True, summary_statistic=None) - assert isfcs.shape == (2, n_voxels, n_targets) - - # Check that supplying targets enforces leave-one-out - isfcs_pw = isfc(data, targets=targets_data, pairwise=True, - vectorize_isfcs=False, tolerate_nans=False) - assert isfcs_pw.shape == (n_subjects, n_voxels, n_targets) - - logger.info("Finished testing ISFC options") - - -# Check for proper handling of NaNs in ISFC -def test_isfc_nans(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Inject NaNs into data - data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels * 2 - 1) - - # With vectorized ISFCs - isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels - 1) - - # Tolerate all NaNs, only subject with NaNs yields NaN - isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == n_voxels * 2 - 1 - - isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == n_voxels - 1 - - # Pairwise approach shouldn't care - isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_F)) == - (n_voxels * 2 - 1) * (n_subjects - 1)) - - isfcs_pw_T, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, - tolerate_nans=True) - isfcs_pw_F, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_F)) == - (n_voxels - 1) * (n_subjects - 1)) - - # Set proportion of nans to reject (70% and 90% non-NaN) - data[0, 0, :] = np.nan - data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan - data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan - - isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - isfcs_loo_95 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.95) - isfcs_loo_90 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.90) - isfcs_loo_80 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.8) - isfcs_loo_70 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.7) - isfcs_loo_60 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.6) - assert (np.sum(np.isnan(isfcs_loo_F)) == - np.sum(np.isnan(isfcs_loo_95)) == 3420) - assert (np.sum(np.isnan(isfcs_loo_80)) == - np.sum(np.isnan(isfcs_loo_90)) == 2430) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_60)) == - np.sum(np.isnan(isfcs_loo_70)) == 1632) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), - np.sum(np.isnan(isfcs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), - np.sum(np.isnan(isfcs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_60), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)))) - - isfcs_loo_T, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=True) - isfcs_loo_F, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=False) - isfcs_loo_95, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.95) - isfcs_loo_90, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.90) - isfcs_loo_80, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.8) - isfcs_loo_70, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.7) - isfcs_loo_60, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.6) - assert (np.sum(np.isnan(isfcs_loo_F)) == - np.sum(np.isnan(isfcs_loo_95)) == 1680) - assert (np.sum(np.isnan(isfcs_loo_80)) == - np.sum(np.isnan(isfcs_loo_90)) == 1194) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_60)) == - np.sum(np.isnan(isfcs_loo_70)) == 802) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), - np.sum(np.isnan(isfcs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), - np.sum(np.isnan(isfcs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_60), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)))) - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Make sure voxel with NaNs across all subjects is always removed - data[0, 0, :] = np.nan - isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_loo_T, isfcs_loo_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_F)) == - 1180) - - isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_T)) == - 11210) - - # Check for NaN-handling in targets - n_targets = 15 - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - - # Inject NaNs into targets_data - targets_data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - isfcs_loo = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False, tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == (n_subjects - 1) * (n_targets * 2) - - # Single NaN in targets will get averaged out with tolerate - isfcs_loo = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False, tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == 0 - - -def test_squareform_isfc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Generate square redundant ISFCs - isfcs_r = isfc(data, vectorize_isfcs=False) - assert isfcs_r.shape == (n_subjects, n_voxels, n_voxels) - - # Squareform these into condensed ISFCs and ISCs - isfcs_c, iscs_c = squareform_isfc(isfcs_r) - assert isfcs_c.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) - assert iscs_c.shape == (n_subjects, n_voxels) - - # Go back the other way and check it's the same - isfcs_new = squareform_isfc(isfcs_c, iscs_c) - assert np.array_equal(isfcs_r, isfcs_new) - - # Check against ISC function - assert np.allclose(isc(data), iscs_c, rtol=1e-03) - - # Check for two subjects - isfcs_r = isfc(data[..., :2], vectorize_isfcs=False) - assert isfcs_r.shape == (n_voxels, n_voxels) - isfcs_c, iscs_c = squareform_isfc(isfcs_r) - assert isfcs_c.shape == (n_voxels * (n_voxels - 1) / 2,) - assert iscs_c.shape == (n_voxels,) - assert np.array_equal(isfcs_r, squareform_isfc(isfcs_c, iscs_c)) - - -if __name__ == '__main__': - test_isc_input() - test_isc_options() - test_isc_output() - test_isc_nans() - test_bootstrap_isc() - test_permutation_isc() - test_timeshift_isc() - test_phaseshift_isc() - test_isfc_options() - test_isfc_nans() - test_squareform_isfc() - logger.info("Finished all ISC tests") diff --git a/tests/reprsimil/example_design.1D b/tests/reprsimil/example_design.1D deleted file mode 100755 index 7385edea6..000000000 --- a/tests/reprsimil/example_design.1D +++ /dev/null @@ -1,272 +0,0 @@ -# - 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 - 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 - 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 - 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 - 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 - 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 - 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 - 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 - 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 - 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 - 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 - 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 - 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 - 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 - 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 - 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 - 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 - 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 - 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 - 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 - 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 - 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 - 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 - 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 - 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 - 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 - 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 - 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 - 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 - 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 - 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 - 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 - 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 - 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 - 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 - 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 - 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 - 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 - 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 - 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 - 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 - 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 - 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 - 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 - 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 - 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 - 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 - 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 - 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 - 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 - 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 - 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 - 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 - 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 - 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 - 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 - 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 - 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 - 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 - 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 - 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 - 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 - 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 - 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 - 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 - 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 - 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 - 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 - 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 - 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 - 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 - 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 - 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 - 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 - 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 - 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 - 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 - 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 - 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 - 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 - 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 - 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 - 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 - 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 - 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 - 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 - 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 - 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 - 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 - 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 - 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 - 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 - 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 - 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 - 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 - 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 - 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 - 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 - 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 - 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 - 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 - 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 - 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 - 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 - 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 - 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 - 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 - 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 - 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 - 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 - 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 - 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 - 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 - 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 - 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 - 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 - 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 - 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 - 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 - 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 - 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 - 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 - 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 - 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 - 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 - 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 - 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 - 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 - 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 - 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 - 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 - 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 - 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 - 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 - 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 - 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 - 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 - 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 - 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 - 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 - 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 - 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 - 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 - 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 - 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 - 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 - 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 - 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 - 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 - 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 - 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 - 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 - 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 - 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 - 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 - 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 - 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 - 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 - 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 - 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 - 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 - 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 - 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 - 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 - 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 - 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 - 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 - 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 - 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 - 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 - 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 - 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 - 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 - 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 - 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 - 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 - 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 - 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 - 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 - 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 - 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 - 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 - 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 - 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 - 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 - 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 -# - diff --git a/tests/reprsimil/test_brsa.py b/tests/reprsimil/test_brsa.py deleted file mode 100755 index fbf898b86..000000000 --- a/tests/reprsimil/test_brsa.py +++ /dev/null @@ -1,651 +0,0 @@ -# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, -# Princeton University -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_can_instantiate(): - import brainiak.reprsimil.brsa - s = brainiak.reprsimil.brsa.BRSA() - assert s, "Invalid BRSA instance!" - - s = brainiak.reprsimil.brsa.BRSA( - n_iter=50, rank=5, auto_nuisance=False, n_nureg=2, nureg_method='ICA', - baseline_single=False, init_iter=5, GP_space=True, GP_inten=True, - tol=2e-3, eta=0.001, space_smooth_range=10.0, inten_smooth_range=100.0, - tau_range=2.0, - tau2_prior=brainiak.reprsimil.brsa.prior_GP_var_inv_gamma, - optimizer='CG', random_state=100, anneal_speed=20) - assert s, "Invalid BRSA instance!" - - -def test_fit(): - from brainiak.reprsimil.brsa import BRSA - import brainiak.utils.utils as utils - import scipy.stats - import numpy as np - import os.path - np.random.seed(10) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 2 times, mimicking 2 runs of itenditcal timing - n_run = 2 - design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) - design.n_TR = design.n_TR * n_run - - # start simulating some data - n_V = 50 - n_C = np.size(design.design_task, axis=1) - n_T = design.n_TR - - noise_bot = 0.5 - noise_top = 5.0 - noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot - - # generating noise - noise = np.zeros([n_T, n_V]) - noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ - np.random.randn(n_V) * noise_level - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_level = 5.0 # test with high SNR - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends - # not only on beta but also on x. - inten = np.random.rand(n_V) * 20.0 - - # parameters of Gaussian process to generate pseuso SNR - tau = 1.0 - smooth_width = 5.0 - inten_kernel = 1.0 - - coords = np.arange(0, n_V)[:, None] - - dist2 = np.square(coords - coords.T) - - inten_tile = np.tile(inten, [n_V, 1]) - inten_diff2 = (inten_tile - inten_tile.T)**2 - - K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / - inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 - - L = np.linalg.cholesky(K) - snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level - sqrt_v = noise_level * snr - betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v - signal = np.dot(design.design_task, betas_simulated) - - # Adding noise to signal as data - Y = signal + noise + inten - - scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) - - # Test fitting with GP prior. - brsa = BRSA(GP_space=True, GP_inten=True, n_iter=5, - init_iter=10, auto_nuisance=False, tol=2e-3) - - # We also test that it can detect baseline regressor included in the - # design matrix for task conditions - wrong_design = np.insert(design.design_task, 0, 1, axis=1) - with pytest.raises(ValueError) as excinfo: - brsa.fit(X=Y, design=wrong_design, scan_onsets=scan_onsets, - coords=coords, inten=inten) - assert ('Your design matrix appears to have included baseline time series.' - in str(excinfo.value)) - # Now we fit with the correct design matrix. - brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets, - coords=coords, inten=inten) - - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], - u_i[np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - - noise_new = np.zeros([n_T, n_V]) - noise_new[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise_new[i_t, :] = noise_new[i_t - 1, :] * \ - rho1 + np.random.randn(n_V) * noise_level - - Y_new = signal + noise_new + inten - ts, ts0 = brsa.transform(Y_new, scan_onsets=scan_onsets) - p = scipy.stats.pearsonr(ts[:, 0], design.design_task[:, 0])[1] - assert p < 0.01, ( - "Recovered time series does not correlate with true time series!") - assert np.shape(ts) == (n_T, n_C) and np.shape(ts0) == (n_T, 1), ( - "Wrong shape in returned time series by transform function!") - - [score, score_null] = brsa.score( - X=Y_new, design=design.design_task, scan_onsets=scan_onsets) - assert score > score_null, ( - "Full model does not win over null model on data containing signal") - - [score, score_null] = brsa.score(X=noise_new + inten, - design=design.design_task, - scan_onsets=scan_onsets) - assert score < score_null, ( - "Null model does not win over full model on data without signal") - - # Test fitting with lower rank, nuisance regressors and without GP prior - rank = n_C - 1 - n_nureg = 1 - brsa = BRSA(rank=rank, n_nureg=n_nureg, tol=2e-3, - n_iter=8, init_iter=4, auto_nuisance=True) - brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets) - # u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ - np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - - assert (not hasattr(brsa, 'bGP_') - and not hasattr(brsa, 'lGPspace_') - and not hasattr(brsa, 'lGPinten_') - ), ("the BRSA object should not have parameters of GP if GP is " - "not requested.") - # GP parameters are not set if not requested - assert brsa.beta0_.shape[0] == n_nureg + 1, 'Shape of beta0 incorrect' - p = scipy.stats.pearsonr(brsa.beta0_[0, :], inten)[1] - assert p < 0.01, ( - 'recovered beta0 does not correlate with the baseline of voxels.') - assert np.shape(brsa.L_) == ( - n_C, rank), 'Cholesky factor should have shape of (n_C, rank)' - - # Test fitting with GP over just spatial coordinates. - brsa = BRSA(GP_space=True, baseline_single=False, - tol=2e-3, n_iter=4, init_iter=4) - brsa.fit(X=Y, design=design.design_task, - scan_onsets=scan_onsets, coords=coords) - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ - np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - assert not hasattr(brsa, 'lGPinten_'), ( - "the BRSA object should not have parameters of lGPinten_ if only " - "smoothness in space is requested.") - # GP parameters are not set if not requested - - -def test_gradient(): - from brainiak.reprsimil.brsa import BRSA - import brainiak.utils.utils as utils - import numpy as np - import os.path - import numdifftools as nd - - np.random.seed(100) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - n_run = 4 - # concatenate it by 4 times, mimicking 4 runs of itenditcal timing - design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) - design.n_TR = design.n_TR * n_run - - # start simulating some data - n_V = 30 - n_C = np.size(design.design_task, axis=1) - n_T = design.n_TR - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot - - # generating noise - noise = np.zeros([n_T, n_V]) - noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ - np.random.randn(n_V) * noise_level - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0, 0] = 0.2 - ideal_cov[5:9, 5:9] = 0.6 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_level = 5.0 # test with high SNR - inten = np.random.randn(n_V) * 20.0 - - # parameters of Gaussian process to generate pseuso SNR - tau = 0.8 - smooth_width = 5.0 - inten_kernel = 1.0 - - coords = np.arange(0, n_V)[:, None] - - dist2 = np.square(coords - coords.T) - - inten_tile = np.tile(inten, [n_V, 1]) - inten_diff2 = (inten_tile - inten_tile.T)**2 - - K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / - inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 - - L = np.linalg.cholesky(K) - snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - sqrt_v = noise_level * snr - betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v - signal = np.dot(design.design_task, betas_simulated) - - # Adding noise to signal as data - Y = signal + noise - - scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) - - # Test fitting with GP prior. - brsa = BRSA(GP_space=True, GP_inten=True, rank=n_C) - - # Additionally, we test the generation of re-used terms. - X0 = np.ones(n_T)[:, None] - D, F, run_TRs, n_run_returned = brsa._prepare_DF( - n_T, scan_onsets=scan_onsets) - assert np.shape(D) == (n_T, n_T), 'D has wrong shape' - assert np.shape(F) == (n_T, n_T), 'F has wrong shape' - assert np.sum(D) == (n_T - n_run) * 2, 'D is initialized incorrectly.' - assert np.sum(F) == n_T - n_run * 2, 'F is initialized incorrectly.' - assert n_run_returned == n_run, ( - 'There is mistake in counting number of runs') - assert np.sum(run_TRs) == n_T, ( - 'The segmentation of the total experiment duration is wrong') - (XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX - ) = brsa._prepare_data_XY(design.design_task, Y, D, F) - (X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, X_base, - n_X0, idx_DC - ) = brsa._prepare_data_XYX0(design.design_task, Y, X0, - np.random.randn(n_T)[:, None], D, F, run_TRs, - no_DC=False) - assert (np.shape(XTY) == (n_C, n_V) - and np.shape(XTDY) == (n_C, n_V) - and np.shape(XTFY) == (n_C, n_V) - ), 'Dimension of XTY etc. returned from _prepare_data is wrong' - assert (np.ndim(YTY_diag) == 1 - and np.ndim(YTDY_diag) == 1 - and np.ndim(YTFY_diag) == 1 - ), ("Dimension of YTY_diag etc. returned from _prepare_data is " - "wrong") - assert (np.ndim(XTX) == 2 - and np.ndim(XTDX) == 2 - and np.ndim(XTFX) == 2 - ), 'Dimension of XTX etc. returned from _prepare_data is wrong' - assert (np.ndim(X0TX0) == 2 - and np.ndim(X0TDX0) == 2 - and np.ndim(X0TFX0) == 2 - ), 'Dimension of X0TX0 etc. returned from _prepare_data is wrong' - assert (np.ndim(XTX0) == 2 - and np.ndim(XTDX0) == 2 - and np.ndim(XTFX0) == 2 - ), 'Dimension of XTX0 etc. returned from _prepare_data is wrong' - assert (np.ndim(X0TY) == 2 - and np.ndim(X0TDY) == 2 - and np.ndim(X0TFY) == 2 - ), 'Dimension of X0TY etc. returned from _prepare_data is wrong' - assert (np.shape(X0) == (n_T, n_X0) - and np.shape(X_base) == (n_T, np.size(idx_DC)) - and np.max(idx_DC) < n_X0 - and np.size(idx_DC) + 1 == n_X0 - ), ("Dimension of X0 or X_base, or n_X0 or indices of DC " - "components are wrong.") - l_idx = np.tril_indices(n_C) - n_l = np.size(l_idx[0]) - - # Make sure all the fields are in the indices. - idx_param_sing, idx_param_fitU, idx_param_fitV = brsa._build_index_param( - n_l, n_V, 2) - assert 'Cholesky' in idx_param_sing and 'a1' in idx_param_sing, \ - 'The dictionary for parameter indexing misses some keys' - assert 'Cholesky' in idx_param_fitU and 'a1' in idx_param_fitU, \ - 'The dictionary for parameter indexing misses some keys' - assert 'log_SNR2' in idx_param_fitV and 'c_space' in idx_param_fitV \ - and 'c_inten' in idx_param_fitV and 'c_both' in idx_param_fitV, \ - 'The dictionary for parameter indexing misses some keys' - - # Initial parameters are correct parameters with some perturbation - param0_fitU = np.random.randn(n_l + n_V) * 0.1 - param0_fitV = np.random.randn(n_V + 1) * 0.1 - param0_sing = np.random.randn(n_l + 1) * 0.1 - param0_sing[idx_param_sing['a1']] += np.mean(np.tan(rho1 * np.pi / 2)) - param0_fitV[idx_param_fitV['log_SNR2']] += np.log(snr[:n_V - 1]) * 2 - param0_fitV[idx_param_fitV['c_space']] += np.log(smooth_width) * 2 - param0_fitV[idx_param_fitV['c_inten']] += np.log(inten_kernel) * 2 - - # test if the gradients are correct - # log likelihood and derivative of the _singpara function - - ll0, deriv0 = brsa._loglike_AR1_singpara(param0_sing, XTX, XTDX, XTFX, - YTY_diag, YTDY_diag, YTFY_diag, - XTY, XTDY, XTFY, X0TX0, X0TDX0, - X0TFX0, XTX0, XTDX0, XTFX0, X0TY, - X0TDY, X0TFY, l_idx, n_C, n_T, - n_V, n_run, n_X0, idx_param_sing) - # We test the gradient to the Cholesky factor - vec = np.zeros(np.size(param0_sing)) - vec[idx_param_sing['Cholesky'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, - XTDX0, XTFX0, X0TY, X0TDY, X0TFY, - l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_sing)[0], - param0_sing, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of singpara wrt Cholesky is incorrect') - - # We test the gradient to a1 - vec = np.zeros(np.size(param0_sing)) - vec[idx_param_sing['a1']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, - XTDX0, XTFX0, X0TY, X0TDY, X0TFY, - l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_sing)[0], - param0_sing, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), - rtol=1e-5), 'gradient of singpara wrt a1 is incorrect' - - # log likelihood and derivative of the fitU function. - ll0, deriv0 = brsa._loglike_AR1_diagV_fitU(param0_fitU, XTX, XTDX, XTFX, - YTY_diag, YTDY_diag, YTFY_diag, - XTY, XTDY, XTFY, X0TX0, X0TDX0, - X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, np.log(snr) - * 2, l_idx, n_C, n_T, n_V, - n_run, n_X0, idx_param_fitU, - n_C) - - # We test the gradient wrt the reparametrization of AR(1) coefficient of - # noise. - vec = np.zeros(np.size(param0_fitU)) - vec[idx_param_fitU['a1'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitU wrt to AR(1) coefficient incorrect') - - # We test if the numerical and analytical gradient wrt to the first - # element of Cholesky factor is correct - vec = np.zeros(np.size(param0_fitU)) - vec[idx_param_fitU['Cholesky'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitU wrt Cholesky factor incorrect') - - # Test on a random direction - vec = np.random.randn(np.size(param0_fitU)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), - rtol=1e-5), 'gradient of fitU incorrect' - - # We test the gradient of _fitV wrt to log(SNR^2) assuming no GP prior. - X0TAX0, XTAX0, X0TAY, X0TAX0_i, \ - XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL = \ - brsa._precompute_ar1_quad_forms(XTY, XTDY, XTFY, - YTY_diag, YTDY_diag, YTFY_diag, - XTX, XTDX, XTFX, - X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, - L_full, rho1, n_V, n_X0) - assert np.shape(XTAcorrX) == (n_V, n_C, n_C), ( - 'Dimension of XTAcorrX is wrong by _precompute_ar1_quad_forms()') - assert XTAcorrY.shape == XTY.shape, ( - 'Shape of XTAcorrY is wrong by _precompute_ar1_quad_forms()') - assert YTAcorrY.shape == YTY_diag.shape, ( - 'Shape of YTAcorrY is wrong by _precompute_ar1_quad_forms()') - assert np.shape(X0TAX0) == (n_V, n_X0, n_X0), ( - 'Dimension of X0TAX0 is wrong by _precompute_ar1_quad_forms()') - assert np.shape(XTAX0) == (n_V, n_C, n_X0), ( - 'Dimension of XTAX0 is wrong by _precompute_ar1_quad_forms()') - assert X0TAY.shape == X0TY.shape, ( - 'Shape of X0TAX0 is wrong by _precompute_ar1_quad_forms()') - assert np.all(np.isfinite(X0TAX0_i)), ( - 'Inverse of X0TAX0 includes NaN or Inf') - ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( - param0_fitV[idx_param_fitV['log_SNR2']], X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, - n_V, n_run, n_X0, idx_param_fitV, n_C, False, False) - vec = np.zeros(np.size(param0_fitV[idx_param_fitV['log_SNR2']])) - vec[idx_param_fitV['log_SNR2'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, - False, False)[0], - param0_fitV[idx_param_fitV['log_SNR2']], - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt log(SNR2) incorrect for model without GP') - - # We test the gradient of _fitV wrt to log(SNR^2) assuming GP prior. - ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( - param0_fitV, X0TAX0, XTAX0, X0TAY, X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_fitV, n_C, True, True, dist2, inten_diff2, 100, 100) - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['log_SNR2'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV srt log(SNR2) incorrect for model with GP') - - # We test the graident wrt spatial length scale parameter of GP prior - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['c_space']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt spatial length scale of GP incorrect') - - # We test the graident wrt intensity length scale parameter of GP prior - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['c_inten']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt intensity length scale of GP incorrect') - - # We test the graident on a random direction - vec = np.random.randn(np.size(param0_fitV)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV incorrect') - - -def test_nureg_determine(): - from brainiak.reprsimil.brsa import Ncomp_SVHT_MG_DLD_approx - import numpy as np - x = np.dot(np.random.randn(100, 5), np.random.randn(5, 40)) + \ - np.random.randn(100, 40) * 0.01 - ncomp = Ncomp_SVHT_MG_DLD_approx(x) - assert ncomp >= 3 and ncomp <= 8, ( - 'recovered number of components should be in a reasonable range') - - -def test_half_log_det(): - import numpy as np - from brainiak.reprsimil.brsa import BRSA - a = np.asarray([[1, 0.2], [0.2, 1]]) - brsa = BRSA() - half_log_det = np.log(np.linalg.det(a)) / 2 - assert np.isclose(half_log_det, brsa._half_log_det( - a)), 'half log determinant function is wrong' - - -def test_n_nureg(): - import brainiak.reprsimil.brsa - import numpy as np - # noise = np.random.randn(100,30) - noise = np.dot(np.random.randn(100, 8), np.random.randn( - 8, 30)) + np.random.randn(100, 30) * 0.01 - design = np.random.randn(100, 2) - s = brainiak.reprsimil.brsa.BRSA(n_iter=2) - s.fit(X=noise, design=design) - assert s.n_nureg_ > 2 and s.n_nureg_ < 16, ( - 'n_nureg_ estimation is wrong in BRSA') diff --git a/tests/reprsimil/test_gbrsa.py b/tests/reprsimil/test_gbrsa.py deleted file mode 100644 index 7038d35b4..000000000 --- a/tests/reprsimil/test_gbrsa.py +++ /dev/null @@ -1,630 +0,0 @@ -# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, -# Princeton University -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def test_can_instantiate(): - import brainiak.reprsimil.brsa - s = brainiak.reprsimil.brsa.GBRSA() - assert s, "Invalid GBRSA instance!" - - s = brainiak.reprsimil.brsa.GBRSA(n_iter=40, rank=4, auto_nuisance=False, - nureg_method='PCA', - baseline_single=False, logS_range=1.0, - SNR_bins=11, rho_bins=40, tol=2e-3, - optimizer='CG', random_state=0, - anneal_speed=20, SNR_prior='unif') - assert s, "Invalid GBRSA instance!" - - -def test_fit(): - from brainiak.reprsimil.brsa import GBRSA - import brainiak.utils.utils as utils - import scipy.stats - import numpy as np - import os.path - np.random.seed(10) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 1, 2, and 3 times, mimicking different length - # of experiments for different participants - n_run = [2, 1, 1] - design_mat = [None] * 3 - n_T = [None] * 3 - n_V = [40, 60, 60] - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - - # start simulating some data - n_C = np.size(design_mat[0], axis=1) - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = [None] * 3 - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = [None] * 3 - - # generating noise - noise = [None] * 3 - - # baseline - inten = [None] * 3 - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - n_T[i] = n_run[i] * design.n_TR - noise_level[i] = np.random.rand( - n_V[i]) * (noise_top - noise_bot) + noise_bot - # noise level is random. - rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot - - noise[i] = np.zeros([n_T[i], n_V[i]]) - noise[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ - np.random.randn(n_V[i]) * noise_level[i] - noise[i] = noise[i] + \ - np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) - inten[i] = np.random.rand(n_V[i]) * 20.0 - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_top = 5.0 # test with high SNR - snr_bot = 1.0 - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - - snr = [None] * 3 - signal = [None] * 3 - betas_simulated = [None] * 3 - scan_onsets = [None] * 3 - Y = [None] * 3 - for i in range(3): - snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot - sqrt_v = noise_level[i] * snr[i] - betas_simulated[i] = np.dot( - L_full, np.random.randn(n_C, n_V[i])) * sqrt_v - signal[i] = np.dot(design_mat[i], betas_simulated[i]) - - # Adding noise to signal as data - Y[i] = signal[i] + noise[i] + inten[i] - - scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) - - # Test fitting. - n_nureg = 2 - gbrsa = GBRSA(n_iter=15, auto_nuisance=True, logS_range=0.5, SNR_bins=11, - rho_bins=16, n_nureg=n_nureg, optimizer='L-BFGS-B') - - gbrsa.fit(X=Y, design=design_mat, scan_onsets=scan_onsets) - - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = gbrsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], - u_i[np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(gbrsa.nSNR_[0], snr[0])[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - p = scipy.stats.pearsonr(gbrsa.sigma_[1], noise_level[1])[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(gbrsa.rho_[2], rho1[2])[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - assert np.shape(gbrsa.X0_[1]) == (n_T[1], n_nureg + 1), "Wrong size of X0" - - Y_new = [None] * 3 - noise_new = [None] * 3 - for i in range(3): - noise_new[i] = np.zeros([n_T[i], n_V[i]]) - noise_new[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise_new[i][i_t, :] = noise_new[i][i_t - 1, :] * \ - rho1[i] + np.random.randn(n_V[i]) * noise_level[i] - - Y_new[i] = signal[i] + noise_new[i] + inten[i] - ts, ts0 = gbrsa.transform(Y_new, scan_onsets=scan_onsets) - [score, score_null] = gbrsa.score( - X=Y_new, design=design_mat, scan_onsets=scan_onsets) - [score_noise, score_null_noise] = gbrsa.score( - X=noise_new, design=design_mat, scan_onsets=scan_onsets) - for i in range(3): - assert np.shape(ts[i]) == (n_T[i], n_C) and np.shape( - ts0[i]) == (n_T[i], n_nureg + 1) - p = scipy.stats.pearsonr(ts[i][:, 0], design_mat[i][:, 0])[1] - assert p < 0.01, ( - "Recovered time series does not correlate with true time series!") - - assert score[i] > score_null[i], ( - "Full model does not win over null model on data containing " - "signal") - - assert score_noise[i] < score_null_noise[i], ( - "Null model does not win over full model on data without signal") - - [score, score_null] = gbrsa.score( - X=[None] * 3, design=design_mat, scan_onsets=scan_onsets) - assert score == [None] * 3 and score_null == [None] * \ - 3, "score did not return list of None when data is None" - ts, ts0 = gbrsa.transform(X=[None] * 3, scan_onsets=scan_onsets) - assert ts == [None] * 3 and ts0 == [None] * \ - 3, "transform did not return list of None when data is None" - - -def test_gradient(): - from brainiak.reprsimil.brsa import GBRSA - import brainiak.utils.utils as utils - import numpy as np - import os.path - import numdifftools as nd - - np.random.seed(100) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 1, 2, and 3 times, mimicking different length - # of experiments for different participants - n_run = [1, 2, 1] - design_mat = [None] * 3 - n_T = [None] * 3 - n_V = [30, 30, 20] - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - n_T[i] = n_run[i] * design.n_TR - - # start simulating some data - n_C = np.size(design_mat[0], axis=1) - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = [None] * 3 - for i in range(3): - noise_level[i] = np.random.rand( - n_V[i]) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = [None] * 3 - - # generating noise - noise = [None] * 3 - - # baseline - inten = [None] * 3 - for i in range(3): - rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot - noise[i] = np.zeros([n_T[i], n_V[i]]) - noise[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ - np.random.randn(n_V[i]) * noise_level[i] - noise[i] = noise[i] + \ - np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) - inten[i] = np.random.rand(n_V[i]) * 20.0 - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_top = 5.0 # test with high SNR - snr_bot = 1.0 - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - - snr = [None] * 3 - signal = [None] * 3 - betas_simulated = [None] * 3 - scan_onsets = [None] * 3 - Y = [None] * 3 - for i in range(3): - snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot - sqrt_v = noise_level[i] * snr[i] - betas_simulated[i] = np.dot( - L_full, np.random.randn(n_C, n_V[i])) * sqrt_v - signal[i] = np.dot(design_mat[i], betas_simulated[i]) - - # Adding noise to signal as data - Y[i] = signal[i] + noise[i] + inten[i] - - scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) - - # Get some initial fitting. - SNR_bins = 11 - rho_bins = 20 - gbrsa = GBRSA(n_iter=3, rank=n_C, SNR_bins=SNR_bins, - rho_bins=rho_bins, logS_range=0.5) - - n_grid = SNR_bins * rho_bins - half_log_det_X0TAX0 = [np.random.randn(n_grid) for i in range(3)] - log_weights = np.random.randn(n_grid) - log_fixed_terms = [np.random.randn(n_grid) for i in range(3)] - l_idx = np.tril_indices(n_C) - L_vec = np.random.randn(int(n_C * (n_C + 1) / 2)) - n_X0 = [2, 2, 2] - s = np.linspace(1, SNR_bins, n_grid) - a = np.linspace(0.5, 1, n_grid) - s2XTAcorrX = [None] * 3 - YTAcorrY_diag = [None] * 3 - sXTAcorrY = [None] * 3 - # The calculations below are quite arbitrary and do not conform - # to the model. They simply conform to the symmetry property and shape of - # the matrix indicated by the model - for i in range(3): - YTAcorrY_diag[i] = np.sum(Y[i] * Y[i], axis=0) * a[:, None] - s2XTAcorrX[i] = np.dot(design_mat[i].T, design_mat[ - i]) * s[:, None, None]**2 * a[:, None, None] - sXTAcorrY[i] = np.dot(design_mat[i].T, Y[i]) * \ - s[:, None, None] * a[:, None, None] - - # test if the gradients are correct - print(log_fixed_terms) - ll0, deriv0 = gbrsa._sum_loglike_marginalized(L_vec, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, - log_weights, log_fixed_terms, - l_idx, n_C, n_T, n_V, n_X0, - n_grid, rank=None) - # We test the gradient to the Cholesky factor - vec = np.random.randn(np.size(L_vec)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: gbrsa._sum_loglike_marginalized(x, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, - half_log_det_X0TAX0, - log_weights, log_fixed_terms, - l_idx, n_C, n_T, n_V, n_X0, - n_grid, rank=None)[0], - L_vec, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), 'gradient incorrect' - - -def test_SNR_grids(): - import brainiak.reprsimil.brsa - import numpy as np - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='unif', SNR_bins=10) - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.isclose(np.sum(SNR_weights), 1) - and np.isclose(np.std(SNR_weights[1:-1]), 0) - and np.all(SNR_weights > 0) - and np.isclose(np.min(SNR_grids), 0) - and np.all(SNR_grids >= 0) - and np.isclose(np.max(SNR_grids), 1) - ), 'SNR_weights or SNR_grids are incorrect for uniform prior' - assert np.isclose(np.ptp(np.diff(SNR_grids[1:-1])), 0), \ - 'SNR grids are not equally spaced for uniform prior' - assert (np.size(SNR_grids) == np.size(SNR_weights) - and np.size(SNR_grids) == 10 - ), ("size of SNR_grids or SNR_weights is not correct for uniform " - "prior") - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='lognorm', SNR_bins=35) - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids >= 0) - and np.isclose(np.sum(SNR_weights), 1) - and np.all(SNR_weights > 0) - and np.all(np.diff(SNR_grids) > 0) - ), 'SNR_grids or SNR_weights not correct for log normal prior' - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='exp') - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids >= 0) - and np.isclose(np.sum(SNR_weights), 1) - and np.all(SNR_weights > 0) - and np.all(np.diff(SNR_grids) > 0) - ), 'SNR_grids or SNR_weights not correct for exponential prior' - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='equal') - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids == 1) - and np.all(SNR_weights == 1) - and np.size(SNR_grids) == 1 - ), 'SNR_grids or SNR_weights not correct for equal prior' - - -def test_n_nureg(): - import brainiak.reprsimil.brsa - import numpy as np - noise = np.dot(np.random.randn(100, 8), np.random.randn( - 8, 30)) + np.random.randn(100, 30) * 0.001 - design = np.random.randn(100, 2) - s = brainiak.reprsimil.brsa.GBRSA(n_iter=2) - s.fit(X=noise, design=design) - assert s.n_nureg_[0] == 8, 'n_nureg_ estimation is wrong in GBRSA' - - -def test_grid_flatten_num_int(): - # Check for numeric integration of SNR, and correctly flattening 2-D grids - # to 1-D grid. - import brainiak.reprsimil.brsa - import brainiak.utils.utils as utils - import numpy as np - import scipy.special - n_V = 30 - n_T = 50 - n_C = 3 - design = np.random.randn(n_T, n_C) - U_simu = np.asarray([[1.0, 0.1, 0.0], [0.1, 1.0, 0.2], [0.0, 0.2, 1.0]]) - L_simu = np.linalg.cholesky(U_simu) - SNR = np.random.exponential(size=n_V) - beta = np.dot(L_simu, np.random.randn(n_C, n_V)) * SNR - noise = np.random.randn(n_T, n_V) - Y = np.dot(design, beta) + noise - X = design - X_base = None - scan_onsets = [0] - - s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, - SNR_prior='exp') - s.fit(X=[Y], design=[design]) - rank = n_C - l_idx, rank = s._chol_idx(n_C, rank) - L = np.zeros((n_C, rank)) - n_l = np.size(l_idx[0]) - current_vec_U_chlsk_l = s.random_state_.randn(n_l) * 10 - L[l_idx] = current_vec_U_chlsk_l - - # Now we change the grids for SNR and rho for testing. - s.SNR_bins = 2 - s.rho_bins = 2 - SNR_grids, SNR_weights = s._set_SNR_grids() - # rho_grids, rho_weights = s._set_rho_grids() - rho_grids = np.ones(2) * 0.1 - rho_weights = np.ones(2) / 2 - # We purposefully set all rhos to be equal to test flattening of - # grids. - n_grid = s.SNR_bins * s.rho_bins - - D, F, run_TRs, n_run = s._prepare_DF( - n_T, scan_onsets=scan_onsets) - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX \ - = s._prepare_data_XY(X, Y, D, F) - X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, \ - X_base, n_X0, idx_DC = s._prepare_data_XYX0( - X, Y, X_base, None, D, F, run_TRs, no_DC=False) - - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] - and half_log_det_X0TAX0[2] == half_log_det_X0TAX0[3] - and half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] - ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' - assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) - and np.array_equal(X0TAX0[2, :, :], X0TAX0[3, :, :]) - and np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0' - assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) - and np.array_equal(X0TAX0_i[2, :, :], X0TAX0_i[3, :, :]) - and np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0_i' - assert np.allclose( - np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), - np.eye(n_X0) - ), 'X0TAX0_i is not inverse of X0TAX0' - assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[1, :]) - and np.array_equal(YTAcorrY_diag[2, :], YTAcorrY_diag[3, :]) - and np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) - ), '_matrix_flattened_grid has mistake YTAcorrY_diag' - assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) - and np.array_equal(sXTAcorrY[2, :, :], sXTAcorrY[3, :, :]) - and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) - ), '_matrix_flattened_grid has mistake sXTAcorrY' - assert (np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) - and np.array_equal(X0TAY[2, :, :], X0TAY[3, :, :]) - and np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAY' - assert (np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) - and np.array_equal(XTAX0[2, :, :], XTAX0[3, :, :]) - and np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) - ), '_matrix_flattened_grid has mistake XTAX0' - - # Now we test the other way - rho_grids, rho_weights = s._set_rho_grids() - # rho_grids, rho_weights = s._set_rho_grids() - SNR_grids = np.ones(2) * 0.1 - SNR_weights = np.ones(2) / 2 - # We purposefully set all SNR to be equal to test flattening of - # grids. - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] - and half_log_det_X0TAX0[1] == half_log_det_X0TAX0[3] - and not half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] - ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' - assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) - and np.array_equal(X0TAX0[1, :, :], X0TAX0[3, :, :]) - and not np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0' - assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) - and np.array_equal(X0TAX0_i[1, :, :], X0TAX0_i[3, :, :]) - and not np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0_i' - assert np.allclose( - np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), - np.eye(n_X0) - ), 'X0TAX0_i is not inverse of X0TAX0' - assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) - and np.array_equal(YTAcorrY_diag[1, :], YTAcorrY_diag[3, :]) - and not np.array_equal(YTAcorrY_diag[0, :], - YTAcorrY_diag[1, :]) - ), '_matrix_flattened_grid has mistake YTAcorrY_diag' - assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) - and np.array_equal(sXTAcorrY[1, :, :], sXTAcorrY[3, :, :]) - and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) - ), '_matrix_flattened_grid has mistake sXTAcorrY' - assert (np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) - and np.array_equal(X0TAY[1, :, :], X0TAY[3, :, :]) - and not np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAY' - assert (np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) - and np.array_equal(XTAX0[1, :, :], XTAX0[3, :, :]) - and not np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) - ), '_matrix_flattened_grid has mistake XTAX0' - - # Now test the integration over SNR - s.SNR_bins = 50 - s.rho_bins = 1 - SNR_grids, SNR_weights = s._set_SNR_grids() - rho_grids, rho_weights = s._set_rho_grids() - n_grid = s.SNR_bins * s.rho_bins - - def setup_for_test(): - # This function will be re-used to set up the variables necessary for - # testing. - - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - log_weights = np.reshape( - np.log(SNR_weights[:, None]) + np.log(rho_weights), n_grid) - all_rho_grids = np.reshape(np.repeat( - rho_grids[None, :], s.SNR_bins, axis=0), n_grid) - log_fixed_terms = - (n_T - n_X0) / 2 * np.log(2 * np.pi) + n_run \ - / 2 * np.log(1 - all_rho_grids**2) + scipy.special.gammaln( - (n_T - n_X0 - 2) / 2) + (n_T - n_X0 - 2) / 2 * np.log(2) - return s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, \ - log_weights, log_fixed_terms - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, log_weights, - log_fixed_terms, l_idx, n_C, n_T, - n_V, n_X0, n_grid, rank=rank) - LL_total = - LL_total - # Now we re-calculate using scipy.integrate - s.SNR_bins = 100 - SNR_grids = np.linspace(0, 12, s.SNR_bins) - SNR_weights = np.exp(- SNR_grids) - SNR_weights = SNR_weights / np.sum(SNR_weights) - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, half_log_det_X0TAX0, - log_weights, log_fixed_terms, - n_C, n_T, n_V, n_X0, - n_grid, rank) - result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) - scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) - LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) - - tol = 1e-3 - assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ - 'Error of log likelihood calculation exceeds the tolerance' - - # Now test the log normal prior - s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, - SNR_prior='lognorm') - s.SNR_bins = 50 - s.rho_bins = 1 - SNR_grids, SNR_weights = s._set_SNR_grids() - rho_grids, rho_weights = s._set_rho_grids() - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, log_weights, - log_fixed_terms, l_idx, n_C, n_T, - n_V, n_X0, n_grid, rank=rank) - LL_total = - LL_total - # Now we re-calculate using scipy.integrate - s.SNR_bins = 400 - SNR_grids = np.linspace(1e-8, 20, s.SNR_bins) - log_SNR_weights = scipy.stats.lognorm.logpdf(SNR_grids, s=s.logS_range) - result_sum, max_value, result_exp = utils.sumexp_stable( - log_SNR_weights[:, None]) - SNR_weights = np.squeeze(result_exp / result_sum) - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, half_log_det_X0TAX0, - log_weights, log_fixed_terms, - n_C, n_T, n_V, n_X0, - n_grid, rank) - result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) - scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) - LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) - - tol = 1e-3 - assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ - 'Error of log likelihood calculation exceeds the tolerance' diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py deleted file mode 100644 index 7d472e6f5..000000000 --- a/tests/searchlight/test_searchlight.py +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections import namedtuple - -import numpy as np -from mpi4py import MPI - -from brainiak.searchlight.searchlight import Searchlight -from brainiak.searchlight.searchlight import Diamond, Ball - -"""Distributed Searchlight Test -""" - - -def cube_sfn(l, msk, myrad, bcast_var): - if np.all(msk) and np.any(msk): - return 1.0 - return None - - -def test_searchlight_with_cube(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = True - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(cube_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def test_searchlight_with_cube_poolsize_1(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = True - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(cube_sfn, pool_size=1) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def diamond_sfn(l, msk, myrad, bcast_var): - assert not np.any(msk[~Diamond(3).mask_]) - if np.all(msk[Diamond(3).mask_]): - return 1.0 - return None - - -def test_searchlight_with_diamond(): - sl = Searchlight(sl_rad=3, shape=Diamond) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = Diamond(3).mask_ - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(diamond_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def ball_sfn(l, msk, myrad, bcast_var): - x, y, z = np.mgrid[-myrad:myrad+1, -myrad:myrad+1, -myrad:myrad+1] - correct_mask = np.square(x) + np.square(y) + np.square(z) <= myrad ** 2 - assert not np.any(msk[~Ball(3).mask_]) - if np.all(correct_mask == msk): - return 1.0 - return None - - -def test_searchlight_with_ball(): - sl = Searchlight(sl_rad=3, shape=Ball) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = Ball(3).mask_ - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(ball_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -MaskRadBcast = namedtuple("MaskRadBcast", "mask rad") - - -def test_instantiate(): - sl = Searchlight(sl_rad=5, max_blk_edge=10) - assert sl - - -def voxel_test_sfn(l, msk, myrad, bcast): - rad = bcast.rad - # Check each point - for subj in l: - for _tr in range(subj.shape[3]): - tr = subj[:, :, :, _tr] - midpt = tr[rad, rad, rad] - for d0 in range(tr.shape[0]): - for d1 in range(tr.shape[1]): - for d2 in range(tr.shape[2]): - assert np.array_equal(tr[d0, d1, d2] - midpt, - np.array([d0-rad, d1-rad, - d2-rad, 0])) - - # Determine midpoint - midpt = l[0][rad, rad, rad, 0] - midpt = (midpt[0], midpt[1], midpt[2]) - - for d0 in range(msk.shape[0]): - for d1 in range(msk.shape[1]): - for d2 in range(msk.shape[2]): - pt = (midpt[0] - rad + d0, midpt[1] - rad + d1, - midpt[2] - rad + d2) - assert bcast.mask[pt] == msk[d0, d1, d2] - - # Return midpoint - return midpt - - -def block_test_sfn(l, msk, myrad, bcast_var, extra_params): - outmat = l[0][:, :, :, 0] - outmat[~msk] = None - return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] - - -def test_correctness(): # noqa: C901 - def voxel_test(data, mask, max_blk_edge, rad): - - comm = MPI.COMM_WORLD - rank = comm.rank - - (dim0, dim1, dim2) = mask.shape - - # Initialize dataset with known pattern - for subj in data: - if subj is not None: - for tr in range(subj.shape[3]): - for d1 in range(dim0): - for d2 in range(dim1): - for d3 in range(dim2): - subj[d1, d2, d3, tr] = np.array( - [d1, d2, d3, tr]) - - sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) - sl.distribute(data, mask) - sl.broadcast(MaskRadBcast(mask, rad)) - global_outputs = sl.run_searchlight(voxel_test_sfn) - - if rank == 0: - for d0 in range(rad, global_outputs.shape[0]-rad): - for d1 in range(rad, global_outputs.shape[1]-rad): - for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: - assert np.array_equal( - np.array(global_outputs[d0, d1, d2]), - np.array([d0, d1, d2])) - - def block_test(data, mask, max_blk_edge, rad): - - comm = MPI.COMM_WORLD - rank = comm.rank - - (dim0, dim1, dim2) = mask.shape - - # Initialize dataset with known pattern - for subj in data: - if subj is not None: - for tr in range(subj.shape[3]): - for d1 in range(dim0): - for d2 in range(dim1): - for d3 in range(dim2): - subj[d1, d2, d3, tr] = np.array( - [d1, d2, d3, tr]) - - sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) - sl.distribute(data, mask) - sl.broadcast(mask) - global_outputs = sl.run_block_function(block_test_sfn) - - if rank == 0: - for d0 in range(rad, global_outputs.shape[0]-rad): - for d1 in range(rad, global_outputs.shape[1]-rad): - for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: - assert np.array_equal( - np.array(global_outputs[d0, d1, d2]), - np.array([d0, d1, d2, 0])) - - # Create dataset - def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - mask = np.random.choice([True, False], (dim0, dim1, dim2)) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - voxel_test(data, mask, max_blk_edge, rad) - block_test(data, mask, max_blk_edge, rad) - - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=1) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=1, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=0, dim1=10, dim2=8, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) diff --git a/tests/utils/example_design.1D b/tests/utils/example_design.1D deleted file mode 100755 index 7385edea6..000000000 --- a/tests/utils/example_design.1D +++ /dev/null @@ -1,272 +0,0 @@ -# - 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 - 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 - 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 - 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 - 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 - 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 - 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 - 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 - 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 - 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 - 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 - 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 - 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 - 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 - 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 - 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 - 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 - 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 - 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 - 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 - 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 - 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 - 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 - 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 - 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 - 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 - 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 - 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 - 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 - 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 - 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 - 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 - 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 - 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 - 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 - 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 - 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 - 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 - 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 - 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 - 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 - 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 - 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 - 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 - 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 - 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 - 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 - 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 - 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 - 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 - 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 - 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 - 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 - 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 - 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 - 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 - 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 - 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 - 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 - 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 - 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 - 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 - 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 - 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 - 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 - 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 - 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 - 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 - 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 - 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 - 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 - 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 - 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 - 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 - 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 - 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 - 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 - 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 - 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 - 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 - 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 - 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 - 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 - 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 - 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 - 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 - 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 - 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 - 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 - 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 - 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 - 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 - 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 - 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 - 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 - 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 - 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 - 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 - 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 - 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 - 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 - 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 - 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 - 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 - 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 - 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 - 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 - 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 - 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 - 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 - 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 - 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 - 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 - 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 - 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 - 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 - 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 - 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 - 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 - 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 - 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 - 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 - 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 - 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 - 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 - 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 - 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 - 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 - 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 - 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 - 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 - 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 - 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 - 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 - 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 - 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 - 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 - 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 - 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 - 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 - 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 - 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 - 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 - 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 - 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 - 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 - 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 - 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 - 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 - 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 - 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 - 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 - 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 - 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 - 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 - 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 - 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 - 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 - 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 - 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 - 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 - 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 - 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 - 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 - 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 - 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 - 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 - 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 - 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 - 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 - 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 - 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 - 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 - 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 - 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 - 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 - 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 - 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 - 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 - 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 - 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 - 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 - 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 - 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 - 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 - 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 -# - diff --git a/tests/utils/example_stimtime_1_AFNI.txt b/tests/utils/example_stimtime_1_AFNI.txt deleted file mode 100644 index 48200041d..000000000 --- a/tests/utils/example_stimtime_1_AFNI.txt +++ /dev/null @@ -1,2 +0,0 @@ -5.2*2.0:2.0 40.0*4.0:1.5 -2.0*2.0:1.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_1_FSL.txt b/tests/utils/example_stimtime_1_FSL.txt deleted file mode 100644 index ce14b1d55..000000000 --- a/tests/utils/example_stimtime_1_FSL.txt +++ /dev/null @@ -1,3 +0,0 @@ -5.2 2.0 2.0 -40.0 1.5 4.0 -50.0 1.0 2.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_2_AFNI.txt b/tests/utils/example_stimtime_2_AFNI.txt deleted file mode 100644 index 39d03d9ee..000000000 --- a/tests/utils/example_stimtime_2_AFNI.txt +++ /dev/null @@ -1 +0,0 @@ --10.0 diff --git a/tests/utils/example_stimtime_2_FSL.txt b/tests/utils/example_stimtime_2_FSL.txt deleted file mode 100644 index e60beb2a6..000000000 --- a/tests/utils/example_stimtime_2_FSL.txt +++ /dev/null @@ -1,3 +0,0 @@ -5.2 2.0 -40.0 1.5 2.0 -50.0 \ No newline at end of file diff --git a/tests/utils/test_fmrisim.py b/tests/utils/test_fmrisim.py deleted file mode 100644 index 7c442661a..000000000 --- a/tests/utils/test_fmrisim.py +++ /dev/null @@ -1,858 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""fMRI Simulator test script - -Test script for generating a run of a participant's data. - - Authors: Cameron Ellis (Princeton) 2016 -""" -import numpy as np -import math -from brainiak.utils import fmrisim as sim -import pytest -from itertools import product - - -def test_generate_signal(): - - # Inputs for generate_signal - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [3] - feature_type = ['cube'] - feature_coordinates = np.array([[5, 5, 5]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - assert np.all(volume.shape == dimensions), "Check signal shape" - assert np.max(volume) == signal_magnitude, "Check signal magnitude" - assert np.sum(volume > 0) == math.pow(feature_size[0], 3), ( - "Check feature size") - assert volume[5, 5, 5] == signal_magnitude, "Check signal location" - assert volume[5, 5, 1] == 0, "Check noise location" - - feature_coordinates = np.array( - [[5, 5, 5], [3, 3, 3], [7, 7, 7]]) - - # Check feature size is correct - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=['loop', 'cavity', 'sphere'], - feature_size=[3], - signal_magnitude=signal_magnitude) - assert volume[5, 5, 5] == 0, "Loop is empty" - assert volume[3, 3, 3] == 0, "Cavity is empty" - assert volume[7, 7, 7] != 0, "Sphere is not empty" - - # Check feature size manipulation - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=['loop', 'cavity', 'sphere'], - feature_size=[1], - signal_magnitude=signal_magnitude) - assert volume[5, 6, 6] == 0, "Loop is too big" - assert volume[3, 5, 5] == 0, "Cavity is too big" - assert volume[7, 9, 9] == 0, "Sphere is too big" - - # Check that out of bounds feature coordinates are corrected - feature_coordinates = np.array([0, 2, dimensions[2]]) - x, y, z = sim._insert_idxs(feature_coordinates, feature_size[0], - dimensions) - assert x[1] - x[0] == 2, "x min not corrected" - assert y[1] - y[0] == 3, "y was corrected when it shouldn't be" - assert z[1] - z[0] == 1, "z max not corrected" - - # Check that signal patterns are created - feature_coordinates = np.array([[5, 5, 5]]) - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - signal_constant=0, - ) - assert volume[4:7, 4:7, 4:7].std() > 0, "Signal is constant" - - -def test_generate_stimfunction(): - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 100 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - assert stimfunction.shape[0] == duration * 100, "stimfunc incorrect length" - eventNumber = np.sum(event_durations * len(onsets)) * 100 - assert np.sum(stimfunction) == eventNumber, "Event number" - - # Create the signal function - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - stim_dur = stimfunction.shape[0] / (tr_duration * 100) - assert signal_function.shape[0] == stim_dur, "The length did not change" - - # Test - onsets = [0] - tr_duration = 1 - event_durations = [1] - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - max_response = np.where(signal_function != 0)[0].max() - assert 25 < max_response <= 30, "HRF has the incorrect length" - assert np.sum(signal_function < 0) > 0, "No values below zero" - - # Export a stimfunction - sim.export_3_column(stimfunction, - 'temp.txt', - ) - - # Load in the stimfunction - stimfunc_new = sim.generate_stimfunction(onsets=None, - event_durations=None, - total_time=duration, - timing_file='temp.txt', - ) - - assert np.all(stimfunc_new == stimfunction), "Export/import failed" - - # Break the timing precision of the generation - stimfunc_new = sim.generate_stimfunction(onsets=None, - event_durations=None, - total_time=duration, - timing_file='temp.txt', - temporal_resolution=0.5, - ) - - assert stimfunc_new.sum() == 0, "Temporal resolution not working right" - - # Set the duration to be too short so you should get an error - onsets = [10, 30, 50, 70, 90] - event_durations = [5] - with pytest.raises(ValueError): - sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=89, - ) - - # Clip the event offset - stimfunc_new = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=95, - ) - assert stimfunc_new[-1] == 1, 'Event offset was not clipped' - - # Test exporting a group of participants to an epoch file - cond_a = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=110, - ) - - cond_b = sim.generate_stimfunction(onsets=[x + 5 for x in onsets], - event_durations=event_durations, - total_time=110, - ) - - stimfunction_group = [np.hstack((cond_a, cond_b))] * 2 - sim.export_epoch_file(stimfunction_group, - 'temp.txt', - tr_duration, - ) - - # Check that convolve throws a warning when the shape is wrong - sim.convolve_hrf(stimfunction=np.hstack((cond_a, cond_b)).T, - tr_duration=tr_duration, - temporal_resolution=1, - ) - - -def test_apply_signal(): - - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[5, 5, 5]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 100 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - # Check that you can compute signal change appropriately - # Preset a bunch of things - stimfunction_tr = stimfunction[::int(tr_duration * 100)] - mask, template = sim.mask_brain(dimensions, mask_self=False) - noise_dict = sim._noise_dict_update({}) - noise = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=noise_dict, - iterations=[0, 0] - ) - coords = feature_coordinates[0] - noise_function_a = noise[coords[0], coords[1], coords[2], :] - noise_function_a = noise_function_a.reshape(duration // tr_duration, 1) - - noise_function_b = noise[coords[0] + 1, coords[1], coords[2], :] - noise_function_b = noise_function_b.reshape(duration // tr_duration, 1) - - # Create the calibrated signal with PSC - method = 'PSC' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - sig_b = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [1.0], - method, - ) - - assert sig_b.max() / sig_a.max() == 2, 'PSC modulation failed' - - # Create the calibrated signal with SFNR - method = 'SFNR' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a / (noise_function_a.mean() / noise_dict['sfnr']) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b / (noise_function_b.mean() / noise_dict['sfnr']) - - assert scaled_b.max() / scaled_a.max() == 2, 'SFNR modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-SD - method = 'CNR_Amp/Noise-SD' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a / noise_function_a.std() - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b / noise_function_b.std() - - assert scaled_b.max() / scaled_a.max() == 2, 'CNR_Amp modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-Var_dB - method = 'CNR_Amp2/Noise-Var_dB' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = np.log(sig_a.max() / noise_function_a.std()) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = np.log(sig_b.max() / noise_function_b.std()) - - assert np.round(scaled_b / scaled_a) == 2, 'CNR_Amp dB modulation failed' - - # Create the calibrated signal with CNR_Signal-SD/Noise-SD - method = 'CNR_Signal-SD/Noise-SD' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a.std() / noise_function_a.std() - sig_b = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b.std() / noise_function_a.std() - - assert (scaled_b / scaled_a) == 2, 'CNR signal modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-Var_dB - method = 'CNR_Signal-Var/Noise-Var_dB' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - - scaled_a = np.log(sig_a.std() / noise_function_a.std()) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = np.log(sig_b.std() / noise_function_b.std()) - - assert np.round(scaled_b / scaled_a) == 2, 'CNR signal dB modulation ' \ - 'failed' - - # Convolve the HRF with the stimulus sequence - signal = sim.apply_signal(signal_function=signal_function, - volume_signal=volume, - ) - - assert signal.shape == (dimensions[0], dimensions[1], dimensions[2], - duration / tr_duration), "The output is the " \ - "wrong size" - - signal = sim.apply_signal(signal_function=stimfunction, - volume_signal=volume, - ) - - assert np.any(signal == signal_magnitude), "The stimfunction is not binary" - - # Check that there is an error if the number of signal voxels doesn't - # match the number of non zero brain voxels - with pytest.raises(IndexError): - sig_vox = (volume > 0).sum() - vox_pattern = np.tile(stimfunction, (1, sig_vox - 1)) - sim.apply_signal(signal_function=vox_pattern, - volume_signal=volume, - ) - - -def test_generate_noise(): - - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[5, 5, 5]]) - signal_magnitude = [1] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 200 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - # Convolve the HRF with the stimulus sequence - signal = sim.apply_signal(signal_function=signal_function, - volume_signal=volume, - ) - - # Generate the mask of the signal - mask, template = sim.mask_brain(signal, - mask_self=None) - - assert min(mask[mask > 0]) > 0.1, "Mask thresholding did not work" - assert len(np.unique(template) > 2), "Template creation did not work" - - stimfunction_tr = stimfunction[::int(tr_duration * 100)] - - # Create the noise volumes (using the default parameters) - noise = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - iterations=[1, 0], - ) - - assert signal.shape == noise.shape, "The dimensions of signal and noise " \ - "the same" - - noise_high = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={'sfnr': 50, 'snr': 25}, - iterations=[1, 0], - ) - - noise_low = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={'sfnr': 100, 'snr': 25}, - iterations=[1, 0], - ) - - system_high = np.std(noise_high[mask > 0], 1).mean() - system_low = np.std(noise_low[mask > 0], 1).mean() - - assert system_low < system_high, "SFNR noise could not be manipulated" - - # Check that you check for the appropriate template values - with pytest.raises(ValueError): - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template * 2, - mask=mask, - noise_dict={}, - ) - - # Check that iterations does what it should - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={}, - iterations=[0, 0], - ) - - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={}, - iterations=None, - ) - - # Test drift noise - trs = 1000 - period = 100 - drift = sim._generate_noise_temporal_drift(trs, - tr_duration, - 'sine', - period, - ) - - # Check that the max frequency is the appropriate frequency - power = abs(np.fft.fft(drift))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs - period_freq = np.where(freq == 1 / (period // tr_duration)) - max_freq = np.argmax(power) - - assert period_freq == max_freq, 'Max frequency is not where it should be' - - # Do the same but now with cosine basis functions, answer should be close - drift = sim._generate_noise_temporal_drift(trs, - tr_duration, - 'discrete_cos', - period, - ) - - # Check that the appropriate frequency is peaky (may not be the max) - power = abs(np.fft.fft(drift))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs - period_freq = np.where(freq == 1 / (period // tr_duration))[0][0] - - assert power[period_freq] > power[period_freq + 1], 'Power is low' - assert power[period_freq] > power[period_freq - 1], 'Power is low' - - # Check it gives a warning if the duration is too short - drift = sim._generate_noise_temporal_drift(50, - tr_duration, - 'discrete_cos', - period, - ) - - # Test physiological noise (using unrealistic parameters so that it's easy) - timepoints = list(np.linspace(0, (trs - 1) * tr_duration, trs)) - resp_freq = 0.2 - heart_freq = 1.17 - phys = sim._generate_noise_temporal_phys(timepoints, - resp_freq, - heart_freq, - ) - - # Check that the max frequency is the appropriate frequency - power = abs(np.fft.fft(phys))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / (trs * tr_duration) - peaks = (power > (power.mean() + power.std())) # Where are the peaks - peak_freqs = freq[peaks] - - assert np.any(resp_freq == peak_freqs), 'Resp frequency not found' - assert len(peak_freqs) == 2, 'Two peaks not found' - - # Test task noise - sim._generate_noise_temporal_task(stimfunction_tr, - motion_noise='gaussian', - ) - sim._generate_noise_temporal_task(stimfunction_tr, - motion_noise='rician', - ) - - # Test ARMA noise - with pytest.raises(ValueError): - noise_dict = {'fwhm': 4, 'auto_reg_rho': [1], 'ma_rho': [1, 1]} - sim._generate_noise_temporal_autoregression(stimfunction_tr, - noise_dict, - dimensions, - mask, - ) - - # Generate spatial noise - vol = sim._generate_noise_spatial(np.array([10, 10, 10, trs])) - assert len(vol.shape) == 3, 'Volume was not reshaped to ignore TRs' - - # Switch some of the noise types on - noise_dict = dict(physiological_sigma=1, drift_sigma=1, task_sigma=1, - auto_reg_sigma=0) - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=noise_dict, - iterations=[0, 0], - ) - - -def test_generate_noise_spatial(): - - # Set up the inputs - dimensions = np.array([10, 5, 10]) - mask = np.ones(dimensions) - vol = sim._generate_noise_spatial(dimensions, mask) - - # Run the analysis from _calc_FHWM but for th elast step of aggregating - # across dimensions - v_count = 0 - v_sum = 0 - v_sq = 0 - - d_sum = [0.0, 0.0, 0.0] - d_sq = [0.0, 0.0, 0.0] - d_count = [0, 0, 0] - - # Pull out all the voxel coordinates - coordinates = list(product(range(dimensions[0]), - range(dimensions[1]), - range(dimensions[2]))) - - # Find the sum of squared error for the non-masked voxels in the brain - for i in list(range(len(coordinates))): - - # Pull out this coordinate - x, y, z = coordinates[i] - - # Is this within the mask? - if mask[x, y, z] > 0: - - # Find the the volume sum and squared values - v_count += 1 - v_sum += vol[x, y, z] - v_sq += vol[x, y, z] ** 2 - - # Get the volume variance - v_var = (v_sq - ((v_sum ** 2) / v_count)) / (v_count - 1) - - for i in list(range(len(coordinates))): - - # Pull out this coordinate - x, y, z = coordinates[i] - - # Is this within the mask? - if mask[x, y, z] > 0: - # For each xyz dimension calculate the squared - # difference of this voxel and the next - - in_range = (x < dimensions[0] - 1) - in_mask = in_range and (mask[x + 1, y, z] > 0) - included = in_mask and (~np.isnan(vol[x + 1, y, z])) - if included: - d_sum[0] += vol[x, y, z] - vol[x + 1, y, z] - d_sq[0] += (vol[x, y, z] - vol[x + 1, y, z]) ** 2 - d_count[0] += 1 - - in_range = (y < dimensions[1] - 1) - in_mask = in_range and (mask[x, y + 1, z] > 0) - included = in_mask and (~np.isnan(vol[x, y + 1, z])) - if included: - d_sum[1] += vol[x, y, z] - vol[x, y + 1, z] - d_sq[1] += (vol[x, y, z] - vol[x, y + 1, z]) ** 2 - d_count[1] += 1 - - in_range = (z < dimensions[2] - 1) - in_mask = in_range and (mask[x, y, z + 1] > 0) - included = in_mask and (~np.isnan(vol[x, y, z + 1])) - if included: - d_sum[2] += vol[x, y, z] - vol[x, y, z + 1] - d_sq[2] += (vol[x, y, z] - vol[x, y, z + 1]) ** 2 - d_count[2] += 1 - - # Find the variance - d_var = np.divide((d_sq - np.divide(np.power(d_sum, 2), - d_count)), (np.add(d_count, -1))) - - o_var = np.divide(-1, (4 * np.log(1 - (0.5 * d_var / v_var)))) - fwhm3 = np.sqrt(o_var) * 2 * np.sqrt(2 * np.log(2)) - - # Calculate the proportion of std relative to the mean - std_proportion = np.nanstd(fwhm3) / np.nanmean(fwhm3) - print(fwhm3) - assert std_proportion < 0.25, 'Variance is inconsistent across dim' - - -def test_mask_brain(): - - # Inputs for generate_signal - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[4, 4, 4]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Mask the volume to be the same shape as a brain - mask, _ = sim.mask_brain(dimensions, mask_self=None,) - brain = volume * mask - - assert np.sum(brain != 0) == np.sum(volume != 0), "Masking did not work" - assert brain[0, 0, 0] == 0, "Masking did not work" - assert brain[4, 4, 4] != 0, "Masking did not work" - - feature_coordinates = np.array( - [[1, 1, 1]]) - - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Mask the volume to be the same shape as a brain - mask, _ = sim.mask_brain(dimensions, mask_self=None, ) - brain = volume * mask - - assert np.sum(brain != 0) < np.sum(volume != 0), "Masking did not work" - - # Test that you can load the default - dimensions = np.array([100, 100, 100]) - mask, template = sim.mask_brain(dimensions, mask_self=False) - - assert mask[20, 80, 50] == 0, 'Masking didn''t work' - assert mask[25, 80, 50] == 1, 'Masking didn''t work' - assert int(template[25, 80, 50] * 100) == 57, 'Template not correct' - - # Check that you can mask self - mask_self, template_self = sim.mask_brain(template, mask_self=True) - - assert (template_self - template).sum() < 1e2, 'Mask self error' - assert (mask_self - mask).sum() == 0, 'Mask self error' - - -def test_calc_noise(): - - # Inputs for functions - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 200 - temporal_res = 100 - tr_number = int(np.floor(duration / tr_duration)) - dimensions_tr = np.array([10, 10, 10, tr_number]) - - # Preset the noise dict - nd_orig = sim._noise_dict_update({}) - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - temporal_resolution=temporal_res, - ) - - # Mask the volume to be the same shape as a brain - mask, template = sim.mask_brain(dimensions_tr, mask_self=None) - stimfunction_tr = stimfunction[::int(tr_duration * temporal_res)] - - nd_orig['matched'] = 0 - noise = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - ) - - # Check the spatial noise match - nd_orig['matched'] = 1 - noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - iterations=[50, 0] - ) - - # Calculate the noise parameters from this newly generated volume - nd_new = sim.calc_noise(noise, mask, template) - nd_matched = sim.calc_noise(noise_matched, mask, template) - - # Check the values are reasonable" - assert nd_new['snr'] > 0, 'snr out of range' - assert nd_new['sfnr'] > 0, 'sfnr out of range' - assert nd_new['auto_reg_rho'][0] > 0, 'ar out of range' - - # Check that the dilation increases SNR - no_dilation_snr = sim._calc_snr(noise_matched, - mask, - dilation=0, - reference_tr=tr_duration, - ) - - assert nd_new['snr'] > no_dilation_snr, "Dilation did not increase SNR" - - # Check that template size is in bounds - with pytest.raises(ValueError): - sim.calc_noise(noise, mask, template * 2) - - # Check that Mask is set is checked - with pytest.raises(ValueError): - sim.calc_noise(noise, None, template) - - # Check that it can deal with missing noise parameters - temp_nd = sim.calc_noise(noise, mask, template, noise_dict={}) - assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' - - temp_nd = sim.calc_noise(noise, mask, template, noise_dict=None) - assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' - - # Check that the fitting worked - snr_diff = abs(nd_orig['snr'] - nd_new['snr']) - snr_diff_match = abs(nd_orig['snr'] - nd_matched['snr']) - assert snr_diff > snr_diff_match, 'snr fit incorrectly' - - # Test that you can generate rician and exponential noise - sim._generate_noise_system(dimensions_tr, - 1, - 1, - spatial_noise_type='exponential', - temporal_noise_type='rician', - ) - - # Check the temporal noise match - nd_orig['matched'] = 1 - noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - iterations=[0, 50] - ) - - nd_matched = sim.calc_noise(noise_matched, mask, template) - - sfnr_diff = abs(nd_orig['sfnr'] - nd_new['sfnr']) - sfnr_diff_match = abs(nd_orig['sfnr'] - nd_matched['sfnr']) - assert sfnr_diff > sfnr_diff_match, 'sfnr fit incorrectly' - - ar1_diff = abs(nd_orig['auto_reg_rho'][0] - nd_new['auto_reg_rho'][0]) - ar1_diff_match = abs(nd_orig['auto_reg_rho'][0] - nd_matched[ - 'auto_reg_rho'][0]) - assert ar1_diff > ar1_diff_match, 'AR1 fit incorrectly' - - # Check that you can calculate ARMA for a single voxel - vox = noise[5, 5, 5, :] - arma = sim._calc_ARMA_noise(vox, - None, - sample_num=2, - ) - assert len(arma) == 2, "Two outputs not given by ARMA" diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py deleted file mode 100644 index f37c51a7e..000000000 --- a/tests/utils/test_utils.py +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - - -def test_tri_sym_convert(): - from brainiak.utils.utils import from_tri_2_sym, from_sym_2_tri - import numpy as np - - sym = np.random.rand(3, 3) - tri = from_sym_2_tri(sym) - assert tri.shape[0] == 6,\ - "from_sym_2_tri returned wrong result!" - sym1 = from_tri_2_sym(tri, 3) - assert sym1.shape[0] == sym1.shape[1],\ - "from_tri_2_sym returned wrong shape!" - tri1 = from_sym_2_tri(sym1) - assert np.array_equiv(tri, tri1),\ - "from_sym_2_tri returned wrong result!" - - -def test_sumexp(): - from brainiak.utils.utils import sumexp_stable - import numpy as np - - data = np.array([[1, 1], [0, 1]]) - sums, maxs, exps = sumexp_stable(data) - assert sums.size == data.shape[1], ( - "Invalid sum(exp(v)) computation (wrong # samples in sums)") - assert exps.shape[0] == data.shape[0], ( - "Invalid exp(v) computation (wrong # features)") - assert exps.shape[1] == data.shape[1], ( - "Invalid exp(v) computation (wrong # samples)") - assert maxs.size == data.shape[1], ( - "Invalid max computation (wrong # samples in maxs)") - - -def test_concatenate_not_none(): - from brainiak.utils.utils import concatenate_not_none - import numpy as np - arrays = [None] * 5 - - arrays[1] = np.array([0, 1, 2]) - arrays[3] = np.array([3, 4]) - - r = concatenate_not_none(arrays, axis=0) - - assert np.all(np.arange(5) == r), ( - "Invalid concatenation of a list of arrays") - - -def test_cov2corr(): - from brainiak.utils.utils import cov2corr - import numpy as np - cov = np.array([[4, 3, 0], [3, 9, 0], [0, 0, 1]]) - corr = cov2corr(cov) - assert np.allclose(corr, - np.array([[1, 0.5, 0], [0.5, 1, 0], [0, 0, 1]])), ( - "Converting from covariance matrix to correlation incorrect") - - -def test_ReadDesign(): - from brainiak.utils.utils import ReadDesign - import numpy as np - import os.path - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - design = ReadDesign(fname=file_path, include_orth=False, - include_pols=False) - assert design, 'Failed to read design matrix' - assert design.reg_nuisance is None, \ - 'Nuiance regressor is not None when include_orth and include_pols are'\ - ' both set to False' - read = ReadDesign() - assert read, 'Failed to initialize an instance of the class' - design = ReadDesign(fname=file_path, include_orth=True, include_pols=True) - assert np.size(design.cols_nuisance) == 10, \ - 'Mistake in counting the number of nuiance regressors' - assert np.size(design.cols_task) == 17, \ - 'Mistake in counting the number of task conditions' - assert (np.shape(design.reg_nuisance)[0] - == np.shape(design.design_task)[0] - ), 'The number of time points in nuiance regressor does not match'\ - ' that of task response' - - -def test_gen_design(): - from brainiak.utils.utils import gen_design - import numpy as np - import os.path - files = {'FSL1': 'example_stimtime_1_FSL.txt', - 'FSL2': 'example_stimtime_2_FSL.txt', - 'AFNI1': 'example_stimtime_1_AFNI.txt', - 'AFNI2': 'example_stimtime_2_AFNI.txt'} - for key in files.keys(): - files[key] = os.path.join(os.path.dirname(__file__), files[key]) - design1 = gen_design(stimtime_files=files['FSL1'], scan_duration=[48, 20], - TR=2, style='FSL') - assert design1.shape == (34, 1), 'Returned design matrix has wrong shape' - assert design1[24] == 0, ( - "gen_design should generated design matrix for each run separately " - "and concatenate them.") - design2 = gen_design(stimtime_files=[files['FSL1'], files['FSL2']], - scan_duration=[48, 20], TR=2, style='FSL') - assert design2.shape == (34, 2), 'Returned design matrix has wrong shape' - design3 = gen_design(stimtime_files=files['FSL1'], scan_duration=68, TR=2, - style='FSL') - assert design3[24] != 0, ( - 'design matrix should be non-zero 8 seconds after an event onset.') - design4 = gen_design(stimtime_files=[files['FSL2']], - scan_duration=[48, 20], TR=2, style='FSL') - assert np.all(np.isclose(design1 * 0.5, design4)), ( - 'gen_design does not treat missing values correctly') - design5 = gen_design(stimtime_files=[files['FSL2']], - scan_duration=[48, 20], TR=1) - assert (np.abs(design4 - design5[::2])).mean() < 0.1, ( - 'design matrices sampled at different frequency do not match' - ' at corresponding time points') - design6 = gen_design(stimtime_files=[files['AFNI1']], - scan_duration=[48, 20], TR=2, style='AFNI') - assert np.all(np.isclose(design1, design6)), ( - 'design matrices generated from AFNI style and FSL style do not match') - design7 = gen_design(stimtime_files=[files['AFNI2']], - scan_duration=[48], TR=2, style='AFNI') - assert np.all(design7 == 0.0), ( - 'A negative stimulus onset of AFNI style should result in an all-zero' - + ' design matrix') - - -def test_center_mass_exp(): - from brainiak.utils.utils import center_mass_exp - import numpy as np - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp([1, 2]) - assert ('interval must be a tuple' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((1, 2, 3)) - assert ('interval must be length two' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((-2, -1)) - assert ('interval_left must be non-negative' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((-2, 3)) - assert ('interval_left must be non-negative' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((3, 3)) - assert ('interval_right must be bigger than interval_left' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((1, 2), -1) - assert ('scale must be positive' - in str(excinfo.value)) - - result = center_mass_exp((0, np.inf), 2.0) - assert np.isclose(result, 2.0), 'center of mass '\ - 'incorrect for the whole distribution' - result = center_mass_exp((1.0, 1.0+2e-10)) - assert np.isclose(result, 1.0+1e-10), 'for a small '\ - 'enough interval, the center of mass should be '\ - 'close to its mid-point' - - -def test_p_from_null(): - import numpy as np - from brainiak.utils.utils import p_from_null - - # Create random null and observed value in tail - null = np.random.randn(10000) - observed = np.ceil(np.percentile(null, 97.5) * 1000) / 1000 - - # Check that we catch improper side - with pytest.raises(ValueError): - _ = p_from_null(observed, null, side='wrong') - - # Check two-tailed p-value for observed - p_ts = p_from_null(observed, null) - assert np.isclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for observed - p_right = p_from_null(observed, null, side='right') - assert np.isclose(p_right, 0.025, atol=1e-02) - assert np.isclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for observed - p_left = p_from_null(observed, null, side='left') - assert np.isclose(p_left, 0.975, atol=1e-02) - assert np.isclose(1 - p_left, p_right, atol=1e-02) - assert np.isclose(1 - p_left, p_ts / 2, atol=1e-02) - - # Check 2-dimensional input (i.e., samples by voxels) - null = np.random.randn(10000, 3) - observed = np.ceil(np.percentile(null, 97.5, axis=0) * 1000) / 1000 - - # Check two-tailed p-value for observed - p_ts = p_from_null(observed, null, axis=0) - assert np.allclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for observed - p_right = p_from_null(observed, null, side='right', axis=0) - assert np.allclose(p_right, 0.025, atol=1e-02) - assert np.allclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for observed - p_left = p_from_null(observed, null, side='left', axis=0) - assert np.allclose(p_left, 0.975, atol=1e-02) - assert np.allclose(1 - p_left, p_right, atol=1e-02) - assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) - - # Check for exact test - p_ts = p_from_null(observed, null, exact=True, axis=0) - assert np.allclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for exact - p_right = p_from_null(observed, null, side='right', - exact=True, axis=0) - assert np.allclose(p_right, 0.025, atol=1e-02) - assert np.allclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for exact - p_left = p_from_null(observed, null, side='left', - exact=True, axis=0) - assert np.allclose(p_left, 0.975, atol=1e-02) - assert np.allclose(1 - p_left, p_right, atol=1e-02) - assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) - - -def test_phase_randomize(): - import numpy as np - from scipy.fftpack import fft - from scipy.stats import pearsonr - from brainiak.utils.utils import phase_randomize - - data = np.repeat(np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], - 30, axis=1), - 20, axis=2) - assert np.array_equal(data[..., 0], data[..., 1]) - - # Phase-randomize data across subjects (same across voxels) - shifted_data = phase_randomize(data, voxelwise=False, random_state=1) - assert shifted_data.shape == data.shape - assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) - assert not np.array_equal(shifted_data[..., 0], data[..., 0]) - - # Check that uneven n_TRs doesn't explode - _ = phase_randomize(data[:-1, ...]) - - # Check that random_state returns same shifts - shifted_data_ = phase_randomize(data, voxelwise=False, random_state=1) - assert np.array_equal(shifted_data, shifted_data_) - - shifted_data_ = phase_randomize(data, voxelwise=False, random_state=2) - assert not np.array_equal(shifted_data, shifted_data_) - - # Phase-randomize subjects and voxels - shifted_data = phase_randomize(data, voxelwise=True, random_state=1) - assert shifted_data.shape == data.shape - assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) - assert not np.array_equal(shifted_data[..., 0], data[..., 0]) - assert not np.array_equal(shifted_data[:, 0, 0], shifted_data[:, 1, 0]) - - # Try with 2-dimensional input - shifted_data = phase_randomize(data[..., 0], - voxelwise=True, - random_state=1) - assert shifted_data.ndim == 2 - assert not np.array_equal(shifted_data[:, 0], shifted_data[:, 1]) - - # Create correlated noisy data - corr_data = np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], - 2, axis=2) + np.random.randn(60, 1, 2) - - # Get correlation and frequency domain for data - corr_r = pearsonr(corr_data[:, 0, 0], - corr_data[:, 0, 1])[0] - corr_freq = fft(corr_data, axis=0) - - # Phase-randomize time series and get correlation/frequency - shifted_data = phase_randomize(corr_data) - shifted_r = pearsonr(shifted_data[:, 0, 0], - shifted_data[:, 0, 1])[0] - shifted_freq = fft(shifted_data, axis=0) - - # Check that phase-randomization reduces correlation - assert np.abs(shifted_r) < np.abs(corr_r) - - # Check that amplitude spectrum is preserved - assert np.allclose(np.abs(shifted_freq), np.abs(corr_freq)) - - -def test_check_timeseries_input(): - import numpy as np - from itertools import combinations - from brainiak.utils.utils import _check_timeseries_input - - # Set a fixed vector for comparison - vector = np.random.randn(60) - - # List of subjects with one voxel/ROI - list_1d = [vector for _ in np.arange(10)] - (data_list_1d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_1d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Array of subjects with one voxel/ROI - array_2d = np.hstack([vector[:, np.newaxis] - for _ in np.arange(10)]) - (data_array_2d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_2d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # List of 2-dimensional arrays - list_2d = [vector[:, np.newaxis] for _ in np.arange(10)] - (data_list_2d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_2d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Check if lists have mismatching size - list_bad = [list_2d[0][:-1, :]] + list_2d[1:] - with pytest.raises(ValueError): - (data_list_bad, _, _, _) = _check_timeseries_input(list_bad) - - # List of 3-dimensional arrays - list_3d = [vector[:, np.newaxis, np.newaxis] - for _ in np.arange(10)] - (data_list_3d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_3d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # 3-dimensional array - array_3d = np.dstack([vector[:, np.newaxis] - for _ in np.arange(10)]) - (data_array_3d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_3d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Check that 4-dimensional input array throws error - array_4d = array_3d[..., np.newaxis] - with pytest.raises(ValueError): - (data_array_4d, _, _, _) = _check_timeseries_input(array_4d) - - # Check they're the same - for pair in combinations([data_list_1d, data_array_2d, - data_list_2d, data_list_3d, - data_array_3d], 2): - assert np.array_equal(pair[0], pair[1]) - - # List of multivoxel arrays - matrix = np.random.randn(60, 30) - list_mv = [matrix - for _ in np.arange(10)] - (data_list_mv, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_mv) - assert n_TRs == 60 - assert n_voxels == 30 - assert n_subjects == 10 - - # 3-dimensional array with multiple voxels - array_mv = np.dstack([matrix for _ in np.arange(10)]) - (data_array_mv, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_mv) - assert n_TRs == 60 - assert n_voxels == 30 - assert n_subjects == 10 - - assert np.array_equal(data_list_mv, data_array_mv) From 56a0f1c71b9262298ad519a7544ae9cb7994ad82 Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 17 Apr 2019 17:51:27 +0200 Subject: [PATCH 02/24] Revert "Add fastSRM algorithm and dependencies" This reverts commit f0135eb0f54a3cd50680a2e000e7c5d401a149cf. --- brainiak/funcalign/fastsrm.py | 824 --------------- requirements-dev.txt | 3 - tests/eventseg/test_event.py | 155 +++ tests/factoranalysis/test_htfa.py | 168 +++ tests/factoranalysis/test_tfa.py | 110 ++ tests/fcma/data/expected_processed_data.npy | Bin 0 -> 2672 bytes tests/fcma/data/expected_raw_data.npy | Bin 0 -> 5264 bytes .../expected_searchlight_processed_data.npy | Bin 0 -> 1704016 bytes tests/fcma/test_classification.py | 222 ++++ tests/fcma/test_mvpa_voxel_selection.py | 51 + tests/fcma/test_preprocessing.py | 109 ++ tests/fcma/test_util.py | 59 ++ tests/fcma/test_voxel_selection.py | 116 +++ tests/hyperparamopt/test_hpo.py | 86 ++ tests/image/test_image.py | 172 ++++ tests/io/data/epoch_labels.npy | Bin 0 -> 160 bytes tests/io/data/mask.nii.gz | Bin 0 -> 634 bytes tests/io/data/subject1_bet.nii.gz | Bin 0 -> 7149 bytes tests/io/data/subject2_bet.nii.gz | Bin 0 -> 7112 bytes tests/io/test_io.py | 106 ++ tests/isc/test_isc.py | 956 ++++++++++++++++++ tests/reprsimil/example_design.1D | 272 +++++ tests/reprsimil/test_brsa.py | 651 ++++++++++++ tests/reprsimil/test_gbrsa.py | 630 ++++++++++++ tests/searchlight/test_searchlight.py | 293 ++++++ tests/utils/example_design.1D | 272 +++++ tests/utils/example_stimtime_1_AFNI.txt | 2 + tests/utils/example_stimtime_1_FSL.txt | 3 + tests/utils/example_stimtime_2_AFNI.txt | 1 + tests/utils/example_stimtime_2_FSL.txt | 3 + tests/utils/test_fmrisim.py | 858 ++++++++++++++++ tests/utils/test_utils.py | 396 ++++++++ 32 files changed, 5691 insertions(+), 827 deletions(-) delete mode 100644 brainiak/funcalign/fastsrm.py create mode 100644 tests/eventseg/test_event.py create mode 100644 tests/factoranalysis/test_htfa.py create mode 100644 tests/factoranalysis/test_tfa.py create mode 100644 tests/fcma/data/expected_processed_data.npy create mode 100644 tests/fcma/data/expected_raw_data.npy create mode 100644 tests/fcma/data/expected_searchlight_processed_data.npy create mode 100644 tests/fcma/test_classification.py create mode 100644 tests/fcma/test_mvpa_voxel_selection.py create mode 100644 tests/fcma/test_preprocessing.py create mode 100644 tests/fcma/test_util.py create mode 100644 tests/fcma/test_voxel_selection.py create mode 100644 tests/hyperparamopt/test_hpo.py create mode 100644 tests/image/test_image.py create mode 100644 tests/io/data/epoch_labels.npy create mode 100755 tests/io/data/mask.nii.gz create mode 100644 tests/io/data/subject1_bet.nii.gz create mode 100644 tests/io/data/subject2_bet.nii.gz create mode 100644 tests/io/test_io.py create mode 100644 tests/isc/test_isc.py create mode 100755 tests/reprsimil/example_design.1D create mode 100755 tests/reprsimil/test_brsa.py create mode 100644 tests/reprsimil/test_gbrsa.py create mode 100644 tests/searchlight/test_searchlight.py create mode 100755 tests/utils/example_design.1D create mode 100644 tests/utils/example_stimtime_1_AFNI.txt create mode 100644 tests/utils/example_stimtime_1_FSL.txt create mode 100644 tests/utils/example_stimtime_2_AFNI.txt create mode 100644 tests/utils/example_stimtime_2_FSL.txt create mode 100644 tests/utils/test_fmrisim.py create mode 100644 tests/utils/test_utils.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py deleted file mode 100644 index c093a1be3..000000000 --- a/brainiak/funcalign/fastsrm.py +++ /dev/null @@ -1,824 +0,0 @@ -"""Fast Shared Response Model (FastSRM) - -The implementations are based on the following publications: - -.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", - P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge - Advances in Neural Information Processing Systems (NIPS), 2015. - http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model - -.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging - Datasets", - Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. - Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, - Kenneth A. Norman, - IEEE International Conference on Big Data, 2016. - https://doi.org/10.1109/BigData.2016.7840719 -""" - -# Author: Hugo Richard (INRIA - Parietal) -# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) and Bertrand Thirion (Inria - Parietal) -# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience Institute) and Javier Turek (Intel Labs) - -import logging - -import numpy as np -import scipy -from sklearn.base import BaseEstimator, TransformerMixin -from sklearn.utils import assert_all_finite -from sklearn.exceptions import NotFittedError -from mpi4py import MPI -import sys -from joblib import Parallel, delayed -import os -import glob -import hashlib - -__all__ = [ - "FastSRM", -] - -logger = logging.getLogger(__name__) - - -def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir=None): - """Reduce data using given atlas - - Parameters - ---------- - - img : str - path to data. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - atlas : array, shape=[n_supervoxels, n_voxels] or None or array, shape=[n_voxels] - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - - inv_atlas : array, shape=[n_voxels, n_supervoxels] - Pseudo inverse of the atlas (only for probabilistic atlases) - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and number of sessions are large - - Returns - ------- - - reduced_data : array, shape=[n_timeframes, n_supervoxels] - reduced data - """ - if atlas is None and inv_atlas is None: - AssertionError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") - - if inv_atlas is None and atlas is not None: - atlas_values = np.unique(atlas) - if 0 in atlas_values: - atlas_values = atlas_values[1:] - data = np.load(img) - reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T - else: - data = np.load(img) - reduced_data = data.dot(inv_atlas) - - if low_ram: - name = hashlib.md5(img.encode()).hexdigest() - path = os.path.join(temp_dir, "reduced_data_" + name) - np.save(path, reduced_data) - return path - else: - return reduced_data - - -def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): - """Reduce data using given atlas. - Work done in parallel across subjects. - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - - n_jobs : integer, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and/or sessions is large - - Returns - ------- - - reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - """ - - if len(atlas.shape) == 2: - A = None - A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) - else: - A = atlas - A_inv = None - - n_subjects, n_sessions = imgs.shape - - reduced_data_list = Parallel(n_jobs=n_jobs)( - delayed(reduce_data_single)( - img, - atlas=A, - inv_atlas=A_inv, - low_ram=low_ram, - temp_dir=temp_dir - ) for img in imgs.flatten()) - - if low_ram: - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) - else: - n_timeframes, n_voxels = reduced_data_list[0].shape - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_voxels)) - - return reduced_data_list - - -def _reduced_space_compute_shared_response(reduced_data_list, - reduced_basis_list, - n_components=50): - """Compute shared response with basis fixed in reduced space - - Parameters - ---------- - - reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - reduced_basis_list : None or list of array, element i has shape=[n_components, n_supervoxels] - each subject's reduced basis - if None the basis will be generated on the fly - - n_components : int or None - number of components - - Returns - ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - - """ - n_subjects, n_sessions = reduced_data_list.shape[:2] - if type(reduced_data_list[0, 0]) == np.ndarray: - low_ram = False - elif type(reduced_data_list[0, 0]) == str: - low_ram = True - else: - AssertionError("Reduced data are stored using type %s which is neither an ndarray or str" - % type(reduced_data_list[0, 0])) - low_ram = False - - s = [None] * n_sessions - - for n in range(n_subjects): - for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] - - if reduced_basis_list is None: - n_timeframes, n_supervoxels = data_nm.shape - reduced_basis_list = [] - for subject in range(n_subjects): - q = np.eye(n_components, n_supervoxels) - reduced_basis_list.append(q) - - basis_n = reduced_basis_list[n] - if s[m] is None: - s[m] = data_nm.dot(basis_n.T) - else: - s[m] = s + data_nm.dot(basis_n.T) - - for m in range(n_sessions): - s[m] = float(s[m]) / n_subjects - - return s - - -def _compute_and_save_corr_mat(img, shared_response, temp_dir): - """computes correlation matrix and stores it - - Parameters - ---------- - img : str - path to data. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - shared_response : array, shape=[n_timeframes, n_components] - shared response - """ - data = np.load(img) - name = hashlib.md5(img.encode()).hexdigest() - path = os.path.join(temp_dir, "corr_mat_" + name) - np.save(path, shared_response.T.dot(data)) - - -def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): - """computes correlation matrix for all sessions - - Parameters - ---------- - - subject_number: int - Number that identifies the subject. Basis will be stored in [temp_dir]/basis_[subject_number].npy - - sessions : array of str - Element i of the array is a path to the data collected during session i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - Returns - ------- - - basis: array, shape=[n_component, n_voxels] or str - basis of subject [subject_number] or path to this basis - """ - corr_mat = None - for session in sessions: - name = hashlib.md5(session.encode()).hexdigest() - path = os.path.join(temp_dir, "corr_mat_" + name + ".npy") - if corr_mat is None: - corr_mat = np.load(path) - else: - corr_mat += np.load(path) - basis_i = _compute_subject_basis(corr_mat) - - if temp_dir is None: - return basis_i - else: - path = os.path.join(temp_dir, "basis_" % subject_number) - np.save(path, basis_i) - return path - - -def _compute_subject_basis(corr_mat): - """From correlation matrix between shared response and subject data, - Finds subject's basis - - Parameters - ---------- - - corr_mat: array, shape=[n_component, n_voxels] or shape=[n_components, n_supervoxels] - correlation matrix between shared response and subject data or subject reduced data - element k, v is given by S.T.dot(X_i) where S is the shared response and - X_i the data of subject i. - - Returns - ------- - - basis: array, shape=[n_components, n_voxels] or shape=[n_components, n_supervoxels] - basis of subject or reduced_basis of subject - """ - if corr_mat.shape[0] == corr_mat.shape[1]: - U, _, V = scipy.linalg.svd( - corr_mat + 1.e-18 * np.eye(corr_mat.shape[0]), - full_matrices=False - ) - else: - U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) - return U.dot(V) - - -def fast_srm(reduced_data_list, n_iter=10, n_components=None): - """Computes shared response and basis in reduced space - - Parameters - ---------- - - reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - n_iter : int - Number of iterations performed - - n_components : int or None - number of components - - Returns - ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - - if type(reduced_data_list[0, 0]) == np.ndarray: - low_ram = False - elif type(reduced_data_list[0, 0]) == str: - low_ram = True - else: - AssertionError("Reduced data are stored using type %s which is neither np.ndarray or str" - % type(reduced_data_list[0, 0])) - low_ram = False - - - n_subjects, n_sessions = reduced_data_list.shape[:2] - shared_response = _reduced_space_compute_shared_response( - reduced_data_list, - None, - n_components - ) - - reduced_basis = [None] * n_subjects - for _ in range(n_iter): - for n in range(n_subjects): - cov = None - for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] - if cov is None: - cov = shared_response[m].T.dot(data_nm) - else: - cov += shared_response[m].T.dot(data_nm) - reduced_basis[n] = _compute_subject_basis(cov) - - shared_response = _reduced_space_compute_shared_response( - reduced_data_list, - reduced_basis, - n_components - ) - - return shared_response - - -def _compute_basis_subject_online(sessions, shared_response_list): - """Computes subject's basis with shared response fixed - - Parameters - ---------- - - sessions : array of str - Element i of the array is a path to the data collected during session i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - - Returns - ------- - - basis: array, shape=[n_components, n_voxels] - basis - """ - - basis_i = None - i = 0 - for session in sessions: - data = np.load(session) - if basis_i is None: - basis_i = shared_response_list[i].T.dot(data) - else: - basis_i += shared_response_list[i].T.dot(data) - i += 1 - del data - return _compute_subject_basis(basis_i) - - -def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subjects_indexes): - """Computes shared response during one session with basis fixed - - Parameters - ---------- - - subjects : array of str - Element i of the array is a path to the data of subject i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - basis_list : None or list of array, element i has shape=[n_components, n_voxels] - basis of all subjects, element i is the basis of subject i - - temp_dir : None or str - path to basis folder where file basis_%i.npy contains the basis of subject i - - subjects_indexes : list of int or None - list of indexes corresponding to the subjects to use to compute shared response - - Returns - ------- - - shared_response : array, shape=[n_timeframes, n_components] - shared response - """ - n = 0 - shared_response = None - for k, i in enumerate(subjects_indexes): - subject = subjects[k] - data = np.load(subject) - if temp_dir is None: - basis_i = basis_list[i] - else: - basis_i = np.load(os.path.join(temp_dir, "basis_%i.npy" % i)) - - if shared_response is None: - shared_response = data.dot(basis_i.T) - else: - shared_response += data.dot(basis_i.T) - - n += 1 - return shared_response / float(n) - - -def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects_indexes): - """Computes shared response with basis fixed - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - basis_list : None or list of array, element i has shape=[n_components, n_voxels] - basis of all subjects, element i is the basis of subject i - - temp_dir : None or str - path to basis folder where file basis_%i.npy contains the basis of subject i - - n_jobs : integer, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. - - subjects_indexes : list or None - list of indexes corresponding to the subjects to use to compute shared response - - Returns - ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - shared_response_list = Parallel(n_jobs=n_jobs)( - delayed(_compute_shared_response_online_single)( - subjects, - basis_list, - temp_dir, - subjects_indexes - ) for subjects in imgs.T) - - return shared_response_list - - -class FastSRM(BaseEstimator, TransformerMixin): - """SRM decomposition using a very low amount of memory and computational power - - Given multi-subject data, factorize it as a shared response S among all - subjects and an orthogonal transform (basis) W per subject: - - .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N - - Parameters - ---------- - - atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - - n_components : int - Number of timecourses of the shared coordinates - - n_iter : int - Number of iterations to perform - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and / or sessions is large - - random_state : int or RandomState - Pseudo number generator state used for random sampling. - - n_jobs : int, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. - - verbose : bool or "warn" - if True, logs are enabled. - if False, logs are disabled. - if "warn" only warnings are printed. - - Attributes - ---------- - - `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str - basis of all subjects, element i is the basis of subject i - or path to basis of all subjects, element i is the path to the basis of subject i - """ - def __init__(self, - atlas, - n_components=20, - n_iter=100, - temp_dir=None, - low_ram=False, - random_state=None, - n_jobs=1, - verbose="warn",): - - self.random_state = random_state - self.n_jobs = n_jobs - self.verbose = verbose - self.n_components = n_components - self.n_iter = n_iter - self.atlas = atlas - - self.basis_list = None - - if temp_dir is None: - if self.verbose == "warn" or self.verbose is True: - logger.warning("temp_dir has value None. All basis (spatial maps) and " - "reconstructed data will therefore be kept in memory." - "This can lead to memory errors when the number of subjects " - "and/or sessions is large.") - - if temp_dir is not None: - if not os.path.exists(os.path.join(temp_dir, "fastsrm")): - os.mkdir(os.path.join(temp_dir, "fastsrm")) - self.temp_dir = os.path.join(temp_dir, "fastsrm") - - # Remove files in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) - for path in paths: - os.remove(path) - - self.low_ram = low_ram - - def fit(self, imgs): - """Computes basis across subjects from input imgs - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - Returns - ------- - self : object - Returns the instance itself. Contains attributes listed - at the object level. - """ - - if self.temp_dir is not None: - # Remove former basis in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "basis") + "*") - for path in paths: - os.remove(path) - - if type(imgs) != np.ndarray: - AssertionError("imgs should be of type np.ndarray but is of type %s" - % type(imgs)) - - if len(imgs.shape) != 2: - AssertionError("imgs should be an array of shape [n_subjects, n_sessions] " - "but its shape is of size %i" - % len(imgs.shape)) - - if self.verbose is True: - n_subjects, n_sessions = imgs.shape - logger.info("Fitting using %i subjects and %i sessions per subject" - % (n_subjects, n_sessions)) - - if self.verbose is True: - logger.info("[FastSRM.fit] Reducing data") - - reduced_data = reduce_data( - imgs, - atlas=self.atlas, - n_jobs=self.n_jobs, - low_ram=self.low_ram, - temp_dir=self.temp_dir - ) - - if self.verbose: - logger.info("[FastSRM.fit] Finds shared response using reduced data") - - shared_response_list = fast_srm( - reduced_data, - n_iter=self.n_iter, - n_components=self.n_components, - ) - - if self.verbose: - print("[FastSRM.fit] Finds basis using full data and shared response") - - if self.n_jobs == 1: - basis = [] - for i, sessions in enumerate(imgs): - basis_i = _compute_basis_subject_online(sessions, shared_response_list) - if self.temp_dir is None: - basis.append(basis_i) - else: - path = os.path.join(self.temp_dir, "basis_%i" % i) - np.save(path, basis_i) - basis.append(path) - del basis_i - else: - Parallel(n_jobs=self.n_jobs)( - delayed(_compute_and_save_corr_mat)( - subject, - shared_response_list[m], - self.temp_dir - ) - for m, subjects in enumerate(imgs.T) - for subject in subjects - ) - - basis = Parallel(n_jobs=self.n_jobs)(delayed(_compute_and_save_subject_basis)(i, sessions, self.temp_dir) - for i, sessions in enumerate(imgs)) - - self.basis_list = basis - return self - - def fit_transform(self, imgs, **fit_params): - """Computes basis across subjects and shared response from input imgs - return shared response. - - Parameters - ---------- - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - Returns - -------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - self.fit(imgs) - return self.transform(imgs) - - def transform(self, imgs, subjects_indexes=None): - """From data in imgs and basis from training data, - computes shared response. - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - - subjects_indexes : list or None: - if None imgs[i] will be transformed using basis[i] - otherwise imgs[i] will be transformed using basis[subjects_index[i]] - - Returns - ------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - if subjects_indexes is None: - subjects_indexes = np.arange(len(imgs)) - else: - subjects_indexes = np.array(subjects_indexes) - - shared_response = _compute_shared_response_online( - imgs, - self.basis_list, - self.temp_dir, - self.n_jobs, - subjects_indexes - ) - - return shared_response - - def inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None): - """From shared response and basis from training data reconstruct subject's data - - Parameters - ---------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - - subjects_indexes: list or None: - if None reconstructs data of all subjects' used during train - otherwise reconstructs data using subject's specified by subjects_indexes - - sessions_indexes: list or None: - if None reconstructs data using all sessions - otherwise uses only specified sessions - - Returns - ------- - reconstructed_data: array shape=[len(subjects_indexes), len(sessions_indexes), n_timeframes, n_voxels] - Reconstructed data for chosen subjects and sessions - """ - n_subjects = len(self.basis_list) - - if subjects_indexes is None: - subjects_indexes = np.arange(n_subjects) - else: - subjects_indexes = np.array(subjects_indexes) - - if sessions_indexes is None: - sessions_indexes = np.arange(len(shared_response_list)) - else: - sessions_indexes = np.array(sessions_indexes) - - data = [] - for i in subjects_indexes: - data_ = [] - if self.temp_dir is None: - basis_i = self.basis_list[i] - else: - basis_i = np.load(os.path.join(self.temp_dir, "basis_%i.npy" % i)) - - for j in sessions_indexes: - data_.append(shared_response_list[j].dot(basis_i)) - - data.append(np.array(data_)) - return np.array(data) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9132617e1..bf2baa3eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,9 +8,6 @@ setuptools_scm sphinx sphinx_rtd_theme towncrier -joblib -os -glob # testing # Also add to .conda/meta.yaml diff --git a/tests/eventseg/test_event.py b/tests/eventseg/test_event.py new file mode 100644 index 000000000..a82de27bd --- /dev/null +++ b/tests/eventseg/test_event.py @@ -0,0 +1,155 @@ +from brainiak.eventseg.event import EventSegment +from scipy.special import comb +import numpy as np +import pytest +from sklearn.exceptions import NotFittedError + + +def test_create_event_segmentation(): + es = EventSegment(5) + assert es, "Invalid EventSegment instance" + + +def test_fit_shapes(): + K = 5 + V = 3 + T = 10 + es = EventSegment(K, n_iter=2) + sample_data = np.random.rand(V, T) + es.fit(sample_data.T) + + assert es.segments_[0].shape == (T, K), "Segmentation from fit " \ + "has incorrect shape" + assert np.isclose(np.sum(es.segments_[0], axis=1), np.ones(T)).all(), \ + "Segmentation from learn_events not correctly normalized" + + T2 = 15 + sample_data2 = np.random.rand(V, T2) + test_segments, test_ll = es.find_events(sample_data2.T) + + assert test_segments.shape == (T2, K), "Segmentation from find_events " \ + "has incorrect shape" + assert np.isclose(np.sum(test_segments, axis=1), np.ones(T2)).all(), \ + "Segmentation from find_events not correctly normalized" + + es_invalid = EventSegment(K) + with pytest.raises(ValueError, message="T < K should cause error"): + es_invalid.model_prior(K-1) + with pytest.raises(ValueError, message="#Events < K should cause error"): + es_invalid.set_event_patterns(np.zeros((V, K-1))) + + +def test_simple_boundary(): + es = EventSegment(2) + random_state = np.random.RandomState(0) + + sample_data = np.array([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + \ + random_state.rand(2, 7) * 10 + es.fit(sample_data.T) + + events = np.argmax(es.segments_[0], axis=1) + assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ + "Failed to correctly segment two events" + + events_predict = es.predict(sample_data.T) + assert np.array_equal(events_predict, [0, 0, 0, 1, 1, 1, 1]), \ + "Error in predict interface" + + +def test_event_transfer(): + es = EventSegment(2) + sample_data = np.asarray([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + + with pytest.raises(NotFittedError, message="Should need to set variance"): + seg = es.find_events(sample_data.T)[0] + + with pytest.raises(NotFittedError, message="Should need to set patterns"): + seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] + + es.set_event_patterns(np.asarray([[1, 0], [0, 1]])) + seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] + + events = np.argmax(seg, axis=1) + assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ + "Failed to correctly transfer two events to new data" + + +def test_weighted_var(): + es = EventSegment(2) + + D = np.zeros((8, 4)) + for t in range(4): + D[t, :] = (1/np.sqrt(4/3)) * np.array([-1, -1, 1, 1]) + for t in range(4, 8): + D[t, :] = (1 / np.sqrt(4 / 3)) * np.array([1, 1, -1, -1]) + mean_pat = D[[0, 4], :].T + + weights = np.zeros((8, 2)) + weights[:, 0] = [1, 1, 1, 1, 0, 0, 0, 0] + weights[:, 1] = [0, 0, 0, 0, 1, 1, 1, 1] + assert np.array_equal( + es.calc_weighted_event_var(D, weights, mean_pat), [0, 0]),\ + "Failed to compute variance with 0/1 weights" + + weights[:, 0] = [1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5] + weights[:, 1] = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] + true_var = (4 * 0.5 * 12)/(6 - 5/6) * np.ones(2) / 4 + assert np.allclose( + es.calc_weighted_event_var(D, weights, mean_pat), true_var),\ + "Failed to compute variance with fractional weights" + + +def test_sym(): + es = EventSegment(4) + + evpat = np.repeat(np.arange(10).reshape(-1, 1), 4, axis=1) + es.set_event_patterns(evpat) + + D = np.repeat(np.arange(10).reshape(1, -1), 20, axis=0) + ev = es.find_events(D, var=1)[0] + + # Check that events 1-4 and 2-3 are symmetric + assert np.all(np.isclose(ev[:, :2], np.fliplr(np.flipud(ev[:, 2:])))),\ + "Fit with constant data is not symmetric" + + +def test_chains(): + es = EventSegment(5, event_chains=np.array(['A', 'A', 'B', 'B', 'B'])) + + es.set_event_patterns(np.array([[1, 1, 0, 0, 0], + [0, 0, 1, 1, 1]])) + sample_data = np.array([[0, 0, 0], [1, 1, 1]]) + seg = es.find_events(sample_data.T, 0.1)[0] + + ev = np.nonzero(seg > 0.99)[1] + assert np.array_equal(ev, [2, 3, 4]),\ + "Failed to fit with multiple chains" + + +def test_prior(): + K = 10 + T = 100 + + es = EventSegment(K) + mp = es.model_prior(T)[0] + + p_bound = np.zeros((T, K-1)) + norm = comb(T-1, K-1) + for t in range(T-1): + for k in range(K-1): + # See supplementary material of Neuron paper + # https://doi.org/10.1016/j.neuron.2017.06.041 + p_bound[t+1, k] = comb(t, k) * comb(T-t-2, K-k-2) / norm + p_bound = np.cumsum(p_bound, axis=0) + + mp_gt = np.zeros((T, K)) + for k in range(K): + if k == 0: + mp_gt[:, k] = 1 - p_bound[:, 0] + elif k == K - 1: + mp_gt[:, k] = p_bound[:, k-1] + else: + mp_gt[:, k] = p_bound[:, k-1] - p_bound[:, k] + + assert np.all(np.isclose(mp, mp_gt)),\ + "Prior does not match analytic solution" diff --git a/tests/factoranalysis/test_htfa.py b/tests/factoranalysis/test_htfa.py new file mode 100644 index 000000000..58591b130 --- /dev/null +++ b/tests/factoranalysis/test_htfa.py @@ -0,0 +1,168 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_R(): + from brainiak.factoranalysis.htfa import HTFA + with pytest.raises(TypeError) as excinfo: + HTFA() + assert "missing 2 required positional arguments" in str(excinfo.value) + + +def test_X(): + from brainiak.factoranalysis.htfa import HTFA + import numpy as np + + n_voxel = 100 + n_tr = 20 + K = 5 + max_global_iter = 3 + max_local_iter = 3 + max_voxel = n_voxel + max_tr = n_tr + + R = [] + n_subj = 2 + for s in np.arange(n_subj): + R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) + + htfa = HTFA( + K, + n_subj=n_subj, + max_global_iter=max_global_iter, + max_local_iter=max_local_iter, + max_voxel=max_voxel, + max_tr=max_tr) + + X = np.random.rand(n_voxel, n_tr) + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "Input data should be a list" in str(excinfo.value) + + X = [] + # Check that does NOT run with wrong array dimension + with pytest.raises(ValueError) as excinfo: + htfa.fit(X, R=R) + assert "Need at leat one subject to train the model" in str(excinfo.value) + + X = [] + X.append([1, 2, 3]) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "data should be an array" in str(excinfo.value) + + X = [] + X.append(np.random.rand(n_voxel)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "subject data should be 2D array" in str(excinfo.value) + + X = [] + for s in np.arange(n_subj): + X.append(np.random.rand(n_voxel, n_tr)) + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "Coordinates should be a list" in str(excinfo.value) + + R = [] + R.append([1, 2, 3]) + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("Each scanner coordinate matrix should be an array" + in str(excinfo.value)) + + R = [] + R.append(np.random.rand(n_voxel)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("Each scanner coordinate matrix should be 2D array" + in str(excinfo.value)) + + R = [] + for s in np.arange(n_subj): + R.append(np.random.rand(n_voxel - 1, 3)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("n_voxel should be the same in X[idx] and R[idx]" + in str(excinfo.value)) + + +def test_can_run(): + import numpy as np + from brainiak.factoranalysis.htfa import HTFA + from mpi4py import MPI + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + + n_voxel = 100 + n_tr = 20 + K = 5 + max_global_iter = 3 + max_local_iter = 3 + max_voxel = n_voxel + max_tr = n_tr + R = [] + n_subj = 2 + for s in np.arange(n_subj): + R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) + my_R = [] + for idx in np.arange(n_subj): + if idx % size == rank: + my_R.append(R[idx]) + + htfa = HTFA( + K, + n_subj=n_subj, + max_global_iter=max_global_iter, + max_local_iter=max_local_iter, + max_voxel=max_voxel, + max_tr=max_tr, + verbose=True) + assert htfa, "Invalid HTFA instance!" + + X = [] + for s in np.arange(n_subj): + X.append(np.random.rand(n_voxel, n_tr)) + my_data = [] + for idx in np.arange(n_subj): + if idx % size == rank: + my_data.append(X[idx]) + + if rank == 0: + htfa.fit(my_data, R=my_R) + assert True, "Root successfully running HTFA" + assert htfa.global_prior_.shape[0] == htfa.prior_bcast_size,\ + "Invalid result of HTFA! (wrong # element in global_prior)" + assert htfa.global_posterior_.shape[0] == htfa.prior_bcast_size,\ + "Invalid result of HTFA! (wrong # element in global_posterior)" + + else: + htfa.fit(my_data, R=my_R) + assert True, "worker successfully running HTFA" + print(htfa.local_weights_.shape) + assert htfa.local_weights_.shape[0] == n_tr * K,\ + "Invalid result of HTFA! (wrong # element in local_weights)" + assert htfa.local_posterior_.shape[0] == htfa.prior_size,\ + "Invalid result of HTFA! (wrong # element in local_posterior)" diff --git a/tests/factoranalysis/test_tfa.py b/tests/factoranalysis/test_tfa.py new file mode 100644 index 000000000..041d03cbd --- /dev/null +++ b/tests/factoranalysis/test_tfa.py @@ -0,0 +1,110 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_tfa(): + from brainiak.factoranalysis.tfa import TFA + import numpy as np + + n_voxel = 100 + n_tr = 20 + K = 5 + max_iter = 5 + max_num_voxel = n_voxel + max_num_tr = n_tr + tfa = TFA( + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + assert tfa, "Invalid TFA instance!" + + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + X = [1, 2, 3] + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "Input data should be an array" in str(excinfo.value) + + X = np.random.rand(n_voxel) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "Input data should be 2D array" in str(excinfo.value) + + X = np.random.rand(n_voxel, n_tr) + R = [1, 2, 3] + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "coordinate matrix should be an array" in str(excinfo.value) + + R = np.random.rand(n_voxel) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "coordinate matrix should be 2D array" in str(excinfo.value) + + R = np.random.randint(2, high=102, size=(n_voxel - 1, 3)) + # Check that does NOT run if n_voxel in X and R does not match + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "The number of voxels should be the same in X and R" in str( + excinfo.value) + + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + tfa.fit(X, R=R) + assert True, "Success running TFA with one subject!" + posterior_size = K * (tfa.n_dim + 1) + assert tfa.local_posterior_.shape[ + 0] == posterior_size,\ + "Invalid result of TFA! (wrong # element in local_posterior)" + + weight_method = 'ols' + tfa = TFA( + weight_method=weight_method, + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + assert tfa, "Invalid TFA instance!" + + X = np.random.rand(n_voxel, n_tr) + tfa.fit(X, R=R) + assert True, "Success running TFA with one subject!" + + template_prior, _, _ = tfa.get_template(R) + tfa.set_K(K) + tfa.set_seed(200) + tfa.fit(X, R=R, template_prior=template_prior) + assert True, "Success running TFA with one subject and template prior!" + assert tfa.local_posterior_.shape[ + 0] == posterior_size,\ + "Invalid result of TFA! (wrong # element in local_posterior)" + + weight_method = 'odd' + tfa = TFA( + weight_method=weight_method, + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + with pytest.raises(ValueError) as excinfo: + tfa.fit(X, R=R) + assert "'rr' and 'ols' are accepted as weight_method!" in str( + excinfo.value) diff --git a/tests/fcma/data/expected_processed_data.npy b/tests/fcma/data/expected_processed_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..460553dff13f0839b8571f9a0943cfb4eb237fe0 GIT binary patch literal 2672 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 Niah$^!R=(7{0Gj{kZb?| literal 0 HcmV?d00001 diff --git a/tests/fcma/data/expected_searchlight_processed_data.npy b/tests/fcma/data/expected_searchlight_processed_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..e23174f2c7f6c3458e2fcb6f9bee6b5ecf7970c7 GIT binary patch literal 1704016 zcmeI*F>BjE6aZj%?XS@07CaS`p%k)ZYsu1~Q!$N0htd?gTM6Vp^auH`RZj06_!&i& zX%c}xfq8zXJL&vApGMv94v{Bli0hZA z4i5_))gk#+-)P?*?zdIPceTg3j`;BGoUQ&HzI{TqKH2}o>-F8w`9pzPzwFt)4mm*`;g>*DSwhl zPxvOC^(uAfqlvmkzsS3;8~wz86UdG+sygq=eoQ&WI*RtlBX+dM^~L`qkn1-zk6Cn$ zi}T{+2xRwsm%0vPU8R`&%Lx!5K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfWR08>ihWZUonPq9L=HDr|g4}JM^AUb16VFKAZ=fke1Hvs|!2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF c5FkK+009C72oNAZfB*pk1PBlyKwzlAA8SKUk^lez literal 0 HcmV?d00001 diff --git a/tests/fcma/test_classification.py b/tests/fcma/test_classification.py new file mode 100644 index 000000000..3225fa80d --- /dev/null +++ b/tests/fcma/test_classification.py @@ -0,0 +1,222 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.classifier import Classifier +from scipy.stats.mstats import zscore +from sklearn import svm +from sklearn.linear_model import LogisticRegression +import numpy as np +import math +from numpy.random import RandomState +from scipy.spatial.distance import hamming + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def create_epoch(idx, num_voxels): + row = 12 + col = num_voxels + mat = prng.rand(row, col).astype(np.float32) + # impose a pattern to even epochs + if idx % 2 == 0: + mat = np.sort(mat, axis=0) + mat = zscore(mat, axis=0, ddof=0) + # if zscore fails (standard deviation is zero), + # set all values to be zero + mat = np.nan_to_num(mat) + mat = mat / math.sqrt(mat.shape[0]) + return mat + + +def test_classification(): + fake_raw_data = [create_epoch(i, 5) for i in range(20)] + labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # 5 subjects, 4 epochs per subject + epochs_per_subj = 4 + # svm + svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + training_data = fake_raw_data[0:12] + clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data)), labels[0:12]) + expected_confidence = np.array([-1.18234421, 0.97403604, -1.04005679, + 0.92403019, -0.95567738, 1.11746593, + -0.83275891, 0.9486868]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM does not provide correct results' + confidence = clf.decision_function(list(zip(fake_raw_data[12:], + fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence) + ) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM without recomputation ' \ + 'does not provide correct results' + y = [0, 1, 0, 1, 0, 1, 0, 1] + score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data[12:])), y) + assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ + 'the prediction score is incorrect' + # svm with partial similarity matrix computation + clf = Classifier(svm_clf, num_processed_voxels=2, + epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(fake_raw_data, fake_raw_data)), + labels, + num_training_samples=12) + y_pred = clf.predict() + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM (partial sim) does not ' \ + 'provide correct results' + confidence = clf.decision_function() + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM (partial sim) without recomputation ' \ + 'does not provide correct results' + # logistic regression + lr_clf = LogisticRegression() + clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data)), labels[0:12]) + expected_confidence = np.array([-4.49666484, 3.73025553, -4.04181695, + 3.73027436, -3.77043872, 4.42613412, + -3.35616616, 3.77716609]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via logistic regression ' \ + 'does not provide correct results' + confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence) + ) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression without precomputation ' \ + 'does not provide correct results' + + +def test_classification_with_two_components(): + fake_raw_data = [create_epoch(i, 5) for i in range(20)] + fake_raw_data2 = [create_epoch(i, 6) for i in range(20)] + labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # 5 subjects, 4 epochs per subject + epochs_per_subj = 4 + # svm + svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + training_data = fake_raw_data[0: 12] + training_data2 = fake_raw_data2[0: 12] + clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data2)), labels[0:12]) + expected_confidence = np.array([-1.23311606, 1.02440964, -0.93898336, + 1.07028798, -1.04420007, 0.97647772, + -1.0498268, 1.04970111]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM does not provide correct results' + confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM without recomputation ' \ + 'does not provide correct results' + y = [0, 1, 0, 1, 0, 1, 0, 1] + score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data2[12:])), y) + assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ + 'the prediction score is incorrect' + # svm with partial similarity matrix computation + clf = Classifier(svm_clf, num_processed_voxels=2, + epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(fake_raw_data, fake_raw_data2)), + labels, + num_training_samples=12) + y_pred = clf.predict() + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM (partial sim) does not ' \ + 'provide correct results' + confidence = clf.decision_function() + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM (partial sim) without recomputation ' \ + 'does not provide correct results' + # logistic regression + lr_clf = LogisticRegression() + clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) + # specifying num_training_samples is for coverage + clf.fit(list(zip(training_data, training_data2)), + labels[0:12], + num_training_samples=12) + expected_confidence = np.array([-4.90819848, 4.22548132, -3.76255726, + 4.46505975, -4.19933099, 4.08313584, + -4.23070437, 4.31779758]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via logistic regression ' \ + 'does not provide correct results' + confidence = clf.decision_function(list(zip(fake_raw_data[12:], + fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression without precomputation ' \ + 'does not provide correct results' + + +if __name__ == '__main__': + test_classification() + test_classification_with_two_components() diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py new file mode 100644 index 000000000..d551ff5ee --- /dev/null +++ b/tests/fcma/test_mvpa_voxel_selection.py @@ -0,0 +1,51 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector +from brainiak.searchlight.searchlight import Searchlight +from sklearn import svm +import numpy as np +from mpi4py import MPI +from numpy.random import RandomState + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def test_mvpa_voxel_selection(): + data = prng.rand(5, 5, 5, 8).astype(np.float32) + # all MPI processes read the mask; the mask file is small + mask = np.ones([5, 5, 5], dtype=np.bool) + mask[0, 0, :] = False + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + sl = Searchlight(sl_rad=1) + mvs = MVPAVoxelSelector(data, mask, labels, 2, sl) + # for cross validation, use SVM with precomputed kernel + + clf = svm.SVC(kernel='rbf', C=10) + result_volume, results = mvs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [] + for tuple in results: + if tuple[1] > 0: + output.append(int(8*tuple[1])) + expected_output = [6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + + +if __name__ == '__main__': + test_mvpa_voxel_selection() diff --git a/tests/fcma/test_preprocessing.py b/tests/fcma/test_preprocessing.py new file mode 100644 index 000000000..0ee38db49 --- /dev/null +++ b/tests/fcma/test_preprocessing.py @@ -0,0 +1,109 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +import numpy as np + +from brainiak.fcma.preprocessing import (prepare_fcma_data, prepare_mvpa_data, + prepare_searchlight_mvpa_data) +from brainiak import io + +data_dir = Path(__file__).parents[1] / 'io' / 'data' +expected_dir = Path(__file__).parent / 'data' +suffix = 'bet.nii.gz' +mask_file = data_dir / 'mask.nii.gz' +epoch_file = data_dir / 'epoch_labels.npy' +expected_labels = np.array([0, 1, 0, 1]) + + +def test_prepare_fcma_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + mask = io.load_boolean_mask(mask_file) + conditions = io.load_labels(epoch_file) + raw_data, _, labels = prepare_fcma_data(images, conditions, mask) + expected_raw_data = np.load(expected_dir / 'expected_raw_data.npy') + assert len(raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + for idx in range(len(raw_data)): + assert np.allclose(raw_data[idx], expected_raw_data[idx]), \ + 'raw data do not match in test_prepare_fcma_data' + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_fcma_data' + from brainiak.fcma.preprocessing import RandomType + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, + random=RandomType.REPRODUCIBLE) + assert len(random_raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, + random=RandomType.UNREPRODUCIBLE) + assert len(random_raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + + +def test_prepare_mvpa_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + mask = io.load_boolean_mask(mask_file) + conditions = io.load_labels(epoch_file) + processed_data, labels = prepare_mvpa_data(images, conditions, mask) + expected_processed_data = np.load(expected_dir + / 'expected_processed_data.npy') + assert len(processed_data) == len(expected_processed_data), \ + 'numbers of epochs do not match in test_prepare_mvpa_data' + for idx in range(len(processed_data)): + assert np.allclose(processed_data[idx], + expected_processed_data[idx]), ( + 'raw data do not match in test_prepare_mvpa_data') + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_mvpa_data' + + +def test_prepare_searchlight_mvpa_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + conditions = io.load_labels(epoch_file) + processed_data, labels = prepare_searchlight_mvpa_data(images, + conditions) + expected_searchlight_processed_data = np.load( + expected_dir / 'expected_searchlight_processed_data.npy') + for idx in range(len(processed_data)): + assert np.allclose(processed_data[idx], + expected_searchlight_processed_data[idx]), ( + 'raw data do not match in test_prepare_searchlight_mvpa_data') + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_searchlight_mvpa_data' + from brainiak.fcma.preprocessing import RandomType + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_processed_data, _ = prepare_searchlight_mvpa_data( + images, + conditions, + random=RandomType.REPRODUCIBLE) + assert (len(random_processed_data) + == len(expected_searchlight_processed_data)), ( + 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_processed_data, _ = prepare_searchlight_mvpa_data( + images, + conditions, + random=RandomType.UNREPRODUCIBLE) + assert (len(random_processed_data) + == len(expected_searchlight_processed_data)), ( + 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') + + +if __name__ == '__main__': + test_prepare_fcma_data() + test_prepare_mvpa_data() + test_prepare_searchlight_mvpa_data() diff --git a/tests/fcma/test_util.py b/tests/fcma/test_util.py new file mode 100644 index 000000000..6508d49b1 --- /dev/null +++ b/tests/fcma/test_util.py @@ -0,0 +1,59 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from numpy.random import RandomState +from brainiak.fcma.util import compute_correlation + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def test_correlation_computation(): + row1 = 5 + col = 10 + row2 = 6 + mat1 = prng.rand(row1, col).astype(np.float32) + mat2 = prng.rand(row2, col).astype(np.float32) + corr = compute_correlation(mat1, mat1) + expected_corr = np.corrcoef(mat1) + assert np.allclose(corr, expected_corr, atol=1e-5), ( + "high performance correlation computation does not provide correct " + "correlation results within the same set") + corr = compute_correlation(mat1, mat2) + mat = np.concatenate((mat1, mat2), axis=0) + expected_corr = np.corrcoef(mat)[0:row1, row1:] + assert np.allclose(corr, expected_corr, atol=1e-5), ( + "high performance correlation computation does not provide correct " + "correlation results between two sets") + + +def test_correlation_nans(): + row1 = 5 + col = 10 + row2 = 6 + mat1 = prng.rand(row1, col).astype(np.float32) + mat2 = prng.rand(row2, col).astype(np.float32) + mat1[0, 0] = np.nan + corr = compute_correlation(mat1, mat2, return_nans=False) + assert np.all(corr == 0, axis=1)[0] + assert np.sum(corr == 0) == row2 + corr = compute_correlation(mat1, mat2, return_nans=True) + assert np.all(np.isnan(corr), axis=1)[0] + assert np.sum(np.isnan(corr)) == row2 + + +if __name__ == '__main__': + test_correlation_computation() + test_correlation_nans() diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py new file mode 100644 index 000000000..98fadc4ea --- /dev/null +++ b/tests/fcma/test_voxel_selection.py @@ -0,0 +1,116 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.voxelselector import VoxelSelector +from scipy.stats.mstats import zscore +from sklearn import svm +from sklearn.linear_model import LogisticRegression +import numpy as np +import math +from mpi4py import MPI +from numpy.random import RandomState + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def create_epoch(): + row = 12 + col = 5 + mat = prng.rand(row, col).astype(np.float32) + mat = zscore(mat, axis=0, ddof=0) + # if zscore fails (standard deviation is zero), + # set all values to be zero + mat = np.nan_to_num(mat) + mat = mat / math.sqrt(mat.shape[0]) + return mat + + +def test_voxel_selection(): + fake_raw_data = [create_epoch() for i in range(8)] + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + vs = VoxelSelector(labels, 4, 2, fake_raw_data, voxel_unit=1) + # test scipy normalization + fake_corr = prng.rand(1, 4, 5).astype(np.float32) + fake_corr = vs._correlation_normalization(fake_corr) + if MPI.COMM_WORLD.Get_rank() == 0: + expected_fake_corr = [[[1.06988919, 0.51641309, -0.46790636, + -1.31926763, 0.2270218], + [-1.22142744, -1.39881694, -1.2979387, + 1.05702305, -0.6525566], + [0.89795232, 1.27406132, 0.36460185, + 0.87538344, 1.5227468], + [-0.74641371, -0.39165771, 1.40124381, + -0.61313909, -1.0972116]]] + assert np.allclose(fake_corr, expected_fake_corr), \ + 'within-subject normalization does not provide correct results' + # for cross validation, use SVM with precomputed kernel + # no shrinking, set C=1 + clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [7, 4, 6, 4, 4] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + # for cross validation, use logistic regression + clf = LogisticRegression() + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [6, 3, 6, 4, 4] + assert np.allclose(output, expected_output, atol=1), ( + "voxel selection via logistic regression does not provide correct " + "results") + + +def test_voxel_selection_with_two_masks(): + fake_raw_data1 = [create_epoch() for i in range(8)] + fake_raw_data2 = [create_epoch() for i in range(8)] + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + vs = VoxelSelector(labels, 4, 2, fake_raw_data1, + raw_data2=fake_raw_data2, voxel_unit=1) + # for cross validation, use SVM with precomputed kernel + # no shrinking, set C=1 + clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [3, 3, 3, 6, 6] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + # for cross validation, use logistic regression + clf = LogisticRegression() + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [3, 4, 4, 6, 6] + assert np.allclose(output, expected_output, atol=1), ( + "voxel selection via logistic regression does not provide correct " + "results") + + +if __name__ == '__main__': + test_voxel_selection() + test_voxel_selection_with_two_masks() diff --git a/tests/hyperparamopt/test_hpo.py b/tests/hyperparamopt/test_hpo.py new file mode 100644 index 000000000..3904458ee --- /dev/null +++ b/tests/hyperparamopt/test_hpo.py @@ -0,0 +1,86 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest +import numpy as np +import scipy.stats as st +from brainiak.hyperparamopt.hpo import gmm_1d_distribution, fmin + + +def test_simple_gmm(): + x = np.array([1., 1., 2., 3., 1.]) + d = gmm_1d_distribution(x, min_limit=0., max_limit=4.) + assert d(1.1) > d(3.5), "GMM distribution not behaving correctly" + assert d(2.0) > d(3.0), "GMM distribution not behaving correctly" + assert d(-1.0) == 0, "GMM distribution out of bounds error" + assert d(9.0) == 0, "GMM distribution out of bounds error" + + samples = d.get_samples(n=25) + np.testing.assert_array_less(samples, 4.) + np.testing.assert_array_less(0., samples) + + +def test_simple_gmm_weights(): + x = np.array([1., 1., 2., 3., 1., 3.]) + d = gmm_1d_distribution(x) + + x2 = np.array([1., 2., 3.]) + w = np.array([3., 1., 2.]) + d2 = gmm_1d_distribution(x2, weights=w) + y2 = d2(np.array([1.1, 2.0])) + + assert d2(1.1) == y2[0],\ + "GMM distribution array & scalar results don't match" + assert np.abs(d(1.1) - d2(1.1)) < 1e-5,\ + "GMM distribution weights not handled correctly" + assert np.abs(d(2.0) - d2(2.0)) < 1e-5,\ + "GMM distribution weights not handled correctly" + + +def test_simple_hpo(): + + def f(args): + x = args['x'] + return x*x + + s = {'x': {'dist': st.uniform(loc=-10., scale=20), 'lo': -10., 'hi': 10.}} + trials = [] + + # Test fmin and ability to continue adding to trials + best = fmin(loss_fn=f, space=s, max_evals=40, trials=trials) + best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) + + assert len(trials) == 50, "HPO continuation trials not working" + + # Test verbose flag + best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) + + yarray = np.array([tr['loss'] for tr in trials]) + np.testing.assert_array_less(yarray, 100.) + + xarray = np.array([tr['x'] for tr in trials]) + np.testing.assert_array_less(np.abs(xarray), 10.) + + assert best['loss'] < 100., "HPO out of range" + assert np.abs(best['x']) < 10., "HPO out of range" + + # Test unknown distributions + s2 = {'x': {'dist': 'normal', 'mu': 0., 'sigma': 1.}} + trials2 = [] + with pytest.raises(ValueError) as excinfo: + fmin(loss_fn=f, space=s2, max_evals=40, trials=trials2) + assert "Unknown distribution type for variable" in str(excinfo.value) + + s3 = {'x': {'dist': st.norm(loc=0., scale=1.)}} + trials3 = [] + fmin(loss_fn=f, space=s3, max_evals=40, trials=trials3) diff --git a/tests/image/test_image.py b/tests/image/test_image.py new file mode 100644 index 000000000..4796bc3f1 --- /dev/null +++ b/tests/image/test_image.py @@ -0,0 +1,172 @@ +# Copyright 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Iterable, Sequence + +import numpy as np +import pytest + +from nibabel.nifti1 import Nifti1Pair +from nibabel.spatialimages import SpatialImage + +from brainiak.image import (mask_image, mask_images, MaskedMultiSubjectData, + multimask_images, SingleConditionSpec) + + +@pytest.fixture +def masked_multi_subject_data(masked_images): + return np.stack(masked_images, axis=-1) + + +class TestMaskedMultiSubjectData: + def test_from_masked_images(self, masked_images, + masked_multi_subject_data): + result = MaskedMultiSubjectData.from_masked_images(masked_images, + len(masked_images)) + assert np.array_equal(np.moveaxis(result, 1, 0), + masked_multi_subject_data) + + +@pytest.fixture +def condition_spec() -> SingleConditionSpec: + return np.array([[[1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]]], + dtype=np.int8).view(SingleConditionSpec) + + +class TestUniqueLabelConditionSpec: + def test_extract_labels(self, condition_spec: SingleConditionSpec + ) -> None: + assert np.array_equal(condition_spec.extract_labels(), + np.array([0, 1])) + + +@pytest.fixture +def spatial_image() -> SpatialImage: + return Nifti1Pair(np.array([[[[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]]]).reshape(4, 4, 4, 1), + np.eye(4)) + + +@pytest.fixture +def mask() -> np.ndarray: + return np.array([[[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]], dtype=np.bool) + + +@pytest.fixture +def masked_data() -> np.ndarray: + return np.array([[1, 0, 0, 1, 0, 1, 1, 0]]).reshape(8, 1) + + +@pytest.fixture +def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]: + images = [spatial_image] + image_data = spatial_image.get_data().copy() + image_data[1, 1, 1, 0] = 2 + images.append(Nifti1Pair(image_data, np.eye(4))) + return images + + +@pytest.fixture +def masks(mask: np.ndarray) -> Sequence[np.ndarray]: + masks = [mask] + mask2 = mask.copy() + mask2[0, 0, 0] = 1 + masks.append(mask2) + mask3 = mask.copy() + mask3[2, 2, 2] = 0 + masks.append(mask3) + return masks + + +@pytest.fixture +def multimasked_images(masked_data) -> Iterable[Iterable[np.ndarray]]: + masked_data_2 = np.concatenate(([[2]], masked_data[1:, :])) + return [[masked_data, np.concatenate(([[0]], masked_data)), + masked_data[:-1, :]], + [masked_data_2, np.concatenate(([[0]], masked_data_2)), + masked_data_2[:-1, :]]] + + +@pytest.fixture +def masked_images(multimasked_images) -> Iterable[np.ndarray]: + return [multimasked_image[0] for multimasked_image in multimasked_images] + + +def test_mask_image(spatial_image: SpatialImage, mask: np.ndarray, + masked_data: np.ndarray) -> None: + result = mask_image(spatial_image, mask) + assert np.array_equal(result, masked_data) + + +def test_mask_image_with_type(spatial_image: SpatialImage, mask: np.ndarray, + masked_data: np.ndarray) -> None: + masked_data_type = np.float32 + result = mask_image(spatial_image, mask, masked_data_type) + assert result.dtype == masked_data_type + assert np.allclose(result, masked_data) + + +def test_multimask_images( + images: Iterable[SpatialImage], + masks: Sequence[np.ndarray], + multimasked_images: Iterable[Iterable[np.ndarray]] + ) -> None: + result = multimask_images(images, masks) + for result_images, expected_images in zip(result, + multimasked_images): + for result_image, expected_image in zip(result_images, + expected_images): + assert np.array_equal(result_image, expected_image) + + +def test_mask_images( + images: Iterable[SpatialImage], + mask: np.ndarray, + masked_images: Iterable[np.ndarray] + ) -> None: + result = mask_images(images, mask) + for result_image, expected_image in zip(result, masked_images): + assert np.array_equal(result_image, expected_image) diff --git a/tests/io/data/epoch_labels.npy b/tests/io/data/epoch_labels.npy new file mode 100644 index 0000000000000000000000000000000000000000..50b0a4304f8d755e5ba6147e7c2ebe6021326848 GIT binary patch literal 160 zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7ItmbEXrQU1P^+Mzz{Lmz4EO<939;$`jT{x$ literal 0 HcmV?d00001 diff --git a/tests/io/data/mask.nii.gz b/tests/io/data/mask.nii.gz new file mode 100755 index 0000000000000000000000000000000000000000..687ab69749ac9e33b4aa23f6e3bf269c37b6d2c9 GIT binary patch literal 634 zcmb2|=3oE==C^kYvzQzuSRY7UV(CwqYA3*PLz^Y&=2G8@+%padSBUgK5L~<>QHRC) z|C8GLMjPH7dbe+W?){o?WzD748Qp&kbN`mC348qa__r_B|F&AJds~99kSj9e&V_GzaCYpfvaTyQ~&ez z32dn14y-5dKEL;-al)M16f~)V`jdO_e|h)Y@fXje&GVN&XGhW5pmk~Y{+B;DvO5%& Vp-9kx)6iAN5cDRYH=&V@0RSu0fKUJc literal 0 HcmV?d00001 diff --git a/tests/io/data/subject1_bet.nii.gz b/tests/io/data/subject1_bet.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..f34b35c1d201cfae247bc3e4857f17eaee53a5f4 GIT binary patch literal 7149 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 P=cU0(r*}}F+er8i>K-AX literal 0 HcmV?d00001 diff --git a/tests/io/test_io.py b/tests/io/test_io.py new file mode 100644 index 000000000..9f72550f6 --- /dev/null +++ b/tests/io/test_io.py @@ -0,0 +1,106 @@ +# Copyright 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Iterable, Sequence + +import nibabel as nib +import numpy as np +import pytest + +from brainiak import io + + +@pytest.fixture +def in_dir() -> Path: + return Path(__file__).parent / "data" + + +@pytest.fixture +def expected_image_data_shape() -> Sequence[int]: + return (64, 64, 26, 10) + + +@pytest.fixture +def mask_path(in_dir: Path) -> Path: + return in_dir / "mask.nii.gz" + + +@pytest.fixture +def labels_path(in_dir: Path) -> Path: + return in_dir / "epoch_labels.npy" + + +@pytest.fixture +def expected_condition_spec_shape() -> Sequence[int]: + return (2, 2, 10) + + +@pytest.fixture +def expected_n_subjects() -> int: + return 2 + + +@pytest.fixture +def image_paths(in_dir: Path) -> Iterable[Path]: + return (in_dir / "subject1_bet.nii.gz", in_dir / "subject2_bet.nii.gz") + + +def test_load_images_from_dir_data_shape( + in_dir: Path, + expected_image_data_shape: Sequence[int], + expected_n_subjects: int + ) -> None: + for i, image in enumerate(io.load_images_from_dir(in_dir, "bet.nii.gz")): + assert image.get_data().shape == (64, 64, 26, 10) + assert i + 1 == expected_n_subjects + + +def test_load_images_data_shape( + image_paths: Iterable[Path], + expected_image_data_shape: Sequence[int], + expected_n_subjects: int + ) -> None: + for i, image in enumerate(io.load_images(image_paths)): + assert image.get_data().shape == (64, 64, 26, 10) + assert i + 1 == expected_n_subjects + + +def test_load_boolean_mask(mask_path: Path) -> None: + mask = io.load_boolean_mask(mask_path) + assert mask.dtype == np.bool + + +def test_load_boolean_mask_predicate(mask_path: Path) -> None: + mask = io.load_boolean_mask(mask_path, lambda x: np.logical_not(x)) + expected_mask = np.logical_not(io.load_boolean_mask(mask_path)) + assert np.array_equal(mask, expected_mask) + + +def test_load_labels(labels_path: Path, + expected_condition_spec_shape: Sequence[int], + expected_n_subjects: int) -> None: + condition_specs = io.load_labels(labels_path) + i = 0 + for condition_spec in condition_specs: + assert condition_spec.shape == expected_condition_spec_shape + i += 1 + assert i == expected_n_subjects + + +def test_save_as_nifti_file(tmpdir) -> None: + out_file = str(tmpdir / "nifti.nii") + shape = (4, 4, 4) + io.save_as_nifti_file(np.ones(shape), np.eye(4), out_file) + assert nib.load(out_file).get_data().shape == shape diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py new file mode 100644 index 000000000..fc4ea17c1 --- /dev/null +++ b/tests/isc/test_isc.py @@ -0,0 +1,956 @@ +import numpy as np +import logging +import pytest +from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, + squareform_isfc, timeshift_isc, + phaseshift_isc) +from scipy.spatial.distance import squareform + +logger = logging.getLogger(__name__) + + +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data + + +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + correlated = True + while correlated: + uncorrelated = np.random.randn(n_TRs, + n_subjects)[:, np.newaxis, :] + unc_max = np.amax(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + unc_mean = np.mean(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + if unc_max < .3 and np.abs(unc_mean) < .001: + correlated = False + data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], + 20, axis=2) + data = np.concatenate((data, uncorrelated), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data + + +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC inputs") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + logger.info("Finished testing ISC inputs") + + +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) + + # Just two subjects + iscs_loo = isc(data[..., :2], pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_voxels,) + + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) + + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic='mean') + assert isc_mean.shape == (n_voxels,) + + isc_median = isc(data, pairwise=False, summary_statistic='median') + assert isc_median.shape == (n_voxels,) + + with pytest.raises(ValueError): + isc(data, pairwise=False, summary_statistic='min') + + logger.info("Finished testing ISC options") + + +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + logger.info("Testing ISC outputs") + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) + assert np.all(iscs[:, -1] < 1.) + + logger.info("Finished testing ISC outputs") + + +# Check for proper handling of NaNs in ISC +def test_isc_nans(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Inject NaNs into data + data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + iscs_loo = isc(data, pairwise=False, tolerate_nans=False) + assert np.sum(np.isnan(iscs_loo)) == n_subjects + + # Tolerate all NaNs, only subject with NaNs yields NaN + iscs_loo = isc(data, pairwise=False, tolerate_nans=True) + assert np.sum(np.isnan(iscs_loo)) == 1 + + # Pairwise approach shouldn't care + iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) + iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) + assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(iscs_pw_T)) == + np.sum(np.isnan(iscs_pw_F)) == + n_subjects - 1) + + # Set proportion of nans to reject (70% and 90% non-NaN) + data[0, 0, :] = np.nan + data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan + data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan + + iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) + iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) + iscs_loo_95 = isc(data, pairwise=False, tolerate_nans=.95) + iscs_loo_90 = isc(data, pairwise=False, tolerate_nans=.90) + iscs_loo_80 = isc(data, pairwise=False, tolerate_nans=.8) + iscs_loo_70 = isc(data, pairwise=False, tolerate_nans=.7) + iscs_loo_60 = isc(data, pairwise=False, tolerate_nans=.6) + + assert (np.sum(np.isnan(iscs_loo_F)) == + np.sum(np.isnan(iscs_loo_95)) == 60) + assert (np.sum(np.isnan(iscs_loo_80)) == + np.sum(np.isnan(iscs_loo_90)) == 42) + assert (np.sum(np.isnan(iscs_loo_T)) == + np.sum(np.isnan(iscs_loo_60)) == + np.sum(np.isnan(iscs_loo_70)) == 28) + assert np.array_equal(np.sum(np.isnan(iscs_loo_F), axis=0), + np.sum(np.isnan(iscs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(iscs_loo_80), axis=0), + np.sum(np.isnan(iscs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(iscs_loo_T), axis=0), + np.sum(np.isnan(iscs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(iscs_loo_T), axis=0), + np.sum(np.isnan(iscs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(iscs_loo_60), axis=0), + np.sum(np.isnan(iscs_loo_70), axis=0)))) + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Make sure voxel with NaNs across all subjects is always removed + data[0, 0, :] = np.nan + iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) + iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) + assert np.allclose(iscs_loo_T, iscs_loo_F, equal_nan=True) + assert (np.sum(np.isnan(iscs_loo_T)) == + np.sum(np.isnan(iscs_loo_F)) == + n_subjects) + + iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) + iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) + assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(iscs_pw_T)) == + np.sum(np.isnan(iscs_pw_F)) == + n_subjects * (n_subjects - 1) / 2) + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + n_bootstraps = 10 + + logger.info("Testing bootstrap hypothesis test") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Test one-sample bootstrap test with pairwise approach + n_bootstraps = 10 + + iscs = isc(data, pairwise=True, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Check random seeds + iscs = isc(data, pairwise=False, summary_statistic=None) + distributions = [] + for random_state in [42, 42, None]: + observed, ci, p, distribution = bootstrap_isc( + iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95, + random_state=random_state) + distributions.append(distribution) + assert np.array_equal(distributions[0], distributions[1]) + assert not np.array_equal(distributions[1], distributions[2]) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='median')) + + logger.info("Finished testing bootstrap hypothesis test") + + +# Test permutation test with group assignments +def test_permutation_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + group_assignment = [1] * 10 + [2] * 10 + + logger.info("Testing permutation test") + + # Create dataset with two groups in pairwise approach + data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=5, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=200) + + # Create data with two groups in leave-one-out approach + data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=10, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=200) + + # One-sample leave-one-out permutation test + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic='median', + n_permutations=200) + + # One-sample pairwise permutation test + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=True, + summary_statistic='median', + n_permutations=200) + + # Small one-sample pairwise exact test + data = simulated_timeseries(12, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median', + n_permutations=10000) + + # Small two-sample pairwise exact test (and unequal groups) + data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=10000) + + # Small two-sample leave-one-out exact test (and unequal groups) + data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=10000) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing permutaton test") + + +def test_timeshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing circular time-shift") + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, pairwise + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing circular time-shift") + + +# Phase randomization test +def test_phaseshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing phase randomization") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Phase randomization one-sample test, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing phase randomization") + + +# Test ISFC +def test_isfc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing ISFC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + isfcs, iscs = isfc(data, pairwise=False, summary_statistic=None) + assert isfcs.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) + assert iscs.shape == (n_subjects, n_voxels) + + # Without vectorized upper triangle + isfcs = isfc(data, pairwise=False, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_voxels) + + # Just two subjects + isfcs, iscs = isfc(data[..., :2], pairwise=False, summary_statistic=None) + assert isfcs.shape == (n_voxels * (n_voxels - 1) / 2,) + assert iscs.shape == (n_voxels,) + + isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_voxels, n_voxels) + + # ISFC with pairwise approach + isfcs, iscs = isfc(data, pairwise=True, summary_statistic=None) + assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels * (n_voxels - 1) / 2) + assert iscs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels) + + isfcs = isfc(data, pairwise=True, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels, n_voxels) + + # ISFC with summary statistics + isfcs, iscs = isfc(data, pairwise=True, summary_statistic='mean') + isfcs, iscs = isfc(data, pairwise=True, summary_statistic='median') + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) + assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) + assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) + + isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) + assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) + assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=False) + isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) + isfcs, iscs_v = isfc(data, pairwise=False) + assert np.allclose(iscs, iscs_v, rtol=1e-03) + + # Check that ISC and ISFC diagonal are identical (pairwise) + iscs = isc(data, pairwise=True) + isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) + isfcs, iscs_v = isfc(data, pairwise=True) + assert np.allclose(iscs, iscs_v, rtol=1e-03) + + # Generate 'targets' data and use for ISFC + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + n_targets = 15 + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Ensure 'square' output enforced + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=True) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Check list input for targets + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='list') + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Check that mismatching subjects / TRs breaks targets + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + + with pytest.raises(ValueError): + isfcs = isfc(data, targets=targets_data[..., :-1], + pairwise=False, vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + with pytest.raises(ValueError): + isfcs = isfc(data, targets=targets_data[:-1, ...], + pairwise=False, vectorize_isfcs=False) + + # Check targets for only 2 subjects + isfcs = isfc(data[..., :2], targets=targets_data[..., :2], + pairwise=False, summary_statistic=None) + assert isfcs.shape == (2, n_voxels, n_targets) + + isfcs = isfc(data[..., :2], targets=targets_data[..., :2], + pairwise=True, summary_statistic=None) + assert isfcs.shape == (2, n_voxels, n_targets) + + # Check that supplying targets enforces leave-one-out + isfcs_pw = isfc(data, targets=targets_data, pairwise=True, + vectorize_isfcs=False, tolerate_nans=False) + assert isfcs_pw.shape == (n_subjects, n_voxels, n_targets) + + logger.info("Finished testing ISFC options") + + +# Check for proper handling of NaNs in ISFC +def test_isfc_nans(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Inject NaNs into data + data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels * 2 - 1) + + # With vectorized ISFCs + isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels - 1) + + # Tolerate all NaNs, only subject with NaNs yields NaN + isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == n_voxels * 2 - 1 + + isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == n_voxels - 1 + + # Pairwise approach shouldn't care + isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_F)) == + (n_voxels * 2 - 1) * (n_subjects - 1)) + + isfcs_pw_T, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, + tolerate_nans=True) + isfcs_pw_F, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_F)) == + (n_voxels - 1) * (n_subjects - 1)) + + # Set proportion of nans to reject (70% and 90% non-NaN) + data[0, 0, :] = np.nan + data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan + data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan + + isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + isfcs_loo_95 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.95) + isfcs_loo_90 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.90) + isfcs_loo_80 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.8) + isfcs_loo_70 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.7) + isfcs_loo_60 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.6) + assert (np.sum(np.isnan(isfcs_loo_F)) == + np.sum(np.isnan(isfcs_loo_95)) == 3420) + assert (np.sum(np.isnan(isfcs_loo_80)) == + np.sum(np.isnan(isfcs_loo_90)) == 2430) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_60)) == + np.sum(np.isnan(isfcs_loo_70)) == 1632) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), + np.sum(np.isnan(isfcs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), + np.sum(np.isnan(isfcs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_60), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)))) + + isfcs_loo_T, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=True) + isfcs_loo_F, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=False) + isfcs_loo_95, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.95) + isfcs_loo_90, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.90) + isfcs_loo_80, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.8) + isfcs_loo_70, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.7) + isfcs_loo_60, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.6) + assert (np.sum(np.isnan(isfcs_loo_F)) == + np.sum(np.isnan(isfcs_loo_95)) == 1680) + assert (np.sum(np.isnan(isfcs_loo_80)) == + np.sum(np.isnan(isfcs_loo_90)) == 1194) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_60)) == + np.sum(np.isnan(isfcs_loo_70)) == 802) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), + np.sum(np.isnan(isfcs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), + np.sum(np.isnan(isfcs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_60), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)))) + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Make sure voxel with NaNs across all subjects is always removed + data[0, 0, :] = np.nan + isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_loo_T, isfcs_loo_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_F)) == + 1180) + + isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_T)) == + 11210) + + # Check for NaN-handling in targets + n_targets = 15 + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + + # Inject NaNs into targets_data + targets_data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + isfcs_loo = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False, tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == (n_subjects - 1) * (n_targets * 2) + + # Single NaN in targets will get averaged out with tolerate + isfcs_loo = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False, tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == 0 + + +def test_squareform_isfc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Generate square redundant ISFCs + isfcs_r = isfc(data, vectorize_isfcs=False) + assert isfcs_r.shape == (n_subjects, n_voxels, n_voxels) + + # Squareform these into condensed ISFCs and ISCs + isfcs_c, iscs_c = squareform_isfc(isfcs_r) + assert isfcs_c.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) + assert iscs_c.shape == (n_subjects, n_voxels) + + # Go back the other way and check it's the same + isfcs_new = squareform_isfc(isfcs_c, iscs_c) + assert np.array_equal(isfcs_r, isfcs_new) + + # Check against ISC function + assert np.allclose(isc(data), iscs_c, rtol=1e-03) + + # Check for two subjects + isfcs_r = isfc(data[..., :2], vectorize_isfcs=False) + assert isfcs_r.shape == (n_voxels, n_voxels) + isfcs_c, iscs_c = squareform_isfc(isfcs_r) + assert isfcs_c.shape == (n_voxels * (n_voxels - 1) / 2,) + assert iscs_c.shape == (n_voxels,) + assert np.array_equal(isfcs_r, squareform_isfc(isfcs_c, iscs_c)) + + +if __name__ == '__main__': + test_isc_input() + test_isc_options() + test_isc_output() + test_isc_nans() + test_bootstrap_isc() + test_permutation_isc() + test_timeshift_isc() + test_phaseshift_isc() + test_isfc_options() + test_isfc_nans() + test_squareform_isfc() + logger.info("Finished all ISC tests") diff --git a/tests/reprsimil/example_design.1D b/tests/reprsimil/example_design.1D new file mode 100755 index 000000000..7385edea6 --- /dev/null +++ b/tests/reprsimil/example_design.1D @@ -0,0 +1,272 @@ +# + 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 + 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 + 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 + 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 + 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 + 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 + 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 + 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 + 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 + 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 + 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 + 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 + 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 + 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 + 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 + 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 + 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 + 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 + 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 + 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 + 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 + 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 + 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 + 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 + 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 + 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 + 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 + 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 + 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 + 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 + 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 + 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 + 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 + 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 + 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 + 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 + 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 + 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 + 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 + 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 + 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 + 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 + 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 + 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 + 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 + 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 + 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 + 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 + 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 + 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 + 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 + 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 + 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 + 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 + 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 + 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 + 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 + 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 + 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 + 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 + 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 + 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 + 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 + 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 + 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 + 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 + 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 + 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 + 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 + 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 + 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 + 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 + 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 + 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 + 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 + 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 + 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 + 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 + 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 + 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 + 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 + 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 + 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 + 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 + 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 + 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 + 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 + 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 + 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 + 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 + 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 + 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 + 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 + 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 + 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 + 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 + 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 + 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 + 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 + 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 + 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 + 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 + 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 + 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 + 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 + 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 + 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 + 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 + 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 + 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 + 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 + 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 + 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 + 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 + 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 + 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 + 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 + 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 + 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 + 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 + 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 + 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 + 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 + 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 + 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 + 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 + 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 + 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 + 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 + 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 + 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 + 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 + 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 + 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 + 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 + 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 + 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 + 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 + 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 + 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 + 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 + 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 + 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 + 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 + 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 + 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 + 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 + 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 + 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 + 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 + 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 + 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 + 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 + 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 + 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 + 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 + 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 + 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 + 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 + 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 + 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 + 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 + 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 + 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 + 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 + 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 + 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 + 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 + 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 + 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 + 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 + 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 + 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 + 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 + 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 + 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 + 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 + 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 + 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 + 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 + 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 + 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 + 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 + 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 + 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 + 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 +# + diff --git a/tests/reprsimil/test_brsa.py b/tests/reprsimil/test_brsa.py new file mode 100755 index 000000000..fbf898b86 --- /dev/null +++ b/tests/reprsimil/test_brsa.py @@ -0,0 +1,651 @@ +# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, +# Princeton University +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_can_instantiate(): + import brainiak.reprsimil.brsa + s = brainiak.reprsimil.brsa.BRSA() + assert s, "Invalid BRSA instance!" + + s = brainiak.reprsimil.brsa.BRSA( + n_iter=50, rank=5, auto_nuisance=False, n_nureg=2, nureg_method='ICA', + baseline_single=False, init_iter=5, GP_space=True, GP_inten=True, + tol=2e-3, eta=0.001, space_smooth_range=10.0, inten_smooth_range=100.0, + tau_range=2.0, + tau2_prior=brainiak.reprsimil.brsa.prior_GP_var_inv_gamma, + optimizer='CG', random_state=100, anneal_speed=20) + assert s, "Invalid BRSA instance!" + + +def test_fit(): + from brainiak.reprsimil.brsa import BRSA + import brainiak.utils.utils as utils + import scipy.stats + import numpy as np + import os.path + np.random.seed(10) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 2 times, mimicking 2 runs of itenditcal timing + n_run = 2 + design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) + design.n_TR = design.n_TR * n_run + + # start simulating some data + n_V = 50 + n_C = np.size(design.design_task, axis=1) + n_T = design.n_TR + + noise_bot = 0.5 + noise_top = 5.0 + noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot + + # generating noise + noise = np.zeros([n_T, n_V]) + noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ + np.random.randn(n_V) * noise_level + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_level = 5.0 # test with high SNR + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends + # not only on beta but also on x. + inten = np.random.rand(n_V) * 20.0 + + # parameters of Gaussian process to generate pseuso SNR + tau = 1.0 + smooth_width = 5.0 + inten_kernel = 1.0 + + coords = np.arange(0, n_V)[:, None] + + dist2 = np.square(coords - coords.T) + + inten_tile = np.tile(inten, [n_V, 1]) + inten_diff2 = (inten_tile - inten_tile.T)**2 + + K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / + inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 + + L = np.linalg.cholesky(K) + snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level + sqrt_v = noise_level * snr + betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v + signal = np.dot(design.design_task, betas_simulated) + + # Adding noise to signal as data + Y = signal + noise + inten + + scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) + + # Test fitting with GP prior. + brsa = BRSA(GP_space=True, GP_inten=True, n_iter=5, + init_iter=10, auto_nuisance=False, tol=2e-3) + + # We also test that it can detect baseline regressor included in the + # design matrix for task conditions + wrong_design = np.insert(design.design_task, 0, 1, axis=1) + with pytest.raises(ValueError) as excinfo: + brsa.fit(X=Y, design=wrong_design, scan_onsets=scan_onsets, + coords=coords, inten=inten) + assert ('Your design matrix appears to have included baseline time series.' + in str(excinfo.value)) + # Now we fit with the correct design matrix. + brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets, + coords=coords, inten=inten) + + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], + u_i[np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + + noise_new = np.zeros([n_T, n_V]) + noise_new[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise_new[i_t, :] = noise_new[i_t - 1, :] * \ + rho1 + np.random.randn(n_V) * noise_level + + Y_new = signal + noise_new + inten + ts, ts0 = brsa.transform(Y_new, scan_onsets=scan_onsets) + p = scipy.stats.pearsonr(ts[:, 0], design.design_task[:, 0])[1] + assert p < 0.01, ( + "Recovered time series does not correlate with true time series!") + assert np.shape(ts) == (n_T, n_C) and np.shape(ts0) == (n_T, 1), ( + "Wrong shape in returned time series by transform function!") + + [score, score_null] = brsa.score( + X=Y_new, design=design.design_task, scan_onsets=scan_onsets) + assert score > score_null, ( + "Full model does not win over null model on data containing signal") + + [score, score_null] = brsa.score(X=noise_new + inten, + design=design.design_task, + scan_onsets=scan_onsets) + assert score < score_null, ( + "Null model does not win over full model on data without signal") + + # Test fitting with lower rank, nuisance regressors and without GP prior + rank = n_C - 1 + n_nureg = 1 + brsa = BRSA(rank=rank, n_nureg=n_nureg, tol=2e-3, + n_iter=8, init_iter=4, auto_nuisance=True) + brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets) + # u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ + np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + + assert (not hasattr(brsa, 'bGP_') + and not hasattr(brsa, 'lGPspace_') + and not hasattr(brsa, 'lGPinten_') + ), ("the BRSA object should not have parameters of GP if GP is " + "not requested.") + # GP parameters are not set if not requested + assert brsa.beta0_.shape[0] == n_nureg + 1, 'Shape of beta0 incorrect' + p = scipy.stats.pearsonr(brsa.beta0_[0, :], inten)[1] + assert p < 0.01, ( + 'recovered beta0 does not correlate with the baseline of voxels.') + assert np.shape(brsa.L_) == ( + n_C, rank), 'Cholesky factor should have shape of (n_C, rank)' + + # Test fitting with GP over just spatial coordinates. + brsa = BRSA(GP_space=True, baseline_single=False, + tol=2e-3, n_iter=4, init_iter=4) + brsa.fit(X=Y, design=design.design_task, + scan_onsets=scan_onsets, coords=coords) + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ + np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + assert not hasattr(brsa, 'lGPinten_'), ( + "the BRSA object should not have parameters of lGPinten_ if only " + "smoothness in space is requested.") + # GP parameters are not set if not requested + + +def test_gradient(): + from brainiak.reprsimil.brsa import BRSA + import brainiak.utils.utils as utils + import numpy as np + import os.path + import numdifftools as nd + + np.random.seed(100) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + n_run = 4 + # concatenate it by 4 times, mimicking 4 runs of itenditcal timing + design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) + design.n_TR = design.n_TR * n_run + + # start simulating some data + n_V = 30 + n_C = np.size(design.design_task, axis=1) + n_T = design.n_TR + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot + + # generating noise + noise = np.zeros([n_T, n_V]) + noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ + np.random.randn(n_V) * noise_level + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0, 0] = 0.2 + ideal_cov[5:9, 5:9] = 0.6 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_level = 5.0 # test with high SNR + inten = np.random.randn(n_V) * 20.0 + + # parameters of Gaussian process to generate pseuso SNR + tau = 0.8 + smooth_width = 5.0 + inten_kernel = 1.0 + + coords = np.arange(0, n_V)[:, None] + + dist2 = np.square(coords - coords.T) + + inten_tile = np.tile(inten, [n_V, 1]) + inten_diff2 = (inten_tile - inten_tile.T)**2 + + K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / + inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 + + L = np.linalg.cholesky(K) + snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + sqrt_v = noise_level * snr + betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v + signal = np.dot(design.design_task, betas_simulated) + + # Adding noise to signal as data + Y = signal + noise + + scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) + + # Test fitting with GP prior. + brsa = BRSA(GP_space=True, GP_inten=True, rank=n_C) + + # Additionally, we test the generation of re-used terms. + X0 = np.ones(n_T)[:, None] + D, F, run_TRs, n_run_returned = brsa._prepare_DF( + n_T, scan_onsets=scan_onsets) + assert np.shape(D) == (n_T, n_T), 'D has wrong shape' + assert np.shape(F) == (n_T, n_T), 'F has wrong shape' + assert np.sum(D) == (n_T - n_run) * 2, 'D is initialized incorrectly.' + assert np.sum(F) == n_T - n_run * 2, 'F is initialized incorrectly.' + assert n_run_returned == n_run, ( + 'There is mistake in counting number of runs') + assert np.sum(run_TRs) == n_T, ( + 'The segmentation of the total experiment duration is wrong') + (XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX + ) = brsa._prepare_data_XY(design.design_task, Y, D, F) + (X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, X_base, + n_X0, idx_DC + ) = brsa._prepare_data_XYX0(design.design_task, Y, X0, + np.random.randn(n_T)[:, None], D, F, run_TRs, + no_DC=False) + assert (np.shape(XTY) == (n_C, n_V) + and np.shape(XTDY) == (n_C, n_V) + and np.shape(XTFY) == (n_C, n_V) + ), 'Dimension of XTY etc. returned from _prepare_data is wrong' + assert (np.ndim(YTY_diag) == 1 + and np.ndim(YTDY_diag) == 1 + and np.ndim(YTFY_diag) == 1 + ), ("Dimension of YTY_diag etc. returned from _prepare_data is " + "wrong") + assert (np.ndim(XTX) == 2 + and np.ndim(XTDX) == 2 + and np.ndim(XTFX) == 2 + ), 'Dimension of XTX etc. returned from _prepare_data is wrong' + assert (np.ndim(X0TX0) == 2 + and np.ndim(X0TDX0) == 2 + and np.ndim(X0TFX0) == 2 + ), 'Dimension of X0TX0 etc. returned from _prepare_data is wrong' + assert (np.ndim(XTX0) == 2 + and np.ndim(XTDX0) == 2 + and np.ndim(XTFX0) == 2 + ), 'Dimension of XTX0 etc. returned from _prepare_data is wrong' + assert (np.ndim(X0TY) == 2 + and np.ndim(X0TDY) == 2 + and np.ndim(X0TFY) == 2 + ), 'Dimension of X0TY etc. returned from _prepare_data is wrong' + assert (np.shape(X0) == (n_T, n_X0) + and np.shape(X_base) == (n_T, np.size(idx_DC)) + and np.max(idx_DC) < n_X0 + and np.size(idx_DC) + 1 == n_X0 + ), ("Dimension of X0 or X_base, or n_X0 or indices of DC " + "components are wrong.") + l_idx = np.tril_indices(n_C) + n_l = np.size(l_idx[0]) + + # Make sure all the fields are in the indices. + idx_param_sing, idx_param_fitU, idx_param_fitV = brsa._build_index_param( + n_l, n_V, 2) + assert 'Cholesky' in idx_param_sing and 'a1' in idx_param_sing, \ + 'The dictionary for parameter indexing misses some keys' + assert 'Cholesky' in idx_param_fitU and 'a1' in idx_param_fitU, \ + 'The dictionary for parameter indexing misses some keys' + assert 'log_SNR2' in idx_param_fitV and 'c_space' in idx_param_fitV \ + and 'c_inten' in idx_param_fitV and 'c_both' in idx_param_fitV, \ + 'The dictionary for parameter indexing misses some keys' + + # Initial parameters are correct parameters with some perturbation + param0_fitU = np.random.randn(n_l + n_V) * 0.1 + param0_fitV = np.random.randn(n_V + 1) * 0.1 + param0_sing = np.random.randn(n_l + 1) * 0.1 + param0_sing[idx_param_sing['a1']] += np.mean(np.tan(rho1 * np.pi / 2)) + param0_fitV[idx_param_fitV['log_SNR2']] += np.log(snr[:n_V - 1]) * 2 + param0_fitV[idx_param_fitV['c_space']] += np.log(smooth_width) * 2 + param0_fitV[idx_param_fitV['c_inten']] += np.log(inten_kernel) * 2 + + # test if the gradients are correct + # log likelihood and derivative of the _singpara function + + ll0, deriv0 = brsa._loglike_AR1_singpara(param0_sing, XTX, XTDX, XTFX, + YTY_diag, YTDY_diag, YTFY_diag, + XTY, XTDY, XTFY, X0TX0, X0TDX0, + X0TFX0, XTX0, XTDX0, XTFX0, X0TY, + X0TDY, X0TFY, l_idx, n_C, n_T, + n_V, n_run, n_X0, idx_param_sing) + # We test the gradient to the Cholesky factor + vec = np.zeros(np.size(param0_sing)) + vec[idx_param_sing['Cholesky'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, + XTDX0, XTFX0, X0TY, X0TDY, X0TFY, + l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_sing)[0], + param0_sing, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of singpara wrt Cholesky is incorrect') + + # We test the gradient to a1 + vec = np.zeros(np.size(param0_sing)) + vec[idx_param_sing['a1']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, + XTDX0, XTFX0, X0TY, X0TDY, X0TFY, + l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_sing)[0], + param0_sing, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), + rtol=1e-5), 'gradient of singpara wrt a1 is incorrect' + + # log likelihood and derivative of the fitU function. + ll0, deriv0 = brsa._loglike_AR1_diagV_fitU(param0_fitU, XTX, XTDX, XTFX, + YTY_diag, YTDY_diag, YTFY_diag, + XTY, XTDY, XTFY, X0TX0, X0TDX0, + X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, np.log(snr) + * 2, l_idx, n_C, n_T, n_V, + n_run, n_X0, idx_param_fitU, + n_C) + + # We test the gradient wrt the reparametrization of AR(1) coefficient of + # noise. + vec = np.zeros(np.size(param0_fitU)) + vec[idx_param_fitU['a1'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitU wrt to AR(1) coefficient incorrect') + + # We test if the numerical and analytical gradient wrt to the first + # element of Cholesky factor is correct + vec = np.zeros(np.size(param0_fitU)) + vec[idx_param_fitU['Cholesky'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitU wrt Cholesky factor incorrect') + + # Test on a random direction + vec = np.random.randn(np.size(param0_fitU)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), + rtol=1e-5), 'gradient of fitU incorrect' + + # We test the gradient of _fitV wrt to log(SNR^2) assuming no GP prior. + X0TAX0, XTAX0, X0TAY, X0TAX0_i, \ + XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL = \ + brsa._precompute_ar1_quad_forms(XTY, XTDY, XTFY, + YTY_diag, YTDY_diag, YTFY_diag, + XTX, XTDX, XTFX, + X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, + L_full, rho1, n_V, n_X0) + assert np.shape(XTAcorrX) == (n_V, n_C, n_C), ( + 'Dimension of XTAcorrX is wrong by _precompute_ar1_quad_forms()') + assert XTAcorrY.shape == XTY.shape, ( + 'Shape of XTAcorrY is wrong by _precompute_ar1_quad_forms()') + assert YTAcorrY.shape == YTY_diag.shape, ( + 'Shape of YTAcorrY is wrong by _precompute_ar1_quad_forms()') + assert np.shape(X0TAX0) == (n_V, n_X0, n_X0), ( + 'Dimension of X0TAX0 is wrong by _precompute_ar1_quad_forms()') + assert np.shape(XTAX0) == (n_V, n_C, n_X0), ( + 'Dimension of XTAX0 is wrong by _precompute_ar1_quad_forms()') + assert X0TAY.shape == X0TY.shape, ( + 'Shape of X0TAX0 is wrong by _precompute_ar1_quad_forms()') + assert np.all(np.isfinite(X0TAX0_i)), ( + 'Inverse of X0TAX0 includes NaN or Inf') + ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( + param0_fitV[idx_param_fitV['log_SNR2']], X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, + n_V, n_run, n_X0, idx_param_fitV, n_C, False, False) + vec = np.zeros(np.size(param0_fitV[idx_param_fitV['log_SNR2']])) + vec[idx_param_fitV['log_SNR2'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, + False, False)[0], + param0_fitV[idx_param_fitV['log_SNR2']], + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt log(SNR2) incorrect for model without GP') + + # We test the gradient of _fitV wrt to log(SNR^2) assuming GP prior. + ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( + param0_fitV, X0TAX0, XTAX0, X0TAY, X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_fitV, n_C, True, True, dist2, inten_diff2, 100, 100) + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['log_SNR2'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV srt log(SNR2) incorrect for model with GP') + + # We test the graident wrt spatial length scale parameter of GP prior + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['c_space']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt spatial length scale of GP incorrect') + + # We test the graident wrt intensity length scale parameter of GP prior + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['c_inten']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt intensity length scale of GP incorrect') + + # We test the graident on a random direction + vec = np.random.randn(np.size(param0_fitV)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV incorrect') + + +def test_nureg_determine(): + from brainiak.reprsimil.brsa import Ncomp_SVHT_MG_DLD_approx + import numpy as np + x = np.dot(np.random.randn(100, 5), np.random.randn(5, 40)) + \ + np.random.randn(100, 40) * 0.01 + ncomp = Ncomp_SVHT_MG_DLD_approx(x) + assert ncomp >= 3 and ncomp <= 8, ( + 'recovered number of components should be in a reasonable range') + + +def test_half_log_det(): + import numpy as np + from brainiak.reprsimil.brsa import BRSA + a = np.asarray([[1, 0.2], [0.2, 1]]) + brsa = BRSA() + half_log_det = np.log(np.linalg.det(a)) / 2 + assert np.isclose(half_log_det, brsa._half_log_det( + a)), 'half log determinant function is wrong' + + +def test_n_nureg(): + import brainiak.reprsimil.brsa + import numpy as np + # noise = np.random.randn(100,30) + noise = np.dot(np.random.randn(100, 8), np.random.randn( + 8, 30)) + np.random.randn(100, 30) * 0.01 + design = np.random.randn(100, 2) + s = brainiak.reprsimil.brsa.BRSA(n_iter=2) + s.fit(X=noise, design=design) + assert s.n_nureg_ > 2 and s.n_nureg_ < 16, ( + 'n_nureg_ estimation is wrong in BRSA') diff --git a/tests/reprsimil/test_gbrsa.py b/tests/reprsimil/test_gbrsa.py new file mode 100644 index 000000000..7038d35b4 --- /dev/null +++ b/tests/reprsimil/test_gbrsa.py @@ -0,0 +1,630 @@ +# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, +# Princeton University +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_can_instantiate(): + import brainiak.reprsimil.brsa + s = brainiak.reprsimil.brsa.GBRSA() + assert s, "Invalid GBRSA instance!" + + s = brainiak.reprsimil.brsa.GBRSA(n_iter=40, rank=4, auto_nuisance=False, + nureg_method='PCA', + baseline_single=False, logS_range=1.0, + SNR_bins=11, rho_bins=40, tol=2e-3, + optimizer='CG', random_state=0, + anneal_speed=20, SNR_prior='unif') + assert s, "Invalid GBRSA instance!" + + +def test_fit(): + from brainiak.reprsimil.brsa import GBRSA + import brainiak.utils.utils as utils + import scipy.stats + import numpy as np + import os.path + np.random.seed(10) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 1, 2, and 3 times, mimicking different length + # of experiments for different participants + n_run = [2, 1, 1] + design_mat = [None] * 3 + n_T = [None] * 3 + n_V = [40, 60, 60] + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + + # start simulating some data + n_C = np.size(design_mat[0], axis=1) + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = [None] * 3 + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = [None] * 3 + + # generating noise + noise = [None] * 3 + + # baseline + inten = [None] * 3 + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + n_T[i] = n_run[i] * design.n_TR + noise_level[i] = np.random.rand( + n_V[i]) * (noise_top - noise_bot) + noise_bot + # noise level is random. + rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot + + noise[i] = np.zeros([n_T[i], n_V[i]]) + noise[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ + np.random.randn(n_V[i]) * noise_level[i] + noise[i] = noise[i] + \ + np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) + inten[i] = np.random.rand(n_V[i]) * 20.0 + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_top = 5.0 # test with high SNR + snr_bot = 1.0 + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + + snr = [None] * 3 + signal = [None] * 3 + betas_simulated = [None] * 3 + scan_onsets = [None] * 3 + Y = [None] * 3 + for i in range(3): + snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot + sqrt_v = noise_level[i] * snr[i] + betas_simulated[i] = np.dot( + L_full, np.random.randn(n_C, n_V[i])) * sqrt_v + signal[i] = np.dot(design_mat[i], betas_simulated[i]) + + # Adding noise to signal as data + Y[i] = signal[i] + noise[i] + inten[i] + + scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) + + # Test fitting. + n_nureg = 2 + gbrsa = GBRSA(n_iter=15, auto_nuisance=True, logS_range=0.5, SNR_bins=11, + rho_bins=16, n_nureg=n_nureg, optimizer='L-BFGS-B') + + gbrsa.fit(X=Y, design=design_mat, scan_onsets=scan_onsets) + + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = gbrsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], + u_i[np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(gbrsa.nSNR_[0], snr[0])[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + p = scipy.stats.pearsonr(gbrsa.sigma_[1], noise_level[1])[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(gbrsa.rho_[2], rho1[2])[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + assert np.shape(gbrsa.X0_[1]) == (n_T[1], n_nureg + 1), "Wrong size of X0" + + Y_new = [None] * 3 + noise_new = [None] * 3 + for i in range(3): + noise_new[i] = np.zeros([n_T[i], n_V[i]]) + noise_new[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise_new[i][i_t, :] = noise_new[i][i_t - 1, :] * \ + rho1[i] + np.random.randn(n_V[i]) * noise_level[i] + + Y_new[i] = signal[i] + noise_new[i] + inten[i] + ts, ts0 = gbrsa.transform(Y_new, scan_onsets=scan_onsets) + [score, score_null] = gbrsa.score( + X=Y_new, design=design_mat, scan_onsets=scan_onsets) + [score_noise, score_null_noise] = gbrsa.score( + X=noise_new, design=design_mat, scan_onsets=scan_onsets) + for i in range(3): + assert np.shape(ts[i]) == (n_T[i], n_C) and np.shape( + ts0[i]) == (n_T[i], n_nureg + 1) + p = scipy.stats.pearsonr(ts[i][:, 0], design_mat[i][:, 0])[1] + assert p < 0.01, ( + "Recovered time series does not correlate with true time series!") + + assert score[i] > score_null[i], ( + "Full model does not win over null model on data containing " + "signal") + + assert score_noise[i] < score_null_noise[i], ( + "Null model does not win over full model on data without signal") + + [score, score_null] = gbrsa.score( + X=[None] * 3, design=design_mat, scan_onsets=scan_onsets) + assert score == [None] * 3 and score_null == [None] * \ + 3, "score did not return list of None when data is None" + ts, ts0 = gbrsa.transform(X=[None] * 3, scan_onsets=scan_onsets) + assert ts == [None] * 3 and ts0 == [None] * \ + 3, "transform did not return list of None when data is None" + + +def test_gradient(): + from brainiak.reprsimil.brsa import GBRSA + import brainiak.utils.utils as utils + import numpy as np + import os.path + import numdifftools as nd + + np.random.seed(100) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 1, 2, and 3 times, mimicking different length + # of experiments for different participants + n_run = [1, 2, 1] + design_mat = [None] * 3 + n_T = [None] * 3 + n_V = [30, 30, 20] + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + n_T[i] = n_run[i] * design.n_TR + + # start simulating some data + n_C = np.size(design_mat[0], axis=1) + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = [None] * 3 + for i in range(3): + noise_level[i] = np.random.rand( + n_V[i]) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = [None] * 3 + + # generating noise + noise = [None] * 3 + + # baseline + inten = [None] * 3 + for i in range(3): + rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot + noise[i] = np.zeros([n_T[i], n_V[i]]) + noise[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ + np.random.randn(n_V[i]) * noise_level[i] + noise[i] = noise[i] + \ + np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) + inten[i] = np.random.rand(n_V[i]) * 20.0 + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_top = 5.0 # test with high SNR + snr_bot = 1.0 + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + + snr = [None] * 3 + signal = [None] * 3 + betas_simulated = [None] * 3 + scan_onsets = [None] * 3 + Y = [None] * 3 + for i in range(3): + snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot + sqrt_v = noise_level[i] * snr[i] + betas_simulated[i] = np.dot( + L_full, np.random.randn(n_C, n_V[i])) * sqrt_v + signal[i] = np.dot(design_mat[i], betas_simulated[i]) + + # Adding noise to signal as data + Y[i] = signal[i] + noise[i] + inten[i] + + scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) + + # Get some initial fitting. + SNR_bins = 11 + rho_bins = 20 + gbrsa = GBRSA(n_iter=3, rank=n_C, SNR_bins=SNR_bins, + rho_bins=rho_bins, logS_range=0.5) + + n_grid = SNR_bins * rho_bins + half_log_det_X0TAX0 = [np.random.randn(n_grid) for i in range(3)] + log_weights = np.random.randn(n_grid) + log_fixed_terms = [np.random.randn(n_grid) for i in range(3)] + l_idx = np.tril_indices(n_C) + L_vec = np.random.randn(int(n_C * (n_C + 1) / 2)) + n_X0 = [2, 2, 2] + s = np.linspace(1, SNR_bins, n_grid) + a = np.linspace(0.5, 1, n_grid) + s2XTAcorrX = [None] * 3 + YTAcorrY_diag = [None] * 3 + sXTAcorrY = [None] * 3 + # The calculations below are quite arbitrary and do not conform + # to the model. They simply conform to the symmetry property and shape of + # the matrix indicated by the model + for i in range(3): + YTAcorrY_diag[i] = np.sum(Y[i] * Y[i], axis=0) * a[:, None] + s2XTAcorrX[i] = np.dot(design_mat[i].T, design_mat[ + i]) * s[:, None, None]**2 * a[:, None, None] + sXTAcorrY[i] = np.dot(design_mat[i].T, Y[i]) * \ + s[:, None, None] * a[:, None, None] + + # test if the gradients are correct + print(log_fixed_terms) + ll0, deriv0 = gbrsa._sum_loglike_marginalized(L_vec, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, + log_weights, log_fixed_terms, + l_idx, n_C, n_T, n_V, n_X0, + n_grid, rank=None) + # We test the gradient to the Cholesky factor + vec = np.random.randn(np.size(L_vec)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: gbrsa._sum_loglike_marginalized(x, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, + half_log_det_X0TAX0, + log_weights, log_fixed_terms, + l_idx, n_C, n_T, n_V, n_X0, + n_grid, rank=None)[0], + L_vec, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), 'gradient incorrect' + + +def test_SNR_grids(): + import brainiak.reprsimil.brsa + import numpy as np + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='unif', SNR_bins=10) + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.isclose(np.sum(SNR_weights), 1) + and np.isclose(np.std(SNR_weights[1:-1]), 0) + and np.all(SNR_weights > 0) + and np.isclose(np.min(SNR_grids), 0) + and np.all(SNR_grids >= 0) + and np.isclose(np.max(SNR_grids), 1) + ), 'SNR_weights or SNR_grids are incorrect for uniform prior' + assert np.isclose(np.ptp(np.diff(SNR_grids[1:-1])), 0), \ + 'SNR grids are not equally spaced for uniform prior' + assert (np.size(SNR_grids) == np.size(SNR_weights) + and np.size(SNR_grids) == 10 + ), ("size of SNR_grids or SNR_weights is not correct for uniform " + "prior") + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='lognorm', SNR_bins=35) + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids >= 0) + and np.isclose(np.sum(SNR_weights), 1) + and np.all(SNR_weights > 0) + and np.all(np.diff(SNR_grids) > 0) + ), 'SNR_grids or SNR_weights not correct for log normal prior' + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='exp') + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids >= 0) + and np.isclose(np.sum(SNR_weights), 1) + and np.all(SNR_weights > 0) + and np.all(np.diff(SNR_grids) > 0) + ), 'SNR_grids or SNR_weights not correct for exponential prior' + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='equal') + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids == 1) + and np.all(SNR_weights == 1) + and np.size(SNR_grids) == 1 + ), 'SNR_grids or SNR_weights not correct for equal prior' + + +def test_n_nureg(): + import brainiak.reprsimil.brsa + import numpy as np + noise = np.dot(np.random.randn(100, 8), np.random.randn( + 8, 30)) + np.random.randn(100, 30) * 0.001 + design = np.random.randn(100, 2) + s = brainiak.reprsimil.brsa.GBRSA(n_iter=2) + s.fit(X=noise, design=design) + assert s.n_nureg_[0] == 8, 'n_nureg_ estimation is wrong in GBRSA' + + +def test_grid_flatten_num_int(): + # Check for numeric integration of SNR, and correctly flattening 2-D grids + # to 1-D grid. + import brainiak.reprsimil.brsa + import brainiak.utils.utils as utils + import numpy as np + import scipy.special + n_V = 30 + n_T = 50 + n_C = 3 + design = np.random.randn(n_T, n_C) + U_simu = np.asarray([[1.0, 0.1, 0.0], [0.1, 1.0, 0.2], [0.0, 0.2, 1.0]]) + L_simu = np.linalg.cholesky(U_simu) + SNR = np.random.exponential(size=n_V) + beta = np.dot(L_simu, np.random.randn(n_C, n_V)) * SNR + noise = np.random.randn(n_T, n_V) + Y = np.dot(design, beta) + noise + X = design + X_base = None + scan_onsets = [0] + + s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, + SNR_prior='exp') + s.fit(X=[Y], design=[design]) + rank = n_C + l_idx, rank = s._chol_idx(n_C, rank) + L = np.zeros((n_C, rank)) + n_l = np.size(l_idx[0]) + current_vec_U_chlsk_l = s.random_state_.randn(n_l) * 10 + L[l_idx] = current_vec_U_chlsk_l + + # Now we change the grids for SNR and rho for testing. + s.SNR_bins = 2 + s.rho_bins = 2 + SNR_grids, SNR_weights = s._set_SNR_grids() + # rho_grids, rho_weights = s._set_rho_grids() + rho_grids = np.ones(2) * 0.1 + rho_weights = np.ones(2) / 2 + # We purposefully set all rhos to be equal to test flattening of + # grids. + n_grid = s.SNR_bins * s.rho_bins + + D, F, run_TRs, n_run = s._prepare_DF( + n_T, scan_onsets=scan_onsets) + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX \ + = s._prepare_data_XY(X, Y, D, F) + X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, \ + X_base, n_X0, idx_DC = s._prepare_data_XYX0( + X, Y, X_base, None, D, F, run_TRs, no_DC=False) + + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] + and half_log_det_X0TAX0[2] == half_log_det_X0TAX0[3] + and half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] + ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' + assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) + and np.array_equal(X0TAX0[2, :, :], X0TAX0[3, :, :]) + and np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0' + assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) + and np.array_equal(X0TAX0_i[2, :, :], X0TAX0_i[3, :, :]) + and np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0_i' + assert np.allclose( + np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), + np.eye(n_X0) + ), 'X0TAX0_i is not inverse of X0TAX0' + assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[1, :]) + and np.array_equal(YTAcorrY_diag[2, :], YTAcorrY_diag[3, :]) + and np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) + ), '_matrix_flattened_grid has mistake YTAcorrY_diag' + assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) + and np.array_equal(sXTAcorrY[2, :, :], sXTAcorrY[3, :, :]) + and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) + ), '_matrix_flattened_grid has mistake sXTAcorrY' + assert (np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) + and np.array_equal(X0TAY[2, :, :], X0TAY[3, :, :]) + and np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAY' + assert (np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) + and np.array_equal(XTAX0[2, :, :], XTAX0[3, :, :]) + and np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) + ), '_matrix_flattened_grid has mistake XTAX0' + + # Now we test the other way + rho_grids, rho_weights = s._set_rho_grids() + # rho_grids, rho_weights = s._set_rho_grids() + SNR_grids = np.ones(2) * 0.1 + SNR_weights = np.ones(2) / 2 + # We purposefully set all SNR to be equal to test flattening of + # grids. + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] + and half_log_det_X0TAX0[1] == half_log_det_X0TAX0[3] + and not half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] + ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' + assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) + and np.array_equal(X0TAX0[1, :, :], X0TAX0[3, :, :]) + and not np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0' + assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) + and np.array_equal(X0TAX0_i[1, :, :], X0TAX0_i[3, :, :]) + and not np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0_i' + assert np.allclose( + np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), + np.eye(n_X0) + ), 'X0TAX0_i is not inverse of X0TAX0' + assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) + and np.array_equal(YTAcorrY_diag[1, :], YTAcorrY_diag[3, :]) + and not np.array_equal(YTAcorrY_diag[0, :], + YTAcorrY_diag[1, :]) + ), '_matrix_flattened_grid has mistake YTAcorrY_diag' + assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) + and np.array_equal(sXTAcorrY[1, :, :], sXTAcorrY[3, :, :]) + and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) + ), '_matrix_flattened_grid has mistake sXTAcorrY' + assert (np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) + and np.array_equal(X0TAY[1, :, :], X0TAY[3, :, :]) + and not np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAY' + assert (np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) + and np.array_equal(XTAX0[1, :, :], XTAX0[3, :, :]) + and not np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) + ), '_matrix_flattened_grid has mistake XTAX0' + + # Now test the integration over SNR + s.SNR_bins = 50 + s.rho_bins = 1 + SNR_grids, SNR_weights = s._set_SNR_grids() + rho_grids, rho_weights = s._set_rho_grids() + n_grid = s.SNR_bins * s.rho_bins + + def setup_for_test(): + # This function will be re-used to set up the variables necessary for + # testing. + + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + log_weights = np.reshape( + np.log(SNR_weights[:, None]) + np.log(rho_weights), n_grid) + all_rho_grids = np.reshape(np.repeat( + rho_grids[None, :], s.SNR_bins, axis=0), n_grid) + log_fixed_terms = - (n_T - n_X0) / 2 * np.log(2 * np.pi) + n_run \ + / 2 * np.log(1 - all_rho_grids**2) + scipy.special.gammaln( + (n_T - n_X0 - 2) / 2) + (n_T - n_X0 - 2) / 2 * np.log(2) + return s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, \ + log_weights, log_fixed_terms + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, log_weights, + log_fixed_terms, l_idx, n_C, n_T, + n_V, n_X0, n_grid, rank=rank) + LL_total = - LL_total + # Now we re-calculate using scipy.integrate + s.SNR_bins = 100 + SNR_grids = np.linspace(0, 12, s.SNR_bins) + SNR_weights = np.exp(- SNR_grids) + SNR_weights = SNR_weights / np.sum(SNR_weights) + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, half_log_det_X0TAX0, + log_weights, log_fixed_terms, + n_C, n_T, n_V, n_X0, + n_grid, rank) + result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) + scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) + LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) + + tol = 1e-3 + assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ + 'Error of log likelihood calculation exceeds the tolerance' + + # Now test the log normal prior + s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, + SNR_prior='lognorm') + s.SNR_bins = 50 + s.rho_bins = 1 + SNR_grids, SNR_weights = s._set_SNR_grids() + rho_grids, rho_weights = s._set_rho_grids() + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, log_weights, + log_fixed_terms, l_idx, n_C, n_T, + n_V, n_X0, n_grid, rank=rank) + LL_total = - LL_total + # Now we re-calculate using scipy.integrate + s.SNR_bins = 400 + SNR_grids = np.linspace(1e-8, 20, s.SNR_bins) + log_SNR_weights = scipy.stats.lognorm.logpdf(SNR_grids, s=s.logS_range) + result_sum, max_value, result_exp = utils.sumexp_stable( + log_SNR_weights[:, None]) + SNR_weights = np.squeeze(result_exp / result_sum) + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, half_log_det_X0TAX0, + log_weights, log_fixed_terms, + n_C, n_T, n_V, n_X0, + n_grid, rank) + result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) + scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) + LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) + + tol = 1e-3 + assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ + 'Error of log likelihood calculation exceeds the tolerance' diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py new file mode 100644 index 000000000..7d472e6f5 --- /dev/null +++ b/tests/searchlight/test_searchlight.py @@ -0,0 +1,293 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import namedtuple + +import numpy as np +from mpi4py import MPI + +from brainiak.searchlight.searchlight import Searchlight +from brainiak.searchlight.searchlight import Diamond, Ball + +"""Distributed Searchlight Test +""" + + +def cube_sfn(l, msk, myrad, bcast_var): + if np.all(msk) and np.any(msk): + return 1.0 + return None + + +def test_searchlight_with_cube(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = True + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(cube_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def test_searchlight_with_cube_poolsize_1(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = True + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(cube_sfn, pool_size=1) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def diamond_sfn(l, msk, myrad, bcast_var): + assert not np.any(msk[~Diamond(3).mask_]) + if np.all(msk[Diamond(3).mask_]): + return 1.0 + return None + + +def test_searchlight_with_diamond(): + sl = Searchlight(sl_rad=3, shape=Diamond) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = Diamond(3).mask_ + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(diamond_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def ball_sfn(l, msk, myrad, bcast_var): + x, y, z = np.mgrid[-myrad:myrad+1, -myrad:myrad+1, -myrad:myrad+1] + correct_mask = np.square(x) + np.square(y) + np.square(z) <= myrad ** 2 + assert not np.any(msk[~Ball(3).mask_]) + if np.all(correct_mask == msk): + return 1.0 + return None + + +def test_searchlight_with_ball(): + sl = Searchlight(sl_rad=3, shape=Ball) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = Ball(3).mask_ + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(ball_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +MaskRadBcast = namedtuple("MaskRadBcast", "mask rad") + + +def test_instantiate(): + sl = Searchlight(sl_rad=5, max_blk_edge=10) + assert sl + + +def voxel_test_sfn(l, msk, myrad, bcast): + rad = bcast.rad + # Check each point + for subj in l: + for _tr in range(subj.shape[3]): + tr = subj[:, :, :, _tr] + midpt = tr[rad, rad, rad] + for d0 in range(tr.shape[0]): + for d1 in range(tr.shape[1]): + for d2 in range(tr.shape[2]): + assert np.array_equal(tr[d0, d1, d2] - midpt, + np.array([d0-rad, d1-rad, + d2-rad, 0])) + + # Determine midpoint + midpt = l[0][rad, rad, rad, 0] + midpt = (midpt[0], midpt[1], midpt[2]) + + for d0 in range(msk.shape[0]): + for d1 in range(msk.shape[1]): + for d2 in range(msk.shape[2]): + pt = (midpt[0] - rad + d0, midpt[1] - rad + d1, + midpt[2] - rad + d2) + assert bcast.mask[pt] == msk[d0, d1, d2] + + # Return midpoint + return midpt + + +def block_test_sfn(l, msk, myrad, bcast_var, extra_params): + outmat = l[0][:, :, :, 0] + outmat[~msk] = None + return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] + + +def test_correctness(): # noqa: C901 + def voxel_test(data, mask, max_blk_edge, rad): + + comm = MPI.COMM_WORLD + rank = comm.rank + + (dim0, dim1, dim2) = mask.shape + + # Initialize dataset with known pattern + for subj in data: + if subj is not None: + for tr in range(subj.shape[3]): + for d1 in range(dim0): + for d2 in range(dim1): + for d3 in range(dim2): + subj[d1, d2, d3, tr] = np.array( + [d1, d2, d3, tr]) + + sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) + sl.distribute(data, mask) + sl.broadcast(MaskRadBcast(mask, rad)) + global_outputs = sl.run_searchlight(voxel_test_sfn) + + if rank == 0: + for d0 in range(rad, global_outputs.shape[0]-rad): + for d1 in range(rad, global_outputs.shape[1]-rad): + for d2 in range(rad, global_outputs.shape[2]-rad): + if mask[d0, d1, d2]: + assert np.array_equal( + np.array(global_outputs[d0, d1, d2]), + np.array([d0, d1, d2])) + + def block_test(data, mask, max_blk_edge, rad): + + comm = MPI.COMM_WORLD + rank = comm.rank + + (dim0, dim1, dim2) = mask.shape + + # Initialize dataset with known pattern + for subj in data: + if subj is not None: + for tr in range(subj.shape[3]): + for d1 in range(dim0): + for d2 in range(dim1): + for d3 in range(dim2): + subj[d1, d2, d3, tr] = np.array( + [d1, d2, d3, tr]) + + sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) + sl.distribute(data, mask) + sl.broadcast(mask) + global_outputs = sl.run_block_function(block_test_sfn) + + if rank == 0: + for d0 in range(rad, global_outputs.shape[0]-rad): + for d1 in range(rad, global_outputs.shape[1]-rad): + for d2 in range(rad, global_outputs.shape[2]-rad): + if mask[d0, d1, d2]: + assert np.array_equal( + np.array(global_outputs[d0, d1, d2]), + np.array([d0, d1, d2, 0])) + + # Create dataset + def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + mask = np.random.choice([True, False], (dim0, dim1, dim2)) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + voxel_test(data, mask, max_blk_edge, rad) + block_test(data, mask, max_blk_edge, rad) + + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=1) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=1, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=0, dim1=10, dim2=8, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) diff --git a/tests/utils/example_design.1D b/tests/utils/example_design.1D new file mode 100755 index 000000000..7385edea6 --- /dev/null +++ b/tests/utils/example_design.1D @@ -0,0 +1,272 @@ +# + 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 + 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 + 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 + 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 + 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 + 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 + 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 + 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 + 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 + 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 + 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 + 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 + 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 + 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 + 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 + 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 + 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 + 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 + 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 + 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 + 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 + 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 + 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 + 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 + 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 + 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 + 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 + 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 + 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 + 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 + 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 + 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 + 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 + 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 + 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 + 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 + 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 + 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 + 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 + 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 + 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 + 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 + 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 + 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 + 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 + 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 + 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 + 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 + 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 + 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 + 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 + 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 + 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 + 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 + 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 + 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 + 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 + 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 + 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 + 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 + 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 + 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 + 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 + 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 + 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 + 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 + 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 + 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 + 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 + 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 + 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 + 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 + 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 + 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 + 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 + 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 + 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 + 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 + 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 + 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 + 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 + 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 + 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 + 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 + 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 + 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 + 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 + 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 + 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 + 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 + 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 + 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 + 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 + 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 + 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 + 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 + 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 + 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 + 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 + 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 + 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 + 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 + 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 + 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 + 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 + 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 + 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 + 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 + 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 + 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 + 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 + 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 + 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 + 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 + 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 + 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 + 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 + 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 + 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 + 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 + 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 + 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 + 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 + 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 + 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 + 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 + 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 + 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 + 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 + 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 + 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 + 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 + 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 + 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 + 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 + 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 + 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 + 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 + 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 + 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 + 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 + 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 + 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 + 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 + 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 + 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 + 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 + 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 + 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 + 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 + 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 + 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 + 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 + 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 + 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 + 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 + 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 + 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 + 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 + 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 + 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 + 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 + 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 + 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 + 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 + 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 + 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 + 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 + 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 + 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 + 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 + 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 + 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 + 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 + 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 + 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 + 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 + 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 + 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 + 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 + 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 + 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 + 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 + 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 + 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 + 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 +# + diff --git a/tests/utils/example_stimtime_1_AFNI.txt b/tests/utils/example_stimtime_1_AFNI.txt new file mode 100644 index 000000000..48200041d --- /dev/null +++ b/tests/utils/example_stimtime_1_AFNI.txt @@ -0,0 +1,2 @@ +5.2*2.0:2.0 40.0*4.0:1.5 +2.0*2.0:1.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_1_FSL.txt b/tests/utils/example_stimtime_1_FSL.txt new file mode 100644 index 000000000..ce14b1d55 --- /dev/null +++ b/tests/utils/example_stimtime_1_FSL.txt @@ -0,0 +1,3 @@ +5.2 2.0 2.0 +40.0 1.5 4.0 +50.0 1.0 2.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_2_AFNI.txt b/tests/utils/example_stimtime_2_AFNI.txt new file mode 100644 index 000000000..39d03d9ee --- /dev/null +++ b/tests/utils/example_stimtime_2_AFNI.txt @@ -0,0 +1 @@ +-10.0 diff --git a/tests/utils/example_stimtime_2_FSL.txt b/tests/utils/example_stimtime_2_FSL.txt new file mode 100644 index 000000000..e60beb2a6 --- /dev/null +++ b/tests/utils/example_stimtime_2_FSL.txt @@ -0,0 +1,3 @@ +5.2 2.0 +40.0 1.5 2.0 +50.0 \ No newline at end of file diff --git a/tests/utils/test_fmrisim.py b/tests/utils/test_fmrisim.py new file mode 100644 index 000000000..7c442661a --- /dev/null +++ b/tests/utils/test_fmrisim.py @@ -0,0 +1,858 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""fMRI Simulator test script + +Test script for generating a run of a participant's data. + + Authors: Cameron Ellis (Princeton) 2016 +""" +import numpy as np +import math +from brainiak.utils import fmrisim as sim +import pytest +from itertools import product + + +def test_generate_signal(): + + # Inputs for generate_signal + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [3] + feature_type = ['cube'] + feature_coordinates = np.array([[5, 5, 5]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + assert np.all(volume.shape == dimensions), "Check signal shape" + assert np.max(volume) == signal_magnitude, "Check signal magnitude" + assert np.sum(volume > 0) == math.pow(feature_size[0], 3), ( + "Check feature size") + assert volume[5, 5, 5] == signal_magnitude, "Check signal location" + assert volume[5, 5, 1] == 0, "Check noise location" + + feature_coordinates = np.array( + [[5, 5, 5], [3, 3, 3], [7, 7, 7]]) + + # Check feature size is correct + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=['loop', 'cavity', 'sphere'], + feature_size=[3], + signal_magnitude=signal_magnitude) + assert volume[5, 5, 5] == 0, "Loop is empty" + assert volume[3, 3, 3] == 0, "Cavity is empty" + assert volume[7, 7, 7] != 0, "Sphere is not empty" + + # Check feature size manipulation + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=['loop', 'cavity', 'sphere'], + feature_size=[1], + signal_magnitude=signal_magnitude) + assert volume[5, 6, 6] == 0, "Loop is too big" + assert volume[3, 5, 5] == 0, "Cavity is too big" + assert volume[7, 9, 9] == 0, "Sphere is too big" + + # Check that out of bounds feature coordinates are corrected + feature_coordinates = np.array([0, 2, dimensions[2]]) + x, y, z = sim._insert_idxs(feature_coordinates, feature_size[0], + dimensions) + assert x[1] - x[0] == 2, "x min not corrected" + assert y[1] - y[0] == 3, "y was corrected when it shouldn't be" + assert z[1] - z[0] == 1, "z max not corrected" + + # Check that signal patterns are created + feature_coordinates = np.array([[5, 5, 5]]) + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + signal_constant=0, + ) + assert volume[4:7, 4:7, 4:7].std() > 0, "Signal is constant" + + +def test_generate_stimfunction(): + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 100 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + assert stimfunction.shape[0] == duration * 100, "stimfunc incorrect length" + eventNumber = np.sum(event_durations * len(onsets)) * 100 + assert np.sum(stimfunction) == eventNumber, "Event number" + + # Create the signal function + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + stim_dur = stimfunction.shape[0] / (tr_duration * 100) + assert signal_function.shape[0] == stim_dur, "The length did not change" + + # Test + onsets = [0] + tr_duration = 1 + event_durations = [1] + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + max_response = np.where(signal_function != 0)[0].max() + assert 25 < max_response <= 30, "HRF has the incorrect length" + assert np.sum(signal_function < 0) > 0, "No values below zero" + + # Export a stimfunction + sim.export_3_column(stimfunction, + 'temp.txt', + ) + + # Load in the stimfunction + stimfunc_new = sim.generate_stimfunction(onsets=None, + event_durations=None, + total_time=duration, + timing_file='temp.txt', + ) + + assert np.all(stimfunc_new == stimfunction), "Export/import failed" + + # Break the timing precision of the generation + stimfunc_new = sim.generate_stimfunction(onsets=None, + event_durations=None, + total_time=duration, + timing_file='temp.txt', + temporal_resolution=0.5, + ) + + assert stimfunc_new.sum() == 0, "Temporal resolution not working right" + + # Set the duration to be too short so you should get an error + onsets = [10, 30, 50, 70, 90] + event_durations = [5] + with pytest.raises(ValueError): + sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=89, + ) + + # Clip the event offset + stimfunc_new = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=95, + ) + assert stimfunc_new[-1] == 1, 'Event offset was not clipped' + + # Test exporting a group of participants to an epoch file + cond_a = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=110, + ) + + cond_b = sim.generate_stimfunction(onsets=[x + 5 for x in onsets], + event_durations=event_durations, + total_time=110, + ) + + stimfunction_group = [np.hstack((cond_a, cond_b))] * 2 + sim.export_epoch_file(stimfunction_group, + 'temp.txt', + tr_duration, + ) + + # Check that convolve throws a warning when the shape is wrong + sim.convolve_hrf(stimfunction=np.hstack((cond_a, cond_b)).T, + tr_duration=tr_duration, + temporal_resolution=1, + ) + + +def test_apply_signal(): + + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[5, 5, 5]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 100 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + # Check that you can compute signal change appropriately + # Preset a bunch of things + stimfunction_tr = stimfunction[::int(tr_duration * 100)] + mask, template = sim.mask_brain(dimensions, mask_self=False) + noise_dict = sim._noise_dict_update({}) + noise = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=noise_dict, + iterations=[0, 0] + ) + coords = feature_coordinates[0] + noise_function_a = noise[coords[0], coords[1], coords[2], :] + noise_function_a = noise_function_a.reshape(duration // tr_duration, 1) + + noise_function_b = noise[coords[0] + 1, coords[1], coords[2], :] + noise_function_b = noise_function_b.reshape(duration // tr_duration, 1) + + # Create the calibrated signal with PSC + method = 'PSC' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + sig_b = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [1.0], + method, + ) + + assert sig_b.max() / sig_a.max() == 2, 'PSC modulation failed' + + # Create the calibrated signal with SFNR + method = 'SFNR' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a / (noise_function_a.mean() / noise_dict['sfnr']) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b / (noise_function_b.mean() / noise_dict['sfnr']) + + assert scaled_b.max() / scaled_a.max() == 2, 'SFNR modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-SD + method = 'CNR_Amp/Noise-SD' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a / noise_function_a.std() + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b / noise_function_b.std() + + assert scaled_b.max() / scaled_a.max() == 2, 'CNR_Amp modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-Var_dB + method = 'CNR_Amp2/Noise-Var_dB' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = np.log(sig_a.max() / noise_function_a.std()) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = np.log(sig_b.max() / noise_function_b.std()) + + assert np.round(scaled_b / scaled_a) == 2, 'CNR_Amp dB modulation failed' + + # Create the calibrated signal with CNR_Signal-SD/Noise-SD + method = 'CNR_Signal-SD/Noise-SD' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a.std() / noise_function_a.std() + sig_b = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b.std() / noise_function_a.std() + + assert (scaled_b / scaled_a) == 2, 'CNR signal modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-Var_dB + method = 'CNR_Signal-Var/Noise-Var_dB' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + + scaled_a = np.log(sig_a.std() / noise_function_a.std()) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = np.log(sig_b.std() / noise_function_b.std()) + + assert np.round(scaled_b / scaled_a) == 2, 'CNR signal dB modulation ' \ + 'failed' + + # Convolve the HRF with the stimulus sequence + signal = sim.apply_signal(signal_function=signal_function, + volume_signal=volume, + ) + + assert signal.shape == (dimensions[0], dimensions[1], dimensions[2], + duration / tr_duration), "The output is the " \ + "wrong size" + + signal = sim.apply_signal(signal_function=stimfunction, + volume_signal=volume, + ) + + assert np.any(signal == signal_magnitude), "The stimfunction is not binary" + + # Check that there is an error if the number of signal voxels doesn't + # match the number of non zero brain voxels + with pytest.raises(IndexError): + sig_vox = (volume > 0).sum() + vox_pattern = np.tile(stimfunction, (1, sig_vox - 1)) + sim.apply_signal(signal_function=vox_pattern, + volume_signal=volume, + ) + + +def test_generate_noise(): + + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[5, 5, 5]]) + signal_magnitude = [1] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 200 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + # Convolve the HRF with the stimulus sequence + signal = sim.apply_signal(signal_function=signal_function, + volume_signal=volume, + ) + + # Generate the mask of the signal + mask, template = sim.mask_brain(signal, + mask_self=None) + + assert min(mask[mask > 0]) > 0.1, "Mask thresholding did not work" + assert len(np.unique(template) > 2), "Template creation did not work" + + stimfunction_tr = stimfunction[::int(tr_duration * 100)] + + # Create the noise volumes (using the default parameters) + noise = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + iterations=[1, 0], + ) + + assert signal.shape == noise.shape, "The dimensions of signal and noise " \ + "the same" + + noise_high = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={'sfnr': 50, 'snr': 25}, + iterations=[1, 0], + ) + + noise_low = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={'sfnr': 100, 'snr': 25}, + iterations=[1, 0], + ) + + system_high = np.std(noise_high[mask > 0], 1).mean() + system_low = np.std(noise_low[mask > 0], 1).mean() + + assert system_low < system_high, "SFNR noise could not be manipulated" + + # Check that you check for the appropriate template values + with pytest.raises(ValueError): + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template * 2, + mask=mask, + noise_dict={}, + ) + + # Check that iterations does what it should + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={}, + iterations=[0, 0], + ) + + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={}, + iterations=None, + ) + + # Test drift noise + trs = 1000 + period = 100 + drift = sim._generate_noise_temporal_drift(trs, + tr_duration, + 'sine', + period, + ) + + # Check that the max frequency is the appropriate frequency + power = abs(np.fft.fft(drift))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs + period_freq = np.where(freq == 1 / (period // tr_duration)) + max_freq = np.argmax(power) + + assert period_freq == max_freq, 'Max frequency is not where it should be' + + # Do the same but now with cosine basis functions, answer should be close + drift = sim._generate_noise_temporal_drift(trs, + tr_duration, + 'discrete_cos', + period, + ) + + # Check that the appropriate frequency is peaky (may not be the max) + power = abs(np.fft.fft(drift))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs + period_freq = np.where(freq == 1 / (period // tr_duration))[0][0] + + assert power[period_freq] > power[period_freq + 1], 'Power is low' + assert power[period_freq] > power[period_freq - 1], 'Power is low' + + # Check it gives a warning if the duration is too short + drift = sim._generate_noise_temporal_drift(50, + tr_duration, + 'discrete_cos', + period, + ) + + # Test physiological noise (using unrealistic parameters so that it's easy) + timepoints = list(np.linspace(0, (trs - 1) * tr_duration, trs)) + resp_freq = 0.2 + heart_freq = 1.17 + phys = sim._generate_noise_temporal_phys(timepoints, + resp_freq, + heart_freq, + ) + + # Check that the max frequency is the appropriate frequency + power = abs(np.fft.fft(phys))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / (trs * tr_duration) + peaks = (power > (power.mean() + power.std())) # Where are the peaks + peak_freqs = freq[peaks] + + assert np.any(resp_freq == peak_freqs), 'Resp frequency not found' + assert len(peak_freqs) == 2, 'Two peaks not found' + + # Test task noise + sim._generate_noise_temporal_task(stimfunction_tr, + motion_noise='gaussian', + ) + sim._generate_noise_temporal_task(stimfunction_tr, + motion_noise='rician', + ) + + # Test ARMA noise + with pytest.raises(ValueError): + noise_dict = {'fwhm': 4, 'auto_reg_rho': [1], 'ma_rho': [1, 1]} + sim._generate_noise_temporal_autoregression(stimfunction_tr, + noise_dict, + dimensions, + mask, + ) + + # Generate spatial noise + vol = sim._generate_noise_spatial(np.array([10, 10, 10, trs])) + assert len(vol.shape) == 3, 'Volume was not reshaped to ignore TRs' + + # Switch some of the noise types on + noise_dict = dict(physiological_sigma=1, drift_sigma=1, task_sigma=1, + auto_reg_sigma=0) + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=noise_dict, + iterations=[0, 0], + ) + + +def test_generate_noise_spatial(): + + # Set up the inputs + dimensions = np.array([10, 5, 10]) + mask = np.ones(dimensions) + vol = sim._generate_noise_spatial(dimensions, mask) + + # Run the analysis from _calc_FHWM but for th elast step of aggregating + # across dimensions + v_count = 0 + v_sum = 0 + v_sq = 0 + + d_sum = [0.0, 0.0, 0.0] + d_sq = [0.0, 0.0, 0.0] + d_count = [0, 0, 0] + + # Pull out all the voxel coordinates + coordinates = list(product(range(dimensions[0]), + range(dimensions[1]), + range(dimensions[2]))) + + # Find the sum of squared error for the non-masked voxels in the brain + for i in list(range(len(coordinates))): + + # Pull out this coordinate + x, y, z = coordinates[i] + + # Is this within the mask? + if mask[x, y, z] > 0: + + # Find the the volume sum and squared values + v_count += 1 + v_sum += vol[x, y, z] + v_sq += vol[x, y, z] ** 2 + + # Get the volume variance + v_var = (v_sq - ((v_sum ** 2) / v_count)) / (v_count - 1) + + for i in list(range(len(coordinates))): + + # Pull out this coordinate + x, y, z = coordinates[i] + + # Is this within the mask? + if mask[x, y, z] > 0: + # For each xyz dimension calculate the squared + # difference of this voxel and the next + + in_range = (x < dimensions[0] - 1) + in_mask = in_range and (mask[x + 1, y, z] > 0) + included = in_mask and (~np.isnan(vol[x + 1, y, z])) + if included: + d_sum[0] += vol[x, y, z] - vol[x + 1, y, z] + d_sq[0] += (vol[x, y, z] - vol[x + 1, y, z]) ** 2 + d_count[0] += 1 + + in_range = (y < dimensions[1] - 1) + in_mask = in_range and (mask[x, y + 1, z] > 0) + included = in_mask and (~np.isnan(vol[x, y + 1, z])) + if included: + d_sum[1] += vol[x, y, z] - vol[x, y + 1, z] + d_sq[1] += (vol[x, y, z] - vol[x, y + 1, z]) ** 2 + d_count[1] += 1 + + in_range = (z < dimensions[2] - 1) + in_mask = in_range and (mask[x, y, z + 1] > 0) + included = in_mask and (~np.isnan(vol[x, y, z + 1])) + if included: + d_sum[2] += vol[x, y, z] - vol[x, y, z + 1] + d_sq[2] += (vol[x, y, z] - vol[x, y, z + 1]) ** 2 + d_count[2] += 1 + + # Find the variance + d_var = np.divide((d_sq - np.divide(np.power(d_sum, 2), + d_count)), (np.add(d_count, -1))) + + o_var = np.divide(-1, (4 * np.log(1 - (0.5 * d_var / v_var)))) + fwhm3 = np.sqrt(o_var) * 2 * np.sqrt(2 * np.log(2)) + + # Calculate the proportion of std relative to the mean + std_proportion = np.nanstd(fwhm3) / np.nanmean(fwhm3) + print(fwhm3) + assert std_proportion < 0.25, 'Variance is inconsistent across dim' + + +def test_mask_brain(): + + # Inputs for generate_signal + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[4, 4, 4]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Mask the volume to be the same shape as a brain + mask, _ = sim.mask_brain(dimensions, mask_self=None,) + brain = volume * mask + + assert np.sum(brain != 0) == np.sum(volume != 0), "Masking did not work" + assert brain[0, 0, 0] == 0, "Masking did not work" + assert brain[4, 4, 4] != 0, "Masking did not work" + + feature_coordinates = np.array( + [[1, 1, 1]]) + + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Mask the volume to be the same shape as a brain + mask, _ = sim.mask_brain(dimensions, mask_self=None, ) + brain = volume * mask + + assert np.sum(brain != 0) < np.sum(volume != 0), "Masking did not work" + + # Test that you can load the default + dimensions = np.array([100, 100, 100]) + mask, template = sim.mask_brain(dimensions, mask_self=False) + + assert mask[20, 80, 50] == 0, 'Masking didn''t work' + assert mask[25, 80, 50] == 1, 'Masking didn''t work' + assert int(template[25, 80, 50] * 100) == 57, 'Template not correct' + + # Check that you can mask self + mask_self, template_self = sim.mask_brain(template, mask_self=True) + + assert (template_self - template).sum() < 1e2, 'Mask self error' + assert (mask_self - mask).sum() == 0, 'Mask self error' + + +def test_calc_noise(): + + # Inputs for functions + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 200 + temporal_res = 100 + tr_number = int(np.floor(duration / tr_duration)) + dimensions_tr = np.array([10, 10, 10, tr_number]) + + # Preset the noise dict + nd_orig = sim._noise_dict_update({}) + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + temporal_resolution=temporal_res, + ) + + # Mask the volume to be the same shape as a brain + mask, template = sim.mask_brain(dimensions_tr, mask_self=None) + stimfunction_tr = stimfunction[::int(tr_duration * temporal_res)] + + nd_orig['matched'] = 0 + noise = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + ) + + # Check the spatial noise match + nd_orig['matched'] = 1 + noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + iterations=[50, 0] + ) + + # Calculate the noise parameters from this newly generated volume + nd_new = sim.calc_noise(noise, mask, template) + nd_matched = sim.calc_noise(noise_matched, mask, template) + + # Check the values are reasonable" + assert nd_new['snr'] > 0, 'snr out of range' + assert nd_new['sfnr'] > 0, 'sfnr out of range' + assert nd_new['auto_reg_rho'][0] > 0, 'ar out of range' + + # Check that the dilation increases SNR + no_dilation_snr = sim._calc_snr(noise_matched, + mask, + dilation=0, + reference_tr=tr_duration, + ) + + assert nd_new['snr'] > no_dilation_snr, "Dilation did not increase SNR" + + # Check that template size is in bounds + with pytest.raises(ValueError): + sim.calc_noise(noise, mask, template * 2) + + # Check that Mask is set is checked + with pytest.raises(ValueError): + sim.calc_noise(noise, None, template) + + # Check that it can deal with missing noise parameters + temp_nd = sim.calc_noise(noise, mask, template, noise_dict={}) + assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' + + temp_nd = sim.calc_noise(noise, mask, template, noise_dict=None) + assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' + + # Check that the fitting worked + snr_diff = abs(nd_orig['snr'] - nd_new['snr']) + snr_diff_match = abs(nd_orig['snr'] - nd_matched['snr']) + assert snr_diff > snr_diff_match, 'snr fit incorrectly' + + # Test that you can generate rician and exponential noise + sim._generate_noise_system(dimensions_tr, + 1, + 1, + spatial_noise_type='exponential', + temporal_noise_type='rician', + ) + + # Check the temporal noise match + nd_orig['matched'] = 1 + noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + iterations=[0, 50] + ) + + nd_matched = sim.calc_noise(noise_matched, mask, template) + + sfnr_diff = abs(nd_orig['sfnr'] - nd_new['sfnr']) + sfnr_diff_match = abs(nd_orig['sfnr'] - nd_matched['sfnr']) + assert sfnr_diff > sfnr_diff_match, 'sfnr fit incorrectly' + + ar1_diff = abs(nd_orig['auto_reg_rho'][0] - nd_new['auto_reg_rho'][0]) + ar1_diff_match = abs(nd_orig['auto_reg_rho'][0] - nd_matched[ + 'auto_reg_rho'][0]) + assert ar1_diff > ar1_diff_match, 'AR1 fit incorrectly' + + # Check that you can calculate ARMA for a single voxel + vox = noise[5, 5, 5, :] + arma = sim._calc_ARMA_noise(vox, + None, + sample_num=2, + ) + assert len(arma) == 2, "Two outputs not given by ARMA" diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py new file mode 100644 index 000000000..f37c51a7e --- /dev/null +++ b/tests/utils/test_utils.py @@ -0,0 +1,396 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + + +def test_tri_sym_convert(): + from brainiak.utils.utils import from_tri_2_sym, from_sym_2_tri + import numpy as np + + sym = np.random.rand(3, 3) + tri = from_sym_2_tri(sym) + assert tri.shape[0] == 6,\ + "from_sym_2_tri returned wrong result!" + sym1 = from_tri_2_sym(tri, 3) + assert sym1.shape[0] == sym1.shape[1],\ + "from_tri_2_sym returned wrong shape!" + tri1 = from_sym_2_tri(sym1) + assert np.array_equiv(tri, tri1),\ + "from_sym_2_tri returned wrong result!" + + +def test_sumexp(): + from brainiak.utils.utils import sumexp_stable + import numpy as np + + data = np.array([[1, 1], [0, 1]]) + sums, maxs, exps = sumexp_stable(data) + assert sums.size == data.shape[1], ( + "Invalid sum(exp(v)) computation (wrong # samples in sums)") + assert exps.shape[0] == data.shape[0], ( + "Invalid exp(v) computation (wrong # features)") + assert exps.shape[1] == data.shape[1], ( + "Invalid exp(v) computation (wrong # samples)") + assert maxs.size == data.shape[1], ( + "Invalid max computation (wrong # samples in maxs)") + + +def test_concatenate_not_none(): + from brainiak.utils.utils import concatenate_not_none + import numpy as np + arrays = [None] * 5 + + arrays[1] = np.array([0, 1, 2]) + arrays[3] = np.array([3, 4]) + + r = concatenate_not_none(arrays, axis=0) + + assert np.all(np.arange(5) == r), ( + "Invalid concatenation of a list of arrays") + + +def test_cov2corr(): + from brainiak.utils.utils import cov2corr + import numpy as np + cov = np.array([[4, 3, 0], [3, 9, 0], [0, 0, 1]]) + corr = cov2corr(cov) + assert np.allclose(corr, + np.array([[1, 0.5, 0], [0.5, 1, 0], [0, 0, 1]])), ( + "Converting from covariance matrix to correlation incorrect") + + +def test_ReadDesign(): + from brainiak.utils.utils import ReadDesign + import numpy as np + import os.path + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + design = ReadDesign(fname=file_path, include_orth=False, + include_pols=False) + assert design, 'Failed to read design matrix' + assert design.reg_nuisance is None, \ + 'Nuiance regressor is not None when include_orth and include_pols are'\ + ' both set to False' + read = ReadDesign() + assert read, 'Failed to initialize an instance of the class' + design = ReadDesign(fname=file_path, include_orth=True, include_pols=True) + assert np.size(design.cols_nuisance) == 10, \ + 'Mistake in counting the number of nuiance regressors' + assert np.size(design.cols_task) == 17, \ + 'Mistake in counting the number of task conditions' + assert (np.shape(design.reg_nuisance)[0] + == np.shape(design.design_task)[0] + ), 'The number of time points in nuiance regressor does not match'\ + ' that of task response' + + +def test_gen_design(): + from brainiak.utils.utils import gen_design + import numpy as np + import os.path + files = {'FSL1': 'example_stimtime_1_FSL.txt', + 'FSL2': 'example_stimtime_2_FSL.txt', + 'AFNI1': 'example_stimtime_1_AFNI.txt', + 'AFNI2': 'example_stimtime_2_AFNI.txt'} + for key in files.keys(): + files[key] = os.path.join(os.path.dirname(__file__), files[key]) + design1 = gen_design(stimtime_files=files['FSL1'], scan_duration=[48, 20], + TR=2, style='FSL') + assert design1.shape == (34, 1), 'Returned design matrix has wrong shape' + assert design1[24] == 0, ( + "gen_design should generated design matrix for each run separately " + "and concatenate them.") + design2 = gen_design(stimtime_files=[files['FSL1'], files['FSL2']], + scan_duration=[48, 20], TR=2, style='FSL') + assert design2.shape == (34, 2), 'Returned design matrix has wrong shape' + design3 = gen_design(stimtime_files=files['FSL1'], scan_duration=68, TR=2, + style='FSL') + assert design3[24] != 0, ( + 'design matrix should be non-zero 8 seconds after an event onset.') + design4 = gen_design(stimtime_files=[files['FSL2']], + scan_duration=[48, 20], TR=2, style='FSL') + assert np.all(np.isclose(design1 * 0.5, design4)), ( + 'gen_design does not treat missing values correctly') + design5 = gen_design(stimtime_files=[files['FSL2']], + scan_duration=[48, 20], TR=1) + assert (np.abs(design4 - design5[::2])).mean() < 0.1, ( + 'design matrices sampled at different frequency do not match' + ' at corresponding time points') + design6 = gen_design(stimtime_files=[files['AFNI1']], + scan_duration=[48, 20], TR=2, style='AFNI') + assert np.all(np.isclose(design1, design6)), ( + 'design matrices generated from AFNI style and FSL style do not match') + design7 = gen_design(stimtime_files=[files['AFNI2']], + scan_duration=[48], TR=2, style='AFNI') + assert np.all(design7 == 0.0), ( + 'A negative stimulus onset of AFNI style should result in an all-zero' + + ' design matrix') + + +def test_center_mass_exp(): + from brainiak.utils.utils import center_mass_exp + import numpy as np + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp([1, 2]) + assert ('interval must be a tuple' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((1, 2, 3)) + assert ('interval must be length two' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((-2, -1)) + assert ('interval_left must be non-negative' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((-2, 3)) + assert ('interval_left must be non-negative' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((3, 3)) + assert ('interval_right must be bigger than interval_left' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((1, 2), -1) + assert ('scale must be positive' + in str(excinfo.value)) + + result = center_mass_exp((0, np.inf), 2.0) + assert np.isclose(result, 2.0), 'center of mass '\ + 'incorrect for the whole distribution' + result = center_mass_exp((1.0, 1.0+2e-10)) + assert np.isclose(result, 1.0+1e-10), 'for a small '\ + 'enough interval, the center of mass should be '\ + 'close to its mid-point' + + +def test_p_from_null(): + import numpy as np + from brainiak.utils.utils import p_from_null + + # Create random null and observed value in tail + null = np.random.randn(10000) + observed = np.ceil(np.percentile(null, 97.5) * 1000) / 1000 + + # Check that we catch improper side + with pytest.raises(ValueError): + _ = p_from_null(observed, null, side='wrong') + + # Check two-tailed p-value for observed + p_ts = p_from_null(observed, null) + assert np.isclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for observed + p_right = p_from_null(observed, null, side='right') + assert np.isclose(p_right, 0.025, atol=1e-02) + assert np.isclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for observed + p_left = p_from_null(observed, null, side='left') + assert np.isclose(p_left, 0.975, atol=1e-02) + assert np.isclose(1 - p_left, p_right, atol=1e-02) + assert np.isclose(1 - p_left, p_ts / 2, atol=1e-02) + + # Check 2-dimensional input (i.e., samples by voxels) + null = np.random.randn(10000, 3) + observed = np.ceil(np.percentile(null, 97.5, axis=0) * 1000) / 1000 + + # Check two-tailed p-value for observed + p_ts = p_from_null(observed, null, axis=0) + assert np.allclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for observed + p_right = p_from_null(observed, null, side='right', axis=0) + assert np.allclose(p_right, 0.025, atol=1e-02) + assert np.allclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for observed + p_left = p_from_null(observed, null, side='left', axis=0) + assert np.allclose(p_left, 0.975, atol=1e-02) + assert np.allclose(1 - p_left, p_right, atol=1e-02) + assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) + + # Check for exact test + p_ts = p_from_null(observed, null, exact=True, axis=0) + assert np.allclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for exact + p_right = p_from_null(observed, null, side='right', + exact=True, axis=0) + assert np.allclose(p_right, 0.025, atol=1e-02) + assert np.allclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for exact + p_left = p_from_null(observed, null, side='left', + exact=True, axis=0) + assert np.allclose(p_left, 0.975, atol=1e-02) + assert np.allclose(1 - p_left, p_right, atol=1e-02) + assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) + + +def test_phase_randomize(): + import numpy as np + from scipy.fftpack import fft + from scipy.stats import pearsonr + from brainiak.utils.utils import phase_randomize + + data = np.repeat(np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], + 30, axis=1), + 20, axis=2) + assert np.array_equal(data[..., 0], data[..., 1]) + + # Phase-randomize data across subjects (same across voxels) + shifted_data = phase_randomize(data, voxelwise=False, random_state=1) + assert shifted_data.shape == data.shape + assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) + assert not np.array_equal(shifted_data[..., 0], data[..., 0]) + + # Check that uneven n_TRs doesn't explode + _ = phase_randomize(data[:-1, ...]) + + # Check that random_state returns same shifts + shifted_data_ = phase_randomize(data, voxelwise=False, random_state=1) + assert np.array_equal(shifted_data, shifted_data_) + + shifted_data_ = phase_randomize(data, voxelwise=False, random_state=2) + assert not np.array_equal(shifted_data, shifted_data_) + + # Phase-randomize subjects and voxels + shifted_data = phase_randomize(data, voxelwise=True, random_state=1) + assert shifted_data.shape == data.shape + assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) + assert not np.array_equal(shifted_data[..., 0], data[..., 0]) + assert not np.array_equal(shifted_data[:, 0, 0], shifted_data[:, 1, 0]) + + # Try with 2-dimensional input + shifted_data = phase_randomize(data[..., 0], + voxelwise=True, + random_state=1) + assert shifted_data.ndim == 2 + assert not np.array_equal(shifted_data[:, 0], shifted_data[:, 1]) + + # Create correlated noisy data + corr_data = np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], + 2, axis=2) + np.random.randn(60, 1, 2) + + # Get correlation and frequency domain for data + corr_r = pearsonr(corr_data[:, 0, 0], + corr_data[:, 0, 1])[0] + corr_freq = fft(corr_data, axis=0) + + # Phase-randomize time series and get correlation/frequency + shifted_data = phase_randomize(corr_data) + shifted_r = pearsonr(shifted_data[:, 0, 0], + shifted_data[:, 0, 1])[0] + shifted_freq = fft(shifted_data, axis=0) + + # Check that phase-randomization reduces correlation + assert np.abs(shifted_r) < np.abs(corr_r) + + # Check that amplitude spectrum is preserved + assert np.allclose(np.abs(shifted_freq), np.abs(corr_freq)) + + +def test_check_timeseries_input(): + import numpy as np + from itertools import combinations + from brainiak.utils.utils import _check_timeseries_input + + # Set a fixed vector for comparison + vector = np.random.randn(60) + + # List of subjects with one voxel/ROI + list_1d = [vector for _ in np.arange(10)] + (data_list_1d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_1d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Array of subjects with one voxel/ROI + array_2d = np.hstack([vector[:, np.newaxis] + for _ in np.arange(10)]) + (data_array_2d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_2d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # List of 2-dimensional arrays + list_2d = [vector[:, np.newaxis] for _ in np.arange(10)] + (data_list_2d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_2d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Check if lists have mismatching size + list_bad = [list_2d[0][:-1, :]] + list_2d[1:] + with pytest.raises(ValueError): + (data_list_bad, _, _, _) = _check_timeseries_input(list_bad) + + # List of 3-dimensional arrays + list_3d = [vector[:, np.newaxis, np.newaxis] + for _ in np.arange(10)] + (data_list_3d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_3d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # 3-dimensional array + array_3d = np.dstack([vector[:, np.newaxis] + for _ in np.arange(10)]) + (data_array_3d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_3d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Check that 4-dimensional input array throws error + array_4d = array_3d[..., np.newaxis] + with pytest.raises(ValueError): + (data_array_4d, _, _, _) = _check_timeseries_input(array_4d) + + # Check they're the same + for pair in combinations([data_list_1d, data_array_2d, + data_list_2d, data_list_3d, + data_array_3d], 2): + assert np.array_equal(pair[0], pair[1]) + + # List of multivoxel arrays + matrix = np.random.randn(60, 30) + list_mv = [matrix + for _ in np.arange(10)] + (data_list_mv, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_mv) + assert n_TRs == 60 + assert n_voxels == 30 + assert n_subjects == 10 + + # 3-dimensional array with multiple voxels + array_mv = np.dstack([matrix for _ in np.arange(10)]) + (data_array_mv, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_mv) + assert n_TRs == 60 + assert n_voxels == 30 + assert n_subjects == 10 + + assert np.array_equal(data_list_mv, data_array_mv) From b132c3926efbe96dc6ecc7361b9f4d71c9fcdc8e Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 17 Apr 2019 17:55:51 +0200 Subject: [PATCH 03/24] Revert "Revert "Add fastSRM algorithm and dependencies"" This reverts commit 56a0f1c71b9262298ad519a7544ae9cb7994ad82. --- brainiak/funcalign/fastsrm.py | 824 +++++++++++++++ requirements-dev.txt | 3 + tests/eventseg/test_event.py | 155 --- tests/factoranalysis/test_htfa.py | 168 --- tests/factoranalysis/test_tfa.py | 110 -- tests/fcma/data/expected_processed_data.npy | Bin 2672 -> 0 bytes tests/fcma/data/expected_raw_data.npy | Bin 5264 -> 0 bytes .../expected_searchlight_processed_data.npy | Bin 1704016 -> 0 bytes tests/fcma/test_classification.py | 222 ---- tests/fcma/test_mvpa_voxel_selection.py | 51 - tests/fcma/test_preprocessing.py | 109 -- tests/fcma/test_util.py | 59 -- tests/fcma/test_voxel_selection.py | 116 --- tests/hyperparamopt/test_hpo.py | 86 -- tests/image/test_image.py | 172 ---- tests/io/data/epoch_labels.npy | Bin 160 -> 0 bytes tests/io/data/mask.nii.gz | Bin 634 -> 0 bytes tests/io/data/subject1_bet.nii.gz | Bin 7149 -> 0 bytes tests/io/data/subject2_bet.nii.gz | Bin 7112 -> 0 bytes tests/io/test_io.py | 106 -- tests/isc/test_isc.py | 956 ------------------ tests/reprsimil/example_design.1D | 272 ----- tests/reprsimil/test_brsa.py | 651 ------------ tests/reprsimil/test_gbrsa.py | 630 ------------ tests/searchlight/test_searchlight.py | 293 ------ tests/utils/example_design.1D | 272 ----- tests/utils/example_stimtime_1_AFNI.txt | 2 - tests/utils/example_stimtime_1_FSL.txt | 3 - tests/utils/example_stimtime_2_AFNI.txt | 1 - tests/utils/example_stimtime_2_FSL.txt | 3 - tests/utils/test_fmrisim.py | 858 ---------------- tests/utils/test_utils.py | 396 -------- 32 files changed, 827 insertions(+), 5691 deletions(-) create mode 100644 brainiak/funcalign/fastsrm.py delete mode 100644 tests/eventseg/test_event.py delete mode 100644 tests/factoranalysis/test_htfa.py delete mode 100644 tests/factoranalysis/test_tfa.py delete mode 100644 tests/fcma/data/expected_processed_data.npy delete mode 100644 tests/fcma/data/expected_raw_data.npy delete mode 100644 tests/fcma/data/expected_searchlight_processed_data.npy delete mode 100644 tests/fcma/test_classification.py delete mode 100644 tests/fcma/test_mvpa_voxel_selection.py delete mode 100644 tests/fcma/test_preprocessing.py delete mode 100644 tests/fcma/test_util.py delete mode 100644 tests/fcma/test_voxel_selection.py delete mode 100644 tests/hyperparamopt/test_hpo.py delete mode 100644 tests/image/test_image.py delete mode 100644 tests/io/data/epoch_labels.npy delete mode 100755 tests/io/data/mask.nii.gz delete mode 100644 tests/io/data/subject1_bet.nii.gz delete mode 100644 tests/io/data/subject2_bet.nii.gz delete mode 100644 tests/io/test_io.py delete mode 100644 tests/isc/test_isc.py delete mode 100755 tests/reprsimil/example_design.1D delete mode 100755 tests/reprsimil/test_brsa.py delete mode 100644 tests/reprsimil/test_gbrsa.py delete mode 100644 tests/searchlight/test_searchlight.py delete mode 100755 tests/utils/example_design.1D delete mode 100644 tests/utils/example_stimtime_1_AFNI.txt delete mode 100644 tests/utils/example_stimtime_1_FSL.txt delete mode 100644 tests/utils/example_stimtime_2_AFNI.txt delete mode 100644 tests/utils/example_stimtime_2_FSL.txt delete mode 100644 tests/utils/test_fmrisim.py delete mode 100644 tests/utils/test_utils.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py new file mode 100644 index 000000000..c093a1be3 --- /dev/null +++ b/brainiak/funcalign/fastsrm.py @@ -0,0 +1,824 @@ +"""Fast Shared Response Model (FastSRM) + +The implementations are based on the following publications: + +.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", + P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge + Advances in Neural Information Processing Systems (NIPS), 2015. + http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model + +.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging + Datasets", + Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. + Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, + Kenneth A. Norman, + IEEE International Conference on Big Data, 2016. + https://doi.org/10.1109/BigData.2016.7840719 +""" + +# Author: Hugo Richard (INRIA - Parietal) +# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) and Bertrand Thirion (Inria - Parietal) +# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience Institute) and Javier Turek (Intel Labs) + +import logging + +import numpy as np +import scipy +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.utils import assert_all_finite +from sklearn.exceptions import NotFittedError +from mpi4py import MPI +import sys +from joblib import Parallel, delayed +import os +import glob +import hashlib + +__all__ = [ + "FastSRM", +] + +logger = logging.getLogger(__name__) + + +def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir=None): + """Reduce data using given atlas + + Parameters + ---------- + + img : str + path to data. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + atlas : array, shape=[n_supervoxels, n_voxels] or None or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + inv_atlas : array, shape=[n_voxels, n_supervoxels] + Pseudo inverse of the atlas (only for probabilistic atlases) + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and number of sessions are large + + Returns + ------- + + reduced_data : array, shape=[n_timeframes, n_supervoxels] + reduced data + """ + if atlas is None and inv_atlas is None: + AssertionError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") + + if inv_atlas is None and atlas is not None: + atlas_values = np.unique(atlas) + if 0 in atlas_values: + atlas_values = atlas_values[1:] + data = np.load(img) + reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T + else: + data = np.load(img) + reduced_data = data.dot(inv_atlas) + + if low_ram: + name = hashlib.md5(img.encode()).hexdigest() + path = os.path.join(temp_dir, "reduced_data_" + name) + np.save(path, reduced_data) + return path + else: + return reduced_data + + +def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): + """Reduce data using given atlas. + Work done in parallel across subjects. + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + n_jobs : integer, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and/or sessions is large + + Returns + ------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + + if len(atlas.shape) == 2: + A = None + A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) + else: + A = atlas + A_inv = None + + n_subjects, n_sessions = imgs.shape + + reduced_data_list = Parallel(n_jobs=n_jobs)( + delayed(reduce_data_single)( + img, + atlas=A, + inv_atlas=A_inv, + low_ram=low_ram, + temp_dir=temp_dir + ) for img in imgs.flatten()) + + if low_ram: + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) + else: + n_timeframes, n_voxels = reduced_data_list[0].shape + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_voxels)) + + return reduced_data_list + + +def _reduced_space_compute_shared_response(reduced_data_list, + reduced_basis_list, + n_components=50): + """Compute shared response with basis fixed in reduced space + + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + reduced_basis_list : None or list of array, element i has shape=[n_components, n_supervoxels] + each subject's reduced basis + if None the basis will be generated on the fly + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + """ + n_subjects, n_sessions = reduced_data_list.shape[:2] + if type(reduced_data_list[0, 0]) == np.ndarray: + low_ram = False + elif type(reduced_data_list[0, 0]) == str: + low_ram = True + else: + AssertionError("Reduced data are stored using type %s which is neither an ndarray or str" + % type(reduced_data_list[0, 0])) + low_ram = False + + s = [None] * n_sessions + + for n in range(n_subjects): + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + + if reduced_basis_list is None: + n_timeframes, n_supervoxels = data_nm.shape + reduced_basis_list = [] + for subject in range(n_subjects): + q = np.eye(n_components, n_supervoxels) + reduced_basis_list.append(q) + + basis_n = reduced_basis_list[n] + if s[m] is None: + s[m] = data_nm.dot(basis_n.T) + else: + s[m] = s + data_nm.dot(basis_n.T) + + for m in range(n_sessions): + s[m] = float(s[m]) / n_subjects + + return s + + +def _compute_and_save_corr_mat(img, shared_response, temp_dir): + """computes correlation matrix and stores it + + Parameters + ---------- + img : str + path to data. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + shared_response : array, shape=[n_timeframes, n_components] + shared response + """ + data = np.load(img) + name = hashlib.md5(img.encode()).hexdigest() + path = os.path.join(temp_dir, "corr_mat_" + name) + np.save(path, shared_response.T.dot(data)) + + +def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): + """computes correlation matrix for all sessions + + Parameters + ---------- + + subject_number: int + Number that identifies the subject. Basis will be stored in [temp_dir]/basis_[subject_number].npy + + sessions : array of str + Element i of the array is a path to the data collected during session i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + Returns + ------- + + basis: array, shape=[n_component, n_voxels] or str + basis of subject [subject_number] or path to this basis + """ + corr_mat = None + for session in sessions: + name = hashlib.md5(session.encode()).hexdigest() + path = os.path.join(temp_dir, "corr_mat_" + name + ".npy") + if corr_mat is None: + corr_mat = np.load(path) + else: + corr_mat += np.load(path) + basis_i = _compute_subject_basis(corr_mat) + + if temp_dir is None: + return basis_i + else: + path = os.path.join(temp_dir, "basis_" % subject_number) + np.save(path, basis_i) + return path + + +def _compute_subject_basis(corr_mat): + """From correlation matrix between shared response and subject data, + Finds subject's basis + + Parameters + ---------- + + corr_mat: array, shape=[n_component, n_voxels] or shape=[n_components, n_supervoxels] + correlation matrix between shared response and subject data or subject reduced data + element k, v is given by S.T.dot(X_i) where S is the shared response and + X_i the data of subject i. + + Returns + ------- + + basis: array, shape=[n_components, n_voxels] or shape=[n_components, n_supervoxels] + basis of subject or reduced_basis of subject + """ + if corr_mat.shape[0] == corr_mat.shape[1]: + U, _, V = scipy.linalg.svd( + corr_mat + 1.e-18 * np.eye(corr_mat.shape[0]), + full_matrices=False + ) + else: + U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) + return U.dot(V) + + +def fast_srm(reduced_data_list, n_iter=10, n_components=None): + """Computes shared response and basis in reduced space + + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + n_iter : int + Number of iterations performed + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + + if type(reduced_data_list[0, 0]) == np.ndarray: + low_ram = False + elif type(reduced_data_list[0, 0]) == str: + low_ram = True + else: + AssertionError("Reduced data are stored using type %s which is neither np.ndarray or str" + % type(reduced_data_list[0, 0])) + low_ram = False + + + n_subjects, n_sessions = reduced_data_list.shape[:2] + shared_response = _reduced_space_compute_shared_response( + reduced_data_list, + None, + n_components + ) + + reduced_basis = [None] * n_subjects + for _ in range(n_iter): + for n in range(n_subjects): + cov = None + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + if cov is None: + cov = shared_response[m].T.dot(data_nm) + else: + cov += shared_response[m].T.dot(data_nm) + reduced_basis[n] = _compute_subject_basis(cov) + + shared_response = _reduced_space_compute_shared_response( + reduced_data_list, + reduced_basis, + n_components + ) + + return shared_response + + +def _compute_basis_subject_online(sessions, shared_response_list): + """Computes subject's basis with shared response fixed + + Parameters + ---------- + + sessions : array of str + Element i of the array is a path to the data collected during session i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + Returns + ------- + + basis: array, shape=[n_components, n_voxels] + basis + """ + + basis_i = None + i = 0 + for session in sessions: + data = np.load(session) + if basis_i is None: + basis_i = shared_response_list[i].T.dot(data) + else: + basis_i += shared_response_list[i].T.dot(data) + i += 1 + del data + return _compute_subject_basis(basis_i) + + +def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subjects_indexes): + """Computes shared response during one session with basis fixed + + Parameters + ---------- + + subjects : array of str + Element i of the array is a path to the data of subject i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis of all subjects, element i is the basis of subject i + + temp_dir : None or str + path to basis folder where file basis_%i.npy contains the basis of subject i + + subjects_indexes : list of int or None + list of indexes corresponding to the subjects to use to compute shared response + + Returns + ------- + + shared_response : array, shape=[n_timeframes, n_components] + shared response + """ + n = 0 + shared_response = None + for k, i in enumerate(subjects_indexes): + subject = subjects[k] + data = np.load(subject) + if temp_dir is None: + basis_i = basis_list[i] + else: + basis_i = np.load(os.path.join(temp_dir, "basis_%i.npy" % i)) + + if shared_response is None: + shared_response = data.dot(basis_i.T) + else: + shared_response += data.dot(basis_i.T) + + n += 1 + return shared_response / float(n) + + +def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects_indexes): + """Computes shared response with basis fixed + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis of all subjects, element i is the basis of subject i + + temp_dir : None or str + path to basis folder where file basis_%i.npy contains the basis of subject i + + n_jobs : integer, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + subjects_indexes : list or None + list of indexes corresponding to the subjects to use to compute shared response + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + shared_response_list = Parallel(n_jobs=n_jobs)( + delayed(_compute_shared_response_online_single)( + subjects, + basis_list, + temp_dir, + subjects_indexes + ) for subjects in imgs.T) + + return shared_response_list + + +class FastSRM(BaseEstimator, TransformerMixin): + """SRM decomposition using a very low amount of memory and computational power + + Given multi-subject data, factorize it as a shared response S among all + subjects and an orthogonal transform (basis) W per subject: + + .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N + + Parameters + ---------- + + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + n_components : int + Number of timecourses of the shared coordinates + + n_iter : int + Number of iterations to perform + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and / or sessions is large + + random_state : int or RandomState + Pseudo number generator state used for random sampling. + + n_jobs : int, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + verbose : bool or "warn" + if True, logs are enabled. + if False, logs are disabled. + if "warn" only warnings are printed. + + Attributes + ---------- + + `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str + basis of all subjects, element i is the basis of subject i + or path to basis of all subjects, element i is the path to the basis of subject i + """ + def __init__(self, + atlas, + n_components=20, + n_iter=100, + temp_dir=None, + low_ram=False, + random_state=None, + n_jobs=1, + verbose="warn",): + + self.random_state = random_state + self.n_jobs = n_jobs + self.verbose = verbose + self.n_components = n_components + self.n_iter = n_iter + self.atlas = atlas + + self.basis_list = None + + if temp_dir is None: + if self.verbose == "warn" or self.verbose is True: + logger.warning("temp_dir has value None. All basis (spatial maps) and " + "reconstructed data will therefore be kept in memory." + "This can lead to memory errors when the number of subjects " + "and/or sessions is large.") + + if temp_dir is not None: + if not os.path.exists(os.path.join(temp_dir, "fastsrm")): + os.mkdir(os.path.join(temp_dir, "fastsrm")) + self.temp_dir = os.path.join(temp_dir, "fastsrm") + + # Remove files in temp folder + paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) + for path in paths: + os.remove(path) + + self.low_ram = low_ram + + def fit(self, imgs): + """Computes basis across subjects from input imgs + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + ------- + self : object + Returns the instance itself. Contains attributes listed + at the object level. + """ + + if self.temp_dir is not None: + # Remove former basis in temp folder + paths = glob.glob(os.path.join(self.temp_dir, "basis") + "*") + for path in paths: + os.remove(path) + + if type(imgs) != np.ndarray: + AssertionError("imgs should be of type np.ndarray but is of type %s" + % type(imgs)) + + if len(imgs.shape) != 2: + AssertionError("imgs should be an array of shape [n_subjects, n_sessions] " + "but its shape is of size %i" + % len(imgs.shape)) + + if self.verbose is True: + n_subjects, n_sessions = imgs.shape + logger.info("Fitting using %i subjects and %i sessions per subject" + % (n_subjects, n_sessions)) + + if self.verbose is True: + logger.info("[FastSRM.fit] Reducing data") + + reduced_data = reduce_data( + imgs, + atlas=self.atlas, + n_jobs=self.n_jobs, + low_ram=self.low_ram, + temp_dir=self.temp_dir + ) + + if self.verbose: + logger.info("[FastSRM.fit] Finds shared response using reduced data") + + shared_response_list = fast_srm( + reduced_data, + n_iter=self.n_iter, + n_components=self.n_components, + ) + + if self.verbose: + print("[FastSRM.fit] Finds basis using full data and shared response") + + if self.n_jobs == 1: + basis = [] + for i, sessions in enumerate(imgs): + basis_i = _compute_basis_subject_online(sessions, shared_response_list) + if self.temp_dir is None: + basis.append(basis_i) + else: + path = os.path.join(self.temp_dir, "basis_%i" % i) + np.save(path, basis_i) + basis.append(path) + del basis_i + else: + Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_corr_mat)( + subject, + shared_response_list[m], + self.temp_dir + ) + for m, subjects in enumerate(imgs.T) + for subject in subjects + ) + + basis = Parallel(n_jobs=self.n_jobs)(delayed(_compute_and_save_subject_basis)(i, sessions, self.temp_dir) + for i, sessions in enumerate(imgs)) + + self.basis_list = basis + return self + + def fit_transform(self, imgs, **fit_params): + """Computes basis across subjects and shared response from input imgs + return shared response. + + Parameters + ---------- + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + -------- + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + self.fit(imgs) + return self.transform(imgs) + + def transform(self, imgs, subjects_indexes=None): + """From data in imgs and basis from training data, + computes shared response. + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + + subjects_indexes : list or None: + if None imgs[i] will be transformed using basis[i] + otherwise imgs[i] will be transformed using basis[subjects_index[i]] + + Returns + ------- + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + if subjects_indexes is None: + subjects_indexes = np.arange(len(imgs)) + else: + subjects_indexes = np.array(subjects_indexes) + + shared_response = _compute_shared_response_online( + imgs, + self.basis_list, + self.temp_dir, + self.n_jobs, + subjects_indexes + ) + + return shared_response + + def inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None): + """From shared response and basis from training data reconstruct subject's data + + Parameters + ---------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + subjects_indexes: list or None: + if None reconstructs data of all subjects' used during train + otherwise reconstructs data using subject's specified by subjects_indexes + + sessions_indexes: list or None: + if None reconstructs data using all sessions + otherwise uses only specified sessions + + Returns + ------- + reconstructed_data: array shape=[len(subjects_indexes), len(sessions_indexes), n_timeframes, n_voxels] + Reconstructed data for chosen subjects and sessions + """ + n_subjects = len(self.basis_list) + + if subjects_indexes is None: + subjects_indexes = np.arange(n_subjects) + else: + subjects_indexes = np.array(subjects_indexes) + + if sessions_indexes is None: + sessions_indexes = np.arange(len(shared_response_list)) + else: + sessions_indexes = np.array(sessions_indexes) + + data = [] + for i in subjects_indexes: + data_ = [] + if self.temp_dir is None: + basis_i = self.basis_list[i] + else: + basis_i = np.load(os.path.join(self.temp_dir, "basis_%i.npy" % i)) + + for j in sessions_indexes: + data_.append(shared_response_list[j].dot(basis_i)) + + data.append(np.array(data_)) + return np.array(data) diff --git a/requirements-dev.txt b/requirements-dev.txt index bf2baa3eb..9132617e1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,6 +8,9 @@ setuptools_scm sphinx sphinx_rtd_theme towncrier +joblib +os +glob # testing # Also add to .conda/meta.yaml diff --git a/tests/eventseg/test_event.py b/tests/eventseg/test_event.py deleted file mode 100644 index a82de27bd..000000000 --- a/tests/eventseg/test_event.py +++ /dev/null @@ -1,155 +0,0 @@ -from brainiak.eventseg.event import EventSegment -from scipy.special import comb -import numpy as np -import pytest -from sklearn.exceptions import NotFittedError - - -def test_create_event_segmentation(): - es = EventSegment(5) - assert es, "Invalid EventSegment instance" - - -def test_fit_shapes(): - K = 5 - V = 3 - T = 10 - es = EventSegment(K, n_iter=2) - sample_data = np.random.rand(V, T) - es.fit(sample_data.T) - - assert es.segments_[0].shape == (T, K), "Segmentation from fit " \ - "has incorrect shape" - assert np.isclose(np.sum(es.segments_[0], axis=1), np.ones(T)).all(), \ - "Segmentation from learn_events not correctly normalized" - - T2 = 15 - sample_data2 = np.random.rand(V, T2) - test_segments, test_ll = es.find_events(sample_data2.T) - - assert test_segments.shape == (T2, K), "Segmentation from find_events " \ - "has incorrect shape" - assert np.isclose(np.sum(test_segments, axis=1), np.ones(T2)).all(), \ - "Segmentation from find_events not correctly normalized" - - es_invalid = EventSegment(K) - with pytest.raises(ValueError, message="T < K should cause error"): - es_invalid.model_prior(K-1) - with pytest.raises(ValueError, message="#Events < K should cause error"): - es_invalid.set_event_patterns(np.zeros((V, K-1))) - - -def test_simple_boundary(): - es = EventSegment(2) - random_state = np.random.RandomState(0) - - sample_data = np.array([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + \ - random_state.rand(2, 7) * 10 - es.fit(sample_data.T) - - events = np.argmax(es.segments_[0], axis=1) - assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ - "Failed to correctly segment two events" - - events_predict = es.predict(sample_data.T) - assert np.array_equal(events_predict, [0, 0, 0, 1, 1, 1, 1]), \ - "Error in predict interface" - - -def test_event_transfer(): - es = EventSegment(2) - sample_data = np.asarray([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) - - with pytest.raises(NotFittedError, message="Should need to set variance"): - seg = es.find_events(sample_data.T)[0] - - with pytest.raises(NotFittedError, message="Should need to set patterns"): - seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] - - es.set_event_patterns(np.asarray([[1, 0], [0, 1]])) - seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] - - events = np.argmax(seg, axis=1) - assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ - "Failed to correctly transfer two events to new data" - - -def test_weighted_var(): - es = EventSegment(2) - - D = np.zeros((8, 4)) - for t in range(4): - D[t, :] = (1/np.sqrt(4/3)) * np.array([-1, -1, 1, 1]) - for t in range(4, 8): - D[t, :] = (1 / np.sqrt(4 / 3)) * np.array([1, 1, -1, -1]) - mean_pat = D[[0, 4], :].T - - weights = np.zeros((8, 2)) - weights[:, 0] = [1, 1, 1, 1, 0, 0, 0, 0] - weights[:, 1] = [0, 0, 0, 0, 1, 1, 1, 1] - assert np.array_equal( - es.calc_weighted_event_var(D, weights, mean_pat), [0, 0]),\ - "Failed to compute variance with 0/1 weights" - - weights[:, 0] = [1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5] - weights[:, 1] = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] - true_var = (4 * 0.5 * 12)/(6 - 5/6) * np.ones(2) / 4 - assert np.allclose( - es.calc_weighted_event_var(D, weights, mean_pat), true_var),\ - "Failed to compute variance with fractional weights" - - -def test_sym(): - es = EventSegment(4) - - evpat = np.repeat(np.arange(10).reshape(-1, 1), 4, axis=1) - es.set_event_patterns(evpat) - - D = np.repeat(np.arange(10).reshape(1, -1), 20, axis=0) - ev = es.find_events(D, var=1)[0] - - # Check that events 1-4 and 2-3 are symmetric - assert np.all(np.isclose(ev[:, :2], np.fliplr(np.flipud(ev[:, 2:])))),\ - "Fit with constant data is not symmetric" - - -def test_chains(): - es = EventSegment(5, event_chains=np.array(['A', 'A', 'B', 'B', 'B'])) - - es.set_event_patterns(np.array([[1, 1, 0, 0, 0], - [0, 0, 1, 1, 1]])) - sample_data = np.array([[0, 0, 0], [1, 1, 1]]) - seg = es.find_events(sample_data.T, 0.1)[0] - - ev = np.nonzero(seg > 0.99)[1] - assert np.array_equal(ev, [2, 3, 4]),\ - "Failed to fit with multiple chains" - - -def test_prior(): - K = 10 - T = 100 - - es = EventSegment(K) - mp = es.model_prior(T)[0] - - p_bound = np.zeros((T, K-1)) - norm = comb(T-1, K-1) - for t in range(T-1): - for k in range(K-1): - # See supplementary material of Neuron paper - # https://doi.org/10.1016/j.neuron.2017.06.041 - p_bound[t+1, k] = comb(t, k) * comb(T-t-2, K-k-2) / norm - p_bound = np.cumsum(p_bound, axis=0) - - mp_gt = np.zeros((T, K)) - for k in range(K): - if k == 0: - mp_gt[:, k] = 1 - p_bound[:, 0] - elif k == K - 1: - mp_gt[:, k] = p_bound[:, k-1] - else: - mp_gt[:, k] = p_bound[:, k-1] - p_bound[:, k] - - assert np.all(np.isclose(mp, mp_gt)),\ - "Prior does not match analytic solution" diff --git a/tests/factoranalysis/test_htfa.py b/tests/factoranalysis/test_htfa.py deleted file mode 100644 index 58591b130..000000000 --- a/tests/factoranalysis/test_htfa.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_R(): - from brainiak.factoranalysis.htfa import HTFA - with pytest.raises(TypeError) as excinfo: - HTFA() - assert "missing 2 required positional arguments" in str(excinfo.value) - - -def test_X(): - from brainiak.factoranalysis.htfa import HTFA - import numpy as np - - n_voxel = 100 - n_tr = 20 - K = 5 - max_global_iter = 3 - max_local_iter = 3 - max_voxel = n_voxel - max_tr = n_tr - - R = [] - n_subj = 2 - for s in np.arange(n_subj): - R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) - - htfa = HTFA( - K, - n_subj=n_subj, - max_global_iter=max_global_iter, - max_local_iter=max_local_iter, - max_voxel=max_voxel, - max_tr=max_tr) - - X = np.random.rand(n_voxel, n_tr) - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "Input data should be a list" in str(excinfo.value) - - X = [] - # Check that does NOT run with wrong array dimension - with pytest.raises(ValueError) as excinfo: - htfa.fit(X, R=R) - assert "Need at leat one subject to train the model" in str(excinfo.value) - - X = [] - X.append([1, 2, 3]) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "data should be an array" in str(excinfo.value) - - X = [] - X.append(np.random.rand(n_voxel)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "subject data should be 2D array" in str(excinfo.value) - - X = [] - for s in np.arange(n_subj): - X.append(np.random.rand(n_voxel, n_tr)) - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "Coordinates should be a list" in str(excinfo.value) - - R = [] - R.append([1, 2, 3]) - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("Each scanner coordinate matrix should be an array" - in str(excinfo.value)) - - R = [] - R.append(np.random.rand(n_voxel)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("Each scanner coordinate matrix should be 2D array" - in str(excinfo.value)) - - R = [] - for s in np.arange(n_subj): - R.append(np.random.rand(n_voxel - 1, 3)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("n_voxel should be the same in X[idx] and R[idx]" - in str(excinfo.value)) - - -def test_can_run(): - import numpy as np - from brainiak.factoranalysis.htfa import HTFA - from mpi4py import MPI - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - - n_voxel = 100 - n_tr = 20 - K = 5 - max_global_iter = 3 - max_local_iter = 3 - max_voxel = n_voxel - max_tr = n_tr - R = [] - n_subj = 2 - for s in np.arange(n_subj): - R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) - my_R = [] - for idx in np.arange(n_subj): - if idx % size == rank: - my_R.append(R[idx]) - - htfa = HTFA( - K, - n_subj=n_subj, - max_global_iter=max_global_iter, - max_local_iter=max_local_iter, - max_voxel=max_voxel, - max_tr=max_tr, - verbose=True) - assert htfa, "Invalid HTFA instance!" - - X = [] - for s in np.arange(n_subj): - X.append(np.random.rand(n_voxel, n_tr)) - my_data = [] - for idx in np.arange(n_subj): - if idx % size == rank: - my_data.append(X[idx]) - - if rank == 0: - htfa.fit(my_data, R=my_R) - assert True, "Root successfully running HTFA" - assert htfa.global_prior_.shape[0] == htfa.prior_bcast_size,\ - "Invalid result of HTFA! (wrong # element in global_prior)" - assert htfa.global_posterior_.shape[0] == htfa.prior_bcast_size,\ - "Invalid result of HTFA! (wrong # element in global_posterior)" - - else: - htfa.fit(my_data, R=my_R) - assert True, "worker successfully running HTFA" - print(htfa.local_weights_.shape) - assert htfa.local_weights_.shape[0] == n_tr * K,\ - "Invalid result of HTFA! (wrong # element in local_weights)" - assert htfa.local_posterior_.shape[0] == htfa.prior_size,\ - "Invalid result of HTFA! (wrong # element in local_posterior)" diff --git a/tests/factoranalysis/test_tfa.py b/tests/factoranalysis/test_tfa.py deleted file mode 100644 index 041d03cbd..000000000 --- a/tests/factoranalysis/test_tfa.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_tfa(): - from brainiak.factoranalysis.tfa import TFA - import numpy as np - - n_voxel = 100 - n_tr = 20 - K = 5 - max_iter = 5 - max_num_voxel = n_voxel - max_num_tr = n_tr - tfa = TFA( - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - assert tfa, "Invalid TFA instance!" - - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - X = [1, 2, 3] - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "Input data should be an array" in str(excinfo.value) - - X = np.random.rand(n_voxel) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "Input data should be 2D array" in str(excinfo.value) - - X = np.random.rand(n_voxel, n_tr) - R = [1, 2, 3] - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "coordinate matrix should be an array" in str(excinfo.value) - - R = np.random.rand(n_voxel) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "coordinate matrix should be 2D array" in str(excinfo.value) - - R = np.random.randint(2, high=102, size=(n_voxel - 1, 3)) - # Check that does NOT run if n_voxel in X and R does not match - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "The number of voxels should be the same in X and R" in str( - excinfo.value) - - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - tfa.fit(X, R=R) - assert True, "Success running TFA with one subject!" - posterior_size = K * (tfa.n_dim + 1) - assert tfa.local_posterior_.shape[ - 0] == posterior_size,\ - "Invalid result of TFA! (wrong # element in local_posterior)" - - weight_method = 'ols' - tfa = TFA( - weight_method=weight_method, - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - assert tfa, "Invalid TFA instance!" - - X = np.random.rand(n_voxel, n_tr) - tfa.fit(X, R=R) - assert True, "Success running TFA with one subject!" - - template_prior, _, _ = tfa.get_template(R) - tfa.set_K(K) - tfa.set_seed(200) - tfa.fit(X, R=R, template_prior=template_prior) - assert True, "Success running TFA with one subject and template prior!" - assert tfa.local_posterior_.shape[ - 0] == posterior_size,\ - "Invalid result of TFA! (wrong # element in local_posterior)" - - weight_method = 'odd' - tfa = TFA( - weight_method=weight_method, - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - with pytest.raises(ValueError) as excinfo: - tfa.fit(X, R=R) - assert "'rr' and 'ols' are accepted as weight_method!" in str( - excinfo.value) diff --git a/tests/fcma/data/expected_processed_data.npy b/tests/fcma/data/expected_processed_data.npy deleted file mode 100644 index 460553dff13f0839b8571f9a0943cfb4eb237fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2672 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 Niah$^!R=(7{0Gj{kZb?| diff --git a/tests/fcma/data/expected_searchlight_processed_data.npy b/tests/fcma/data/expected_searchlight_processed_data.npy deleted file mode 100644 index e23174f2c7f6c3458e2fcb6f9bee6b5ecf7970c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1704016 zcmeI*F>BjE6aZj%?XS@07CaS`p%k)ZYsu1~Q!$N0htd?gTM6Vp^auH`RZj06_!&i& zX%c}xfq8zXJL&vApGMv94v{Bli0hZA z4i5_))gk#+-)P?*?zdIPceTg3j`;BGoUQ&HzI{TqKH2}o>-F8w`9pzPzwFt)4mm*`;g>*DSwhl zPxvOC^(uAfqlvmkzsS3;8~wz86UdG+sygq=eoQ&WI*RtlBX+dM^~L`qkn1-zk6Cn$ zi}T{+2xRwsm%0vPU8R`&%Lx!5K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfWR08>ihWZUonPq9L=HDr|g4}JM^AUb16VFKAZ=fke1Hvs|!2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF c5FkK+009C72oNAZfB*pk1PBlyKwzlAA8SKUk^lez diff --git a/tests/fcma/test_classification.py b/tests/fcma/test_classification.py deleted file mode 100644 index 3225fa80d..000000000 --- a/tests/fcma/test_classification.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.classifier import Classifier -from scipy.stats.mstats import zscore -from sklearn import svm -from sklearn.linear_model import LogisticRegression -import numpy as np -import math -from numpy.random import RandomState -from scipy.spatial.distance import hamming - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def create_epoch(idx, num_voxels): - row = 12 - col = num_voxels - mat = prng.rand(row, col).astype(np.float32) - # impose a pattern to even epochs - if idx % 2 == 0: - mat = np.sort(mat, axis=0) - mat = zscore(mat, axis=0, ddof=0) - # if zscore fails (standard deviation is zero), - # set all values to be zero - mat = np.nan_to_num(mat) - mat = mat / math.sqrt(mat.shape[0]) - return mat - - -def test_classification(): - fake_raw_data = [create_epoch(i, 5) for i in range(20)] - labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - # 5 subjects, 4 epochs per subject - epochs_per_subj = 4 - # svm - svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - training_data = fake_raw_data[0:12] - clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data)), labels[0:12]) - expected_confidence = np.array([-1.18234421, 0.97403604, -1.04005679, - 0.92403019, -0.95567738, 1.11746593, - -0.83275891, 0.9486868]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM does not provide correct results' - confidence = clf.decision_function(list(zip(fake_raw_data[12:], - fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence) - ) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM without recomputation ' \ - 'does not provide correct results' - y = [0, 1, 0, 1, 0, 1, 0, 1] - score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data[12:])), y) - assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ - 'the prediction score is incorrect' - # svm with partial similarity matrix computation - clf = Classifier(svm_clf, num_processed_voxels=2, - epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(fake_raw_data, fake_raw_data)), - labels, - num_training_samples=12) - y_pred = clf.predict() - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM (partial sim) does not ' \ - 'provide correct results' - confidence = clf.decision_function() - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM (partial sim) without recomputation ' \ - 'does not provide correct results' - # logistic regression - lr_clf = LogisticRegression() - clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data)), labels[0:12]) - expected_confidence = np.array([-4.49666484, 3.73025553, -4.04181695, - 3.73027436, -3.77043872, 4.42613412, - -3.35616616, 3.77716609]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via logistic regression ' \ - 'does not provide correct results' - confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence) - ) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression without precomputation ' \ - 'does not provide correct results' - - -def test_classification_with_two_components(): - fake_raw_data = [create_epoch(i, 5) for i in range(20)] - fake_raw_data2 = [create_epoch(i, 6) for i in range(20)] - labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - # 5 subjects, 4 epochs per subject - epochs_per_subj = 4 - # svm - svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - training_data = fake_raw_data[0: 12] - training_data2 = fake_raw_data2[0: 12] - clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data2)), labels[0:12]) - expected_confidence = np.array([-1.23311606, 1.02440964, -0.93898336, - 1.07028798, -1.04420007, 0.97647772, - -1.0498268, 1.04970111]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM does not provide correct results' - confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM without recomputation ' \ - 'does not provide correct results' - y = [0, 1, 0, 1, 0, 1, 0, 1] - score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data2[12:])), y) - assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ - 'the prediction score is incorrect' - # svm with partial similarity matrix computation - clf = Classifier(svm_clf, num_processed_voxels=2, - epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(fake_raw_data, fake_raw_data2)), - labels, - num_training_samples=12) - y_pred = clf.predict() - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM (partial sim) does not ' \ - 'provide correct results' - confidence = clf.decision_function() - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM (partial sim) without recomputation ' \ - 'does not provide correct results' - # logistic regression - lr_clf = LogisticRegression() - clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) - # specifying num_training_samples is for coverage - clf.fit(list(zip(training_data, training_data2)), - labels[0:12], - num_training_samples=12) - expected_confidence = np.array([-4.90819848, 4.22548132, -3.76255726, - 4.46505975, -4.19933099, 4.08313584, - -4.23070437, 4.31779758]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via logistic regression ' \ - 'does not provide correct results' - confidence = clf.decision_function(list(zip(fake_raw_data[12:], - fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression without precomputation ' \ - 'does not provide correct results' - - -if __name__ == '__main__': - test_classification() - test_classification_with_two_components() diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py deleted file mode 100644 index d551ff5ee..000000000 --- a/tests/fcma/test_mvpa_voxel_selection.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector -from brainiak.searchlight.searchlight import Searchlight -from sklearn import svm -import numpy as np -from mpi4py import MPI -from numpy.random import RandomState - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def test_mvpa_voxel_selection(): - data = prng.rand(5, 5, 5, 8).astype(np.float32) - # all MPI processes read the mask; the mask file is small - mask = np.ones([5, 5, 5], dtype=np.bool) - mask[0, 0, :] = False - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - sl = Searchlight(sl_rad=1) - mvs = MVPAVoxelSelector(data, mask, labels, 2, sl) - # for cross validation, use SVM with precomputed kernel - - clf = svm.SVC(kernel='rbf', C=10) - result_volume, results = mvs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [] - for tuple in results: - if tuple[1] > 0: - output.append(int(8*tuple[1])) - expected_output = [6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, - 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - - -if __name__ == '__main__': - test_mvpa_voxel_selection() diff --git a/tests/fcma/test_preprocessing.py b/tests/fcma/test_preprocessing.py deleted file mode 100644 index 0ee38db49..000000000 --- a/tests/fcma/test_preprocessing.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path - -import numpy as np - -from brainiak.fcma.preprocessing import (prepare_fcma_data, prepare_mvpa_data, - prepare_searchlight_mvpa_data) -from brainiak import io - -data_dir = Path(__file__).parents[1] / 'io' / 'data' -expected_dir = Path(__file__).parent / 'data' -suffix = 'bet.nii.gz' -mask_file = data_dir / 'mask.nii.gz' -epoch_file = data_dir / 'epoch_labels.npy' -expected_labels = np.array([0, 1, 0, 1]) - - -def test_prepare_fcma_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - mask = io.load_boolean_mask(mask_file) - conditions = io.load_labels(epoch_file) - raw_data, _, labels = prepare_fcma_data(images, conditions, mask) - expected_raw_data = np.load(expected_dir / 'expected_raw_data.npy') - assert len(raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - for idx in range(len(raw_data)): - assert np.allclose(raw_data[idx], expected_raw_data[idx]), \ - 'raw data do not match in test_prepare_fcma_data' - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_fcma_data' - from brainiak.fcma.preprocessing import RandomType - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, - random=RandomType.REPRODUCIBLE) - assert len(random_raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, - random=RandomType.UNREPRODUCIBLE) - assert len(random_raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - - -def test_prepare_mvpa_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - mask = io.load_boolean_mask(mask_file) - conditions = io.load_labels(epoch_file) - processed_data, labels = prepare_mvpa_data(images, conditions, mask) - expected_processed_data = np.load(expected_dir - / 'expected_processed_data.npy') - assert len(processed_data) == len(expected_processed_data), \ - 'numbers of epochs do not match in test_prepare_mvpa_data' - for idx in range(len(processed_data)): - assert np.allclose(processed_data[idx], - expected_processed_data[idx]), ( - 'raw data do not match in test_prepare_mvpa_data') - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_mvpa_data' - - -def test_prepare_searchlight_mvpa_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - conditions = io.load_labels(epoch_file) - processed_data, labels = prepare_searchlight_mvpa_data(images, - conditions) - expected_searchlight_processed_data = np.load( - expected_dir / 'expected_searchlight_processed_data.npy') - for idx in range(len(processed_data)): - assert np.allclose(processed_data[idx], - expected_searchlight_processed_data[idx]), ( - 'raw data do not match in test_prepare_searchlight_mvpa_data') - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_searchlight_mvpa_data' - from brainiak.fcma.preprocessing import RandomType - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_processed_data, _ = prepare_searchlight_mvpa_data( - images, - conditions, - random=RandomType.REPRODUCIBLE) - assert (len(random_processed_data) - == len(expected_searchlight_processed_data)), ( - 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_processed_data, _ = prepare_searchlight_mvpa_data( - images, - conditions, - random=RandomType.UNREPRODUCIBLE) - assert (len(random_processed_data) - == len(expected_searchlight_processed_data)), ( - 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') - - -if __name__ == '__main__': - test_prepare_fcma_data() - test_prepare_mvpa_data() - test_prepare_searchlight_mvpa_data() diff --git a/tests/fcma/test_util.py b/tests/fcma/test_util.py deleted file mode 100644 index 6508d49b1..000000000 --- a/tests/fcma/test_util.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.random import RandomState -from brainiak.fcma.util import compute_correlation - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def test_correlation_computation(): - row1 = 5 - col = 10 - row2 = 6 - mat1 = prng.rand(row1, col).astype(np.float32) - mat2 = prng.rand(row2, col).astype(np.float32) - corr = compute_correlation(mat1, mat1) - expected_corr = np.corrcoef(mat1) - assert np.allclose(corr, expected_corr, atol=1e-5), ( - "high performance correlation computation does not provide correct " - "correlation results within the same set") - corr = compute_correlation(mat1, mat2) - mat = np.concatenate((mat1, mat2), axis=0) - expected_corr = np.corrcoef(mat)[0:row1, row1:] - assert np.allclose(corr, expected_corr, atol=1e-5), ( - "high performance correlation computation does not provide correct " - "correlation results between two sets") - - -def test_correlation_nans(): - row1 = 5 - col = 10 - row2 = 6 - mat1 = prng.rand(row1, col).astype(np.float32) - mat2 = prng.rand(row2, col).astype(np.float32) - mat1[0, 0] = np.nan - corr = compute_correlation(mat1, mat2, return_nans=False) - assert np.all(corr == 0, axis=1)[0] - assert np.sum(corr == 0) == row2 - corr = compute_correlation(mat1, mat2, return_nans=True) - assert np.all(np.isnan(corr), axis=1)[0] - assert np.sum(np.isnan(corr)) == row2 - - -if __name__ == '__main__': - test_correlation_computation() - test_correlation_nans() diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py deleted file mode 100644 index 98fadc4ea..000000000 --- a/tests/fcma/test_voxel_selection.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.voxelselector import VoxelSelector -from scipy.stats.mstats import zscore -from sklearn import svm -from sklearn.linear_model import LogisticRegression -import numpy as np -import math -from mpi4py import MPI -from numpy.random import RandomState - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def create_epoch(): - row = 12 - col = 5 - mat = prng.rand(row, col).astype(np.float32) - mat = zscore(mat, axis=0, ddof=0) - # if zscore fails (standard deviation is zero), - # set all values to be zero - mat = np.nan_to_num(mat) - mat = mat / math.sqrt(mat.shape[0]) - return mat - - -def test_voxel_selection(): - fake_raw_data = [create_epoch() for i in range(8)] - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - vs = VoxelSelector(labels, 4, 2, fake_raw_data, voxel_unit=1) - # test scipy normalization - fake_corr = prng.rand(1, 4, 5).astype(np.float32) - fake_corr = vs._correlation_normalization(fake_corr) - if MPI.COMM_WORLD.Get_rank() == 0: - expected_fake_corr = [[[1.06988919, 0.51641309, -0.46790636, - -1.31926763, 0.2270218], - [-1.22142744, -1.39881694, -1.2979387, - 1.05702305, -0.6525566], - [0.89795232, 1.27406132, 0.36460185, - 0.87538344, 1.5227468], - [-0.74641371, -0.39165771, 1.40124381, - -0.61313909, -1.0972116]]] - assert np.allclose(fake_corr, expected_fake_corr), \ - 'within-subject normalization does not provide correct results' - # for cross validation, use SVM with precomputed kernel - # no shrinking, set C=1 - clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [7, 4, 6, 4, 4] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - # for cross validation, use logistic regression - clf = LogisticRegression() - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [6, 3, 6, 4, 4] - assert np.allclose(output, expected_output, atol=1), ( - "voxel selection via logistic regression does not provide correct " - "results") - - -def test_voxel_selection_with_two_masks(): - fake_raw_data1 = [create_epoch() for i in range(8)] - fake_raw_data2 = [create_epoch() for i in range(8)] - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - vs = VoxelSelector(labels, 4, 2, fake_raw_data1, - raw_data2=fake_raw_data2, voxel_unit=1) - # for cross validation, use SVM with precomputed kernel - # no shrinking, set C=1 - clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [3, 3, 3, 6, 6] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - # for cross validation, use logistic regression - clf = LogisticRegression() - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [3, 4, 4, 6, 6] - assert np.allclose(output, expected_output, atol=1), ( - "voxel selection via logistic regression does not provide correct " - "results") - - -if __name__ == '__main__': - test_voxel_selection() - test_voxel_selection_with_two_masks() diff --git a/tests/hyperparamopt/test_hpo.py b/tests/hyperparamopt/test_hpo.py deleted file mode 100644 index 3904458ee..000000000 --- a/tests/hyperparamopt/test_hpo.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest -import numpy as np -import scipy.stats as st -from brainiak.hyperparamopt.hpo import gmm_1d_distribution, fmin - - -def test_simple_gmm(): - x = np.array([1., 1., 2., 3., 1.]) - d = gmm_1d_distribution(x, min_limit=0., max_limit=4.) - assert d(1.1) > d(3.5), "GMM distribution not behaving correctly" - assert d(2.0) > d(3.0), "GMM distribution not behaving correctly" - assert d(-1.0) == 0, "GMM distribution out of bounds error" - assert d(9.0) == 0, "GMM distribution out of bounds error" - - samples = d.get_samples(n=25) - np.testing.assert_array_less(samples, 4.) - np.testing.assert_array_less(0., samples) - - -def test_simple_gmm_weights(): - x = np.array([1., 1., 2., 3., 1., 3.]) - d = gmm_1d_distribution(x) - - x2 = np.array([1., 2., 3.]) - w = np.array([3., 1., 2.]) - d2 = gmm_1d_distribution(x2, weights=w) - y2 = d2(np.array([1.1, 2.0])) - - assert d2(1.1) == y2[0],\ - "GMM distribution array & scalar results don't match" - assert np.abs(d(1.1) - d2(1.1)) < 1e-5,\ - "GMM distribution weights not handled correctly" - assert np.abs(d(2.0) - d2(2.0)) < 1e-5,\ - "GMM distribution weights not handled correctly" - - -def test_simple_hpo(): - - def f(args): - x = args['x'] - return x*x - - s = {'x': {'dist': st.uniform(loc=-10., scale=20), 'lo': -10., 'hi': 10.}} - trials = [] - - # Test fmin and ability to continue adding to trials - best = fmin(loss_fn=f, space=s, max_evals=40, trials=trials) - best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) - - assert len(trials) == 50, "HPO continuation trials not working" - - # Test verbose flag - best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) - - yarray = np.array([tr['loss'] for tr in trials]) - np.testing.assert_array_less(yarray, 100.) - - xarray = np.array([tr['x'] for tr in trials]) - np.testing.assert_array_less(np.abs(xarray), 10.) - - assert best['loss'] < 100., "HPO out of range" - assert np.abs(best['x']) < 10., "HPO out of range" - - # Test unknown distributions - s2 = {'x': {'dist': 'normal', 'mu': 0., 'sigma': 1.}} - trials2 = [] - with pytest.raises(ValueError) as excinfo: - fmin(loss_fn=f, space=s2, max_evals=40, trials=trials2) - assert "Unknown distribution type for variable" in str(excinfo.value) - - s3 = {'x': {'dist': st.norm(loc=0., scale=1.)}} - trials3 = [] - fmin(loss_fn=f, space=s3, max_evals=40, trials=trials3) diff --git a/tests/image/test_image.py b/tests/image/test_image.py deleted file mode 100644 index 4796bc3f1..000000000 --- a/tests/image/test_image.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Iterable, Sequence - -import numpy as np -import pytest - -from nibabel.nifti1 import Nifti1Pair -from nibabel.spatialimages import SpatialImage - -from brainiak.image import (mask_image, mask_images, MaskedMultiSubjectData, - multimask_images, SingleConditionSpec) - - -@pytest.fixture -def masked_multi_subject_data(masked_images): - return np.stack(masked_images, axis=-1) - - -class TestMaskedMultiSubjectData: - def test_from_masked_images(self, masked_images, - masked_multi_subject_data): - result = MaskedMultiSubjectData.from_masked_images(masked_images, - len(masked_images)) - assert np.array_equal(np.moveaxis(result, 1, 0), - masked_multi_subject_data) - - -@pytest.fixture -def condition_spec() -> SingleConditionSpec: - return np.array([[[1, 1, 1, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]]], - dtype=np.int8).view(SingleConditionSpec) - - -class TestUniqueLabelConditionSpec: - def test_extract_labels(self, condition_spec: SingleConditionSpec - ) -> None: - assert np.array_equal(condition_spec.extract_labels(), - np.array([0, 1])) - - -@pytest.fixture -def spatial_image() -> SpatialImage: - return Nifti1Pair(np.array([[[[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 1, 0], - [0, 1, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]]]).reshape(4, 4, 4, 1), - np.eye(4)) - - -@pytest.fixture -def mask() -> np.ndarray: - return np.array([[[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 1, 0], - [0, 1, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 1, 0], - [0, 1, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]], dtype=np.bool) - - -@pytest.fixture -def masked_data() -> np.ndarray: - return np.array([[1, 0, 0, 1, 0, 1, 1, 0]]).reshape(8, 1) - - -@pytest.fixture -def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]: - images = [spatial_image] - image_data = spatial_image.get_data().copy() - image_data[1, 1, 1, 0] = 2 - images.append(Nifti1Pair(image_data, np.eye(4))) - return images - - -@pytest.fixture -def masks(mask: np.ndarray) -> Sequence[np.ndarray]: - masks = [mask] - mask2 = mask.copy() - mask2[0, 0, 0] = 1 - masks.append(mask2) - mask3 = mask.copy() - mask3[2, 2, 2] = 0 - masks.append(mask3) - return masks - - -@pytest.fixture -def multimasked_images(masked_data) -> Iterable[Iterable[np.ndarray]]: - masked_data_2 = np.concatenate(([[2]], masked_data[1:, :])) - return [[masked_data, np.concatenate(([[0]], masked_data)), - masked_data[:-1, :]], - [masked_data_2, np.concatenate(([[0]], masked_data_2)), - masked_data_2[:-1, :]]] - - -@pytest.fixture -def masked_images(multimasked_images) -> Iterable[np.ndarray]: - return [multimasked_image[0] for multimasked_image in multimasked_images] - - -def test_mask_image(spatial_image: SpatialImage, mask: np.ndarray, - masked_data: np.ndarray) -> None: - result = mask_image(spatial_image, mask) - assert np.array_equal(result, masked_data) - - -def test_mask_image_with_type(spatial_image: SpatialImage, mask: np.ndarray, - masked_data: np.ndarray) -> None: - masked_data_type = np.float32 - result = mask_image(spatial_image, mask, masked_data_type) - assert result.dtype == masked_data_type - assert np.allclose(result, masked_data) - - -def test_multimask_images( - images: Iterable[SpatialImage], - masks: Sequence[np.ndarray], - multimasked_images: Iterable[Iterable[np.ndarray]] - ) -> None: - result = multimask_images(images, masks) - for result_images, expected_images in zip(result, - multimasked_images): - for result_image, expected_image in zip(result_images, - expected_images): - assert np.array_equal(result_image, expected_image) - - -def test_mask_images( - images: Iterable[SpatialImage], - mask: np.ndarray, - masked_images: Iterable[np.ndarray] - ) -> None: - result = mask_images(images, mask) - for result_image, expected_image in zip(result, masked_images): - assert np.array_equal(result_image, expected_image) diff --git a/tests/io/data/epoch_labels.npy b/tests/io/data/epoch_labels.npy deleted file mode 100644 index 50b0a4304f8d755e5ba6147e7c2ebe6021326848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7ItmbEXrQU1P^+Mzz{Lmz4EO<939;$`jT{x$ diff --git a/tests/io/data/mask.nii.gz b/tests/io/data/mask.nii.gz deleted file mode 100755 index 687ab69749ac9e33b4aa23f6e3bf269c37b6d2c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmb2|=3oE==C^kYvzQzuSRY7UV(CwqYA3*PLz^Y&=2G8@+%padSBUgK5L~<>QHRC) z|C8GLMjPH7dbe+W?){o?WzD748Qp&kbN`mC348qa__r_B|F&AJds~99kSj9e&V_GzaCYpfvaTyQ~&ez z32dn14y-5dKEL;-al)M16f~)V`jdO_e|h)Y@fXje&GVN&XGhW5pmk~Y{+B;DvO5%& Vp-9kx)6iAN5cDRYH=&V@0RSu0fKUJc diff --git a/tests/io/data/subject1_bet.nii.gz b/tests/io/data/subject1_bet.nii.gz deleted file mode 100644 index f34b35c1d201cfae247bc3e4857f17eaee53a5f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7149 zcmeHMYg7|w8m3DNuCm~s#R%&KcePa!3YHbga*6j_tq5H~4HCpP=}I7AAVFd>SeDfi zD(k8h6%!XP6a*1ME|7#oC8b^hCe-r7+6ICoxVQ1DzoC7lB=JY|CxVOp&GKYzvDH;Ivd&#v|nAhRWNYWSh|>X zjC$+`=Qfj5u#laY{-F_(sYBG!B-c8Y51`|;#-0T@&9Pmr;qX{becXf%Xn^idL^y;F z@Ayo9@@U&>KZMEY4w|6EPsSr+BMcc`Uy~qVyDJr0edVO_^ExQ3vJmU-gxjPwc1J)Ag_@8YJC^DYMjR$s(XGo8Z1LrAOO< zpBb$P-=WUSmTGWv^WYjI9Jnm_x&Yc;&%z|7aR2$6&EWiG7Ppy7IAzN3 z2yB{#FFIdoS)qx&2=$GQ<#q+S|Bi>x8lAN+)P(;$-^r@W22Xw;*mkXw9w4O6$@F#) z_4+W0#eHz(zV8+9qlP3K!8zNjZ7B%BokjDW%@9Z|N6?u@?P2+A!E zJ8SmL56AT@t?YGz4UPp_4vp_kqd<}^YYNswuzR|46Fccy_TS_PUDLrgcMA$ zk5XtzZyvVNYbj1kOds8=o!j|j8$;bk1-@S%;T~MLm0LE`1{p?%jEJH4r_Mz;DPT(H zz!zPCAqTNsv|N-6O|P#i zRBde|8aQzPwvFS$Ow}B3UY*v@Jvresju&Lux^>XWYBL9rvSps6os$yrW1@h)#PGlY z<$>CUqp-ICU=Cn`TJ+l>e?7ZZM@;#i4tUEhM_sZ!+Nhl?08<=cuk7>Pf5)S|bhoYU zYlf0C)5amK&~c21Xe)`izAH5HT}>G72{;pA;i$)Zclhg%rLO3Q3U}8;7fmeF+McPE z9W59LaVudADDfKsj6s+wyuPhv6n`TP#}(OZy8rDfKkNG)~1n-xy@Fz@av z$`3fk>l>gFa|F_U984OJ8~s?^9y6&vx=2zHAt^U)=?HAwX5@HpIB4^?xr4Cq*oliM zt+s>4Zbqa{`RtDK32mkrL+qHWyxYq%|FH31-pFbA_ov4SM2ejfL@HNtHOSmz%FB8U zY$^sZZPaU#?<*W_jvj)`r~svk%|xql5nm)@KtG|P zDs~C5fGt037~Dq7Y#H{=rFNS+J8N;g+j=U}7LBd%N6X`^bsbJz2Fk08LG9rISVc9~ zXbFrz0bZT{e9U&ka~ggi66;X;GEc4|i%9qAl3|~I-3}n{EgB#QX8`3=0Z7V4fF(B_ zxO3nHQ8?CR(9Z<3vLl@|9<<>fXqt>K@h4IK-fOA-X&+AGX8XYy9t;P&Zyb*o)qNMEVm zgu-8FCN0+~4DB|LWtJ}t$NWmv?vM2-mlYe*g;V|ipfibg179?RlIk$%SpM;ja?JL& zrwka`3pESpX%q=eFU#J}v?e4q7a@Ej07PVv-B88AoL!%dx^Ly+yc{)zEfP*i-zzTr5;QXOGBL>v%6(q{fvbG1R*IUf{zym+p?64I%wlvAQi}yq7#y z5z^u*Pnxn;DX6uo5=+jR7}F&4_Ps}cB!&!YzLkQp1}JRO_wNb7xE_hxI{{%xPnn%! z4Xv>%%35fDMb5^cIyJ33F1ZO>yNi#--Mu8e+$cc_b;?pDH$sUiWj;F{d*yj(k^;Y3kxN#6oe)bH>Q zh{TdJmZhU(J5m3S!WAXd5DMsu>1$Lx$LKzfe=LeD;9hG1ZHpTcr!e{;Iy^bo_X?!(Lfj^j3$bUvXn)c%UZ@fi_E zyvehSMT$@zrdPaKt9Sl7CQsS-=Ip)doo$k>VJ%i-jEo`90II8ugec z+k^6|LG5rd`k8$dYgtknzcM{o>zr#PoV6Sum7!YAJ+aE{wo{c-HC?VMjycX?C})+N zH}k4BhHn~KuO{nFCQT*Qrw4qAni%07(6$^mf(|7Y+is_H;7j?)r3+YJEj!HPv~4hamLcaMEvG*5X_Mb56BUU zMX7WrKC+P|5SOdzGSo|b3vKIic(?r;^Rk6sN%t{rtF2@ZMRZcW7hMCaRG+Gh*nQuj zsYJaX%`$DGD%rzwibt&k?cjF9F>bN#w();Cq=Kb#@NdfK z+8-kIGq#m13Er@F(cuq}%D&pmi}n_fw`BJn_Uar{KA@VI)`z1yj0ATtJv)5z+wG`x#qrUsnTGk~?f&?S_(!2)O(=x9-w>q;Jw#f`2neJteldjGX z`K3$Cs%oOlL#)}f5p2IMKUIbbhoL1>g#+h(L|>0Da(oC9`lw}N)F2Kb^QB`lm-hRd zK4QvnwG3qMkwEu8p=2lJ`u1rc`4^ZeK==xKop|?Cd~k~=o74n0Uqn5m(Pc*N!9jV` z@GYFsw%Zhb$g!QhnPGB-;6OrjgvSql1`dLssP_#7h7@og#dvR|WS`PkZPfCqbFgEr=H0yp=vImssuu~xe|paNE_FB2c6);NSM6!b zdrejdA;k%2NsUuA4BwxQBQ`tLZn;?+5H_)`l>9UW4PHAPB)z9fn7MVqFEnQ;GK%~T zY?=R57=Tz&?yqI|zx;-`1~k^;vH4BSgcxa`+7!mzgena;`saPK^uljC=l zY*=hqW%U4&T#^J*ALd>hcGETI*af|Ger1=HlL04DH1mh1d#?vM`VqoEx$wwjiC{{h z_{H+HmPc+xA)MZAoy;+*eao+)ySlw`4ka7*j;>OTx5i}J*uYV^q3;%{aaWrA>MXnX zkO-I6(jYa`))!BTOLNH{cmI6czqe}Rz)fqEh1E{Ng(WyIM7!f9U+c3$(A@HsF=oUX z1#wd)N6{PI=Q_U7;qyK&?vJN?fE?{j6G>v;P!X`cz;${ifOuFMwX)(l$KYx> zofr4>h2WceG-~keRNK?jxNu% z_2jJXtXB6e=TYhEb6ka$(Q7RA9G?z5vwTEx9;h%vD+cB})+g}!CaHKzTrGjp)QIW` z{B|&Hf#876Cs zL=XGcSU&Dig}e54uNytu{yjWlAxpZ}RSY%v#IN+M`eDe>7I*bk$IxEoqkybX+y6x+ z>CVSC$<{&Ek{eZYG(=fH=fQXl)NrhbImXqT%Qyft2kkl59UL1PtGEyTAxP_b1~^|D z=~A9Phxgr9TI0_(HP>SUaSz37&p@rj=Hq82mYKQGB~k%%wWd7Hx$}@>iAu<+O@>1m zi_9(#Ip;OC?8&i-$4y~7Q`ei@BS7|osC$PU&p0?d^9JQa4Lb!gY~~gX&YTx#UOB=g z^ASjHIdwhgAf&Y3!tnrM=>mq8$iO&NTMn{WnuPjw1yiXMwD*uGN`3&{3)(}zGP-1| zVH~_mZs*8BeMoQoIeA)$GQT<~L|s!;9ji3S$ewA=#ld(~2Rb)_HKhAxEW)l}FkfJb zP*ATuTD;7LW_L3k?~J-v#7;@c+O8VN7r|9QeJjWXBtX`gbz~6_5B+vsnYRBTnUukj zCXJg;yqy7Su7fCZ1?|HL#LqEm`^mZqwR_Ii7$Fia5)7}~o#x#1bx=rx5Xo(#Iy;^X zY)Q$m-7anZneT1) z{pN{r*mwL7&$z`mVqDrynW*tnM5^tdfcB|e(Qj1xVJq(9s4pZoh%@VlO)Nu~NlY`2 z9qaZs+%)l3bp9vcQ!oDXzOAM7TRCFBIgeObH%nRa>xOA6(lPw*U*3B0p zWv!@qp3*TOfl6JihP8%za78N&^w7+CE8beBY@0H17k5Dg3!Cxjx&`=85Tk97z}N;; z-%xf}2Azyl1ABzXbDeTrWM`J}iPn4qkR|gl=00hWz^E(MT}G#8%2Py;P@Ea&=uonS z-m68b4Cu~*vpO>&v-Ph|?^qxQ3+wc3pjkS)nOo9vqZp9w#8^eBwx{#9xhkp0T;7^9 zly>B6`+1cT2#^dw+zn-56&LOtuNc<%|s#=wEHw*AgZh zG=%M7OJG|6q* zeudpkG;vqM{M(jT(EI95b9=ArA3}9B80(e^eBNV7`cb+w`{~54MX->Q0NRWa)aHE8WGDqmWrPsyMyP$t6(y++*Sc@=I3nnN*|KB&^_ zPQ|fX_oh1wXf|!7abZ({hP%6kOX0dKL&EAvxFL(Wg?PwB>5O0o*od9ui|f`EFt01O zZ)`fNMBW!A3?P4VNQ}@P*f2@-0h(_~AfG~wxnJRZE@1tMn-*#G9OZPT@A0bLmUAnv zJ8`}{XyfQ22EB7CSZJ7Kz@LmuBJToHEf#_C9=R^<6Uh8R8@e2+e`-Q=wE40*+R527 z1!$IIdM}1uOj5U1hLx($ZQNTqSuUP4lwN&oXGNph8VIEz?o*eRnJKY~v^tDWf95vK zN+%*-I-Ontz?7yPt$gBgi~GtRz-25G#Le1{^?1gu`$E}Wpm`ho%Q`AT6c*QlUZhBz zN*D1uENb5j8284*2dRv)GOX{9F_}_8V?QMKzM5lgsyWEk08We^Bct;Qhbv&Cj&6CW zasR#sFYZz!xxDWH*#%5Eoi>_PbcT;QcfBUyp;A_jqbGt_e&6Hz`k{3@v0YUCQFvcG z$Lc>joocYX7w+YR4FHC?CbP zP|Fkj;rS{Ir9$LAUW_YVy?B*QS4xzr6iIn945hXVyY<&nNH5B=nS2FJ`VFmV7>5$V zk4uhBJa*6Y0J;1MLtBhz{JMfOSlf?ObAV)AFAsc212nq=ve`XUYJIZJ$={RL@#`Y$ z`#Iq6Up}jIZb4{U6@rbXtSbn%jrw9~mdkb`-|=Y?0$V~7SZJu|3g-%(`s3Skw=M01B3o6c(SQ@Re^N^S<1jNZ6yKL^odevYHP?0+=fl>{ zyN9^%zHScYb(9@45-S(IKR355XJ~>mvzSDDJ1ZuGB zPtkB6u`=9b9pET_7@>HbCQZU3y_;@lhIx{%EE~}ZEJmxRtBXHgn6VpSv^9yfy^Km@H1@O-Z4B}^3)IcFK4M{U~><@ z9DpBedth&7^+sVAl#hmZE^fe8?n zCoCOrRcwX4>sXK_t*Yu@K_T{DD6VY;#4Yh6S94IHiEIR;!2Hq-m%mG$miSHl_qVID P=cU0(r*}}F+er8i>K-AX diff --git a/tests/io/test_io.py b/tests/io/test_io.py deleted file mode 100644 index 9f72550f6..000000000 --- a/tests/io/test_io.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path -from typing import Iterable, Sequence - -import nibabel as nib -import numpy as np -import pytest - -from brainiak import io - - -@pytest.fixture -def in_dir() -> Path: - return Path(__file__).parent / "data" - - -@pytest.fixture -def expected_image_data_shape() -> Sequence[int]: - return (64, 64, 26, 10) - - -@pytest.fixture -def mask_path(in_dir: Path) -> Path: - return in_dir / "mask.nii.gz" - - -@pytest.fixture -def labels_path(in_dir: Path) -> Path: - return in_dir / "epoch_labels.npy" - - -@pytest.fixture -def expected_condition_spec_shape() -> Sequence[int]: - return (2, 2, 10) - - -@pytest.fixture -def expected_n_subjects() -> int: - return 2 - - -@pytest.fixture -def image_paths(in_dir: Path) -> Iterable[Path]: - return (in_dir / "subject1_bet.nii.gz", in_dir / "subject2_bet.nii.gz") - - -def test_load_images_from_dir_data_shape( - in_dir: Path, - expected_image_data_shape: Sequence[int], - expected_n_subjects: int - ) -> None: - for i, image in enumerate(io.load_images_from_dir(in_dir, "bet.nii.gz")): - assert image.get_data().shape == (64, 64, 26, 10) - assert i + 1 == expected_n_subjects - - -def test_load_images_data_shape( - image_paths: Iterable[Path], - expected_image_data_shape: Sequence[int], - expected_n_subjects: int - ) -> None: - for i, image in enumerate(io.load_images(image_paths)): - assert image.get_data().shape == (64, 64, 26, 10) - assert i + 1 == expected_n_subjects - - -def test_load_boolean_mask(mask_path: Path) -> None: - mask = io.load_boolean_mask(mask_path) - assert mask.dtype == np.bool - - -def test_load_boolean_mask_predicate(mask_path: Path) -> None: - mask = io.load_boolean_mask(mask_path, lambda x: np.logical_not(x)) - expected_mask = np.logical_not(io.load_boolean_mask(mask_path)) - assert np.array_equal(mask, expected_mask) - - -def test_load_labels(labels_path: Path, - expected_condition_spec_shape: Sequence[int], - expected_n_subjects: int) -> None: - condition_specs = io.load_labels(labels_path) - i = 0 - for condition_spec in condition_specs: - assert condition_spec.shape == expected_condition_spec_shape - i += 1 - assert i == expected_n_subjects - - -def test_save_as_nifti_file(tmpdir) -> None: - out_file = str(tmpdir / "nifti.nii") - shape = (4, 4, 4) - io.save_as_nifti_file(np.ones(shape), np.eye(4), out_file) - assert nib.load(out_file).get_data().shape == shape diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py deleted file mode 100644 index fc4ea17c1..000000000 --- a/tests/isc/test_isc.py +++ /dev/null @@ -1,956 +0,0 @@ -import numpy as np -import logging -import pytest -from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, - squareform_isfc, timeshift_isc, - phaseshift_isc) -from scipy.spatial.distance import squareform - -logger = logging.getLogger(__name__) - - -# Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, - noise=1, data_type='array', - random_state=None): - prng = np.random.RandomState(random_state) - if n_voxels: - signal = prng.randn(n_TRs, n_voxels) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs, n_voxels) * noise - for subject in np.arange(n_subjects)] - elif not n_voxels: - signal = prng.randn(n_TRs) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs) * noise - for subject in np.arange(n_subjects)] - if data_type == 'array': - if n_voxels: - data = np.dstack(data) - elif not n_voxels: - data = np.column_stack(data) - return data - - -# Create 3 voxel simulated data with correlated time series -def correlated_timeseries(n_subjects, n_TRs, noise=0, - random_state=None): - prng = np.random.RandomState(random_state) - signal = prng.randn(n_TRs) - correlated = True - while correlated: - uncorrelated = np.random.randn(n_TRs, - n_subjects)[:, np.newaxis, :] - unc_max = np.amax(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - unc_mean = np.mean(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - if unc_max < .3 and np.abs(unc_mean) < .001: - correlated = False - data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], - 20, axis=2) - data = np.concatenate((data, uncorrelated), axis=1) - data = data + np.random.randn(n_TRs, 3, n_subjects) * noise - return data - - -# Compute ISCs using different input types -# List of subjects with one voxel/ROI -def test_isc_input(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC inputs") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with one voxel/ROI - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - # List of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - logger.info("Finished testing ISC inputs") - - -# Check pairwise and leave-one-out, and summary statistics for ISC -def test_isc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs_loo = isc(data, pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_subjects, n_voxels) - - # Just two subjects - iscs_loo = isc(data[..., :2], pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_voxels,) - - iscs_pw = isc(data, pairwise=True, summary_statistic=None) - assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - - # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic='mean') - assert isc_mean.shape == (n_voxels,) - - isc_median = isc(data, pairwise=False, summary_statistic='median') - assert isc_median.shape == (n_voxels,) - - with pytest.raises(ValueError): - isc(data, pairwise=False, summary_statistic='min') - - logger.info("Finished testing ISC options") - - -# Make sure ISC recovers correlations of 1 and less than 1 -def test_isc_output(): - - logger.info("Testing ISC outputs") - - data = correlated_timeseries(20, 60, noise=0, - random_state=42) - iscs = isc(data, pairwise=False) - assert np.allclose(iscs[:, :2], 1., rtol=1e-05) - assert np.all(iscs[:, -1] < 1.) - - iscs = isc(data, pairwise=True) - assert np.allclose(iscs[:, :2], 1., rtol=1e-05) - assert np.all(iscs[:, -1] < 1.) - - logger.info("Finished testing ISC outputs") - - -# Check for proper handling of NaNs in ISC -def test_isc_nans(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Inject NaNs into data - data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - iscs_loo = isc(data, pairwise=False, tolerate_nans=False) - assert np.sum(np.isnan(iscs_loo)) == n_subjects - - # Tolerate all NaNs, only subject with NaNs yields NaN - iscs_loo = isc(data, pairwise=False, tolerate_nans=True) - assert np.sum(np.isnan(iscs_loo)) == 1 - - # Pairwise approach shouldn't care - iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) - iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) - assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(iscs_pw_T)) == - np.sum(np.isnan(iscs_pw_F)) == - n_subjects - 1) - - # Set proportion of nans to reject (70% and 90% non-NaN) - data[0, 0, :] = np.nan - data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan - data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan - - iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) - iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) - iscs_loo_95 = isc(data, pairwise=False, tolerate_nans=.95) - iscs_loo_90 = isc(data, pairwise=False, tolerate_nans=.90) - iscs_loo_80 = isc(data, pairwise=False, tolerate_nans=.8) - iscs_loo_70 = isc(data, pairwise=False, tolerate_nans=.7) - iscs_loo_60 = isc(data, pairwise=False, tolerate_nans=.6) - - assert (np.sum(np.isnan(iscs_loo_F)) == - np.sum(np.isnan(iscs_loo_95)) == 60) - assert (np.sum(np.isnan(iscs_loo_80)) == - np.sum(np.isnan(iscs_loo_90)) == 42) - assert (np.sum(np.isnan(iscs_loo_T)) == - np.sum(np.isnan(iscs_loo_60)) == - np.sum(np.isnan(iscs_loo_70)) == 28) - assert np.array_equal(np.sum(np.isnan(iscs_loo_F), axis=0), - np.sum(np.isnan(iscs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(iscs_loo_80), axis=0), - np.sum(np.isnan(iscs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(iscs_loo_T), axis=0), - np.sum(np.isnan(iscs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(iscs_loo_T), axis=0), - np.sum(np.isnan(iscs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(iscs_loo_60), axis=0), - np.sum(np.isnan(iscs_loo_70), axis=0)))) - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Make sure voxel with NaNs across all subjects is always removed - data[0, 0, :] = np.nan - iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) - iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) - assert np.allclose(iscs_loo_T, iscs_loo_F, equal_nan=True) - assert (np.sum(np.isnan(iscs_loo_T)) == - np.sum(np.isnan(iscs_loo_F)) == - n_subjects) - - iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) - iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) - assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(iscs_pw_T)) == - np.sum(np.isnan(iscs_pw_F)) == - n_subjects * (n_subjects - 1) / 2) - - -# Test one-sample bootstrap test -def test_bootstrap_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - n_bootstraps = 10 - - logger.info("Testing bootstrap hypothesis test") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs = isc(data, pairwise=False, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Test one-sample bootstrap test with pairwise approach - n_bootstraps = 10 - - iscs = isc(data, pairwise=True, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Check random seeds - iscs = isc(data, pairwise=False, summary_statistic=None) - distributions = [] - for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc( - iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95, - random_state=random_state) - distributions.append(distribution) - assert np.array_equal(distributions[0], distributions[1]) - assert not np.array_equal(distributions[1], distributions[2]) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='median')) - - logger.info("Finished testing bootstrap hypothesis test") - - -# Test permutation test with group assignments -def test_permutation_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - group_assignment = [1] * 10 + [2] * 10 - - logger.info("Testing permutation test") - - # Create dataset with two groups in pairwise approach - data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=5, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=200) - - # Create data with two groups in leave-one-out approach - data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=10, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=200) - - # One-sample leave-one-out permutation test - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=False, - summary_statistic='median', - n_permutations=200) - - # One-sample pairwise permutation test - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=True, - summary_statistic='median', - n_permutations=200) - - # Small one-sample pairwise exact test - data = simulated_timeseries(12, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median', - n_permutations=10000) - - # Small two-sample pairwise exact test (and unequal groups) - data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=10000) - - # Small two-sample leave-one-out exact test (and unequal groups) - data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=10000) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing permutaton test") - - -def test_timeshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing circular time-shift") - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, pairwise - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing circular time-shift") - - -# Phase randomization test -def test_phaseshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing phase randomization") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Phase randomization one-sample test, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing phase randomization") - - -# Test ISFC -def test_isfc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing ISFC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - isfcs, iscs = isfc(data, pairwise=False, summary_statistic=None) - assert isfcs.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) - assert iscs.shape == (n_subjects, n_voxels) - - # Without vectorized upper triangle - isfcs = isfc(data, pairwise=False, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_voxels) - - # Just two subjects - isfcs, iscs = isfc(data[..., :2], pairwise=False, summary_statistic=None) - assert isfcs.shape == (n_voxels * (n_voxels - 1) / 2,) - assert iscs.shape == (n_voxels,) - - isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_voxels, n_voxels) - - # ISFC with pairwise approach - isfcs, iscs = isfc(data, pairwise=True, summary_statistic=None) - assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels * (n_voxels - 1) / 2) - assert iscs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels) - - isfcs = isfc(data, pairwise=True, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels, n_voxels) - - # ISFC with summary statistics - isfcs, iscs = isfc(data, pairwise=True, summary_statistic='mean') - isfcs, iscs = isfc(data, pairwise=True, summary_statistic='median') - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) - assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) - assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) - - isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) - assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) - assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=False) - isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) - isfcs, iscs_v = isfc(data, pairwise=False) - assert np.allclose(iscs, iscs_v, rtol=1e-03) - - # Check that ISC and ISFC diagonal are identical (pairwise) - iscs = isc(data, pairwise=True) - isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) - isfcs, iscs_v = isfc(data, pairwise=True) - assert np.allclose(iscs, iscs_v, rtol=1e-03) - - # Generate 'targets' data and use for ISFC - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - n_targets = 15 - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Ensure 'square' output enforced - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=True) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Check list input for targets - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='list') - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Check that mismatching subjects / TRs breaks targets - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - - with pytest.raises(ValueError): - isfcs = isfc(data, targets=targets_data[..., :-1], - pairwise=False, vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - with pytest.raises(ValueError): - isfcs = isfc(data, targets=targets_data[:-1, ...], - pairwise=False, vectorize_isfcs=False) - - # Check targets for only 2 subjects - isfcs = isfc(data[..., :2], targets=targets_data[..., :2], - pairwise=False, summary_statistic=None) - assert isfcs.shape == (2, n_voxels, n_targets) - - isfcs = isfc(data[..., :2], targets=targets_data[..., :2], - pairwise=True, summary_statistic=None) - assert isfcs.shape == (2, n_voxels, n_targets) - - # Check that supplying targets enforces leave-one-out - isfcs_pw = isfc(data, targets=targets_data, pairwise=True, - vectorize_isfcs=False, tolerate_nans=False) - assert isfcs_pw.shape == (n_subjects, n_voxels, n_targets) - - logger.info("Finished testing ISFC options") - - -# Check for proper handling of NaNs in ISFC -def test_isfc_nans(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Inject NaNs into data - data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels * 2 - 1) - - # With vectorized ISFCs - isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels - 1) - - # Tolerate all NaNs, only subject with NaNs yields NaN - isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == n_voxels * 2 - 1 - - isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == n_voxels - 1 - - # Pairwise approach shouldn't care - isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_F)) == - (n_voxels * 2 - 1) * (n_subjects - 1)) - - isfcs_pw_T, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, - tolerate_nans=True) - isfcs_pw_F, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_F)) == - (n_voxels - 1) * (n_subjects - 1)) - - # Set proportion of nans to reject (70% and 90% non-NaN) - data[0, 0, :] = np.nan - data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan - data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan - - isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - isfcs_loo_95 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.95) - isfcs_loo_90 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.90) - isfcs_loo_80 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.8) - isfcs_loo_70 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.7) - isfcs_loo_60 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.6) - assert (np.sum(np.isnan(isfcs_loo_F)) == - np.sum(np.isnan(isfcs_loo_95)) == 3420) - assert (np.sum(np.isnan(isfcs_loo_80)) == - np.sum(np.isnan(isfcs_loo_90)) == 2430) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_60)) == - np.sum(np.isnan(isfcs_loo_70)) == 1632) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), - np.sum(np.isnan(isfcs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), - np.sum(np.isnan(isfcs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_60), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)))) - - isfcs_loo_T, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=True) - isfcs_loo_F, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=False) - isfcs_loo_95, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.95) - isfcs_loo_90, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.90) - isfcs_loo_80, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.8) - isfcs_loo_70, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.7) - isfcs_loo_60, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.6) - assert (np.sum(np.isnan(isfcs_loo_F)) == - np.sum(np.isnan(isfcs_loo_95)) == 1680) - assert (np.sum(np.isnan(isfcs_loo_80)) == - np.sum(np.isnan(isfcs_loo_90)) == 1194) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_60)) == - np.sum(np.isnan(isfcs_loo_70)) == 802) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), - np.sum(np.isnan(isfcs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), - np.sum(np.isnan(isfcs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_60), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)))) - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Make sure voxel with NaNs across all subjects is always removed - data[0, 0, :] = np.nan - isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_loo_T, isfcs_loo_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_F)) == - 1180) - - isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_T)) == - 11210) - - # Check for NaN-handling in targets - n_targets = 15 - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - - # Inject NaNs into targets_data - targets_data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - isfcs_loo = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False, tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == (n_subjects - 1) * (n_targets * 2) - - # Single NaN in targets will get averaged out with tolerate - isfcs_loo = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False, tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == 0 - - -def test_squareform_isfc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Generate square redundant ISFCs - isfcs_r = isfc(data, vectorize_isfcs=False) - assert isfcs_r.shape == (n_subjects, n_voxels, n_voxels) - - # Squareform these into condensed ISFCs and ISCs - isfcs_c, iscs_c = squareform_isfc(isfcs_r) - assert isfcs_c.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) - assert iscs_c.shape == (n_subjects, n_voxels) - - # Go back the other way and check it's the same - isfcs_new = squareform_isfc(isfcs_c, iscs_c) - assert np.array_equal(isfcs_r, isfcs_new) - - # Check against ISC function - assert np.allclose(isc(data), iscs_c, rtol=1e-03) - - # Check for two subjects - isfcs_r = isfc(data[..., :2], vectorize_isfcs=False) - assert isfcs_r.shape == (n_voxels, n_voxels) - isfcs_c, iscs_c = squareform_isfc(isfcs_r) - assert isfcs_c.shape == (n_voxels * (n_voxels - 1) / 2,) - assert iscs_c.shape == (n_voxels,) - assert np.array_equal(isfcs_r, squareform_isfc(isfcs_c, iscs_c)) - - -if __name__ == '__main__': - test_isc_input() - test_isc_options() - test_isc_output() - test_isc_nans() - test_bootstrap_isc() - test_permutation_isc() - test_timeshift_isc() - test_phaseshift_isc() - test_isfc_options() - test_isfc_nans() - test_squareform_isfc() - logger.info("Finished all ISC tests") diff --git a/tests/reprsimil/example_design.1D b/tests/reprsimil/example_design.1D deleted file mode 100755 index 7385edea6..000000000 --- a/tests/reprsimil/example_design.1D +++ /dev/null @@ -1,272 +0,0 @@ -# - 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 - 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 - 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 - 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 - 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 - 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 - 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 - 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 - 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 - 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 - 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 - 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 - 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 - 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 - 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 - 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 - 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 - 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 - 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 - 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 - 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 - 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 - 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 - 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 - 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 - 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 - 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 - 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 - 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 - 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 - 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 - 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 - 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 - 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 - 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 - 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 - 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 - 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 - 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 - 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 - 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 - 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 - 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 - 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 - 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 - 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 - 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 - 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 - 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 - 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 - 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 - 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 - 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 - 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 - 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 - 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 - 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 - 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 - 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 - 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 - 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 - 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 - 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 - 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 - 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 - 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 - 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 - 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 - 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 - 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 - 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 - 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 - 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 - 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 - 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 - 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 - 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 - 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 - 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 - 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 - 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 - 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 - 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 - 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 - 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 - 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 - 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 - 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 - 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 - 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 - 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 - 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 - 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 - 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 - 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 - 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 - 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 - 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 - 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 - 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 - 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 - 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 - 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 - 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 - 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 - 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 - 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 - 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 - 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 - 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 - 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 - 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 - 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 - 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 - 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 - 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 - 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 - 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 - 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 - 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 - 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 - 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 - 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 - 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 - 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 - 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 - 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 - 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 - 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 - 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 - 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 - 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 - 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 - 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 - 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 - 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 - 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 - 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 - 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 - 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 - 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 - 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 - 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 - 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 - 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 - 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 - 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 - 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 - 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 - 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 - 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 - 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 - 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 - 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 - 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 - 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 - 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 - 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 - 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 - 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 - 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 - 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 - 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 - 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 - 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 - 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 - 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 - 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 - 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 - 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 - 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 - 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 - 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 - 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 - 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 - 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 - 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 - 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 - 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 - 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 - 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 - 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 - 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 - 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 - 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 - 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 -# - diff --git a/tests/reprsimil/test_brsa.py b/tests/reprsimil/test_brsa.py deleted file mode 100755 index fbf898b86..000000000 --- a/tests/reprsimil/test_brsa.py +++ /dev/null @@ -1,651 +0,0 @@ -# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, -# Princeton University -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_can_instantiate(): - import brainiak.reprsimil.brsa - s = brainiak.reprsimil.brsa.BRSA() - assert s, "Invalid BRSA instance!" - - s = brainiak.reprsimil.brsa.BRSA( - n_iter=50, rank=5, auto_nuisance=False, n_nureg=2, nureg_method='ICA', - baseline_single=False, init_iter=5, GP_space=True, GP_inten=True, - tol=2e-3, eta=0.001, space_smooth_range=10.0, inten_smooth_range=100.0, - tau_range=2.0, - tau2_prior=brainiak.reprsimil.brsa.prior_GP_var_inv_gamma, - optimizer='CG', random_state=100, anneal_speed=20) - assert s, "Invalid BRSA instance!" - - -def test_fit(): - from brainiak.reprsimil.brsa import BRSA - import brainiak.utils.utils as utils - import scipy.stats - import numpy as np - import os.path - np.random.seed(10) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 2 times, mimicking 2 runs of itenditcal timing - n_run = 2 - design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) - design.n_TR = design.n_TR * n_run - - # start simulating some data - n_V = 50 - n_C = np.size(design.design_task, axis=1) - n_T = design.n_TR - - noise_bot = 0.5 - noise_top = 5.0 - noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot - - # generating noise - noise = np.zeros([n_T, n_V]) - noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ - np.random.randn(n_V) * noise_level - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_level = 5.0 # test with high SNR - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends - # not only on beta but also on x. - inten = np.random.rand(n_V) * 20.0 - - # parameters of Gaussian process to generate pseuso SNR - tau = 1.0 - smooth_width = 5.0 - inten_kernel = 1.0 - - coords = np.arange(0, n_V)[:, None] - - dist2 = np.square(coords - coords.T) - - inten_tile = np.tile(inten, [n_V, 1]) - inten_diff2 = (inten_tile - inten_tile.T)**2 - - K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / - inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 - - L = np.linalg.cholesky(K) - snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level - sqrt_v = noise_level * snr - betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v - signal = np.dot(design.design_task, betas_simulated) - - # Adding noise to signal as data - Y = signal + noise + inten - - scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) - - # Test fitting with GP prior. - brsa = BRSA(GP_space=True, GP_inten=True, n_iter=5, - init_iter=10, auto_nuisance=False, tol=2e-3) - - # We also test that it can detect baseline regressor included in the - # design matrix for task conditions - wrong_design = np.insert(design.design_task, 0, 1, axis=1) - with pytest.raises(ValueError) as excinfo: - brsa.fit(X=Y, design=wrong_design, scan_onsets=scan_onsets, - coords=coords, inten=inten) - assert ('Your design matrix appears to have included baseline time series.' - in str(excinfo.value)) - # Now we fit with the correct design matrix. - brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets, - coords=coords, inten=inten) - - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], - u_i[np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - - noise_new = np.zeros([n_T, n_V]) - noise_new[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise_new[i_t, :] = noise_new[i_t - 1, :] * \ - rho1 + np.random.randn(n_V) * noise_level - - Y_new = signal + noise_new + inten - ts, ts0 = brsa.transform(Y_new, scan_onsets=scan_onsets) - p = scipy.stats.pearsonr(ts[:, 0], design.design_task[:, 0])[1] - assert p < 0.01, ( - "Recovered time series does not correlate with true time series!") - assert np.shape(ts) == (n_T, n_C) and np.shape(ts0) == (n_T, 1), ( - "Wrong shape in returned time series by transform function!") - - [score, score_null] = brsa.score( - X=Y_new, design=design.design_task, scan_onsets=scan_onsets) - assert score > score_null, ( - "Full model does not win over null model on data containing signal") - - [score, score_null] = brsa.score(X=noise_new + inten, - design=design.design_task, - scan_onsets=scan_onsets) - assert score < score_null, ( - "Null model does not win over full model on data without signal") - - # Test fitting with lower rank, nuisance regressors and without GP prior - rank = n_C - 1 - n_nureg = 1 - brsa = BRSA(rank=rank, n_nureg=n_nureg, tol=2e-3, - n_iter=8, init_iter=4, auto_nuisance=True) - brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets) - # u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ - np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - - assert (not hasattr(brsa, 'bGP_') - and not hasattr(brsa, 'lGPspace_') - and not hasattr(brsa, 'lGPinten_') - ), ("the BRSA object should not have parameters of GP if GP is " - "not requested.") - # GP parameters are not set if not requested - assert brsa.beta0_.shape[0] == n_nureg + 1, 'Shape of beta0 incorrect' - p = scipy.stats.pearsonr(brsa.beta0_[0, :], inten)[1] - assert p < 0.01, ( - 'recovered beta0 does not correlate with the baseline of voxels.') - assert np.shape(brsa.L_) == ( - n_C, rank), 'Cholesky factor should have shape of (n_C, rank)' - - # Test fitting with GP over just spatial coordinates. - brsa = BRSA(GP_space=True, baseline_single=False, - tol=2e-3, n_iter=4, init_iter=4) - brsa.fit(X=Y, design=design.design_task, - scan_onsets=scan_onsets, coords=coords) - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ - np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - assert not hasattr(brsa, 'lGPinten_'), ( - "the BRSA object should not have parameters of lGPinten_ if only " - "smoothness in space is requested.") - # GP parameters are not set if not requested - - -def test_gradient(): - from brainiak.reprsimil.brsa import BRSA - import brainiak.utils.utils as utils - import numpy as np - import os.path - import numdifftools as nd - - np.random.seed(100) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - n_run = 4 - # concatenate it by 4 times, mimicking 4 runs of itenditcal timing - design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) - design.n_TR = design.n_TR * n_run - - # start simulating some data - n_V = 30 - n_C = np.size(design.design_task, axis=1) - n_T = design.n_TR - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot - - # generating noise - noise = np.zeros([n_T, n_V]) - noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ - np.random.randn(n_V) * noise_level - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0, 0] = 0.2 - ideal_cov[5:9, 5:9] = 0.6 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_level = 5.0 # test with high SNR - inten = np.random.randn(n_V) * 20.0 - - # parameters of Gaussian process to generate pseuso SNR - tau = 0.8 - smooth_width = 5.0 - inten_kernel = 1.0 - - coords = np.arange(0, n_V)[:, None] - - dist2 = np.square(coords - coords.T) - - inten_tile = np.tile(inten, [n_V, 1]) - inten_diff2 = (inten_tile - inten_tile.T)**2 - - K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / - inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 - - L = np.linalg.cholesky(K) - snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - sqrt_v = noise_level * snr - betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v - signal = np.dot(design.design_task, betas_simulated) - - # Adding noise to signal as data - Y = signal + noise - - scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) - - # Test fitting with GP prior. - brsa = BRSA(GP_space=True, GP_inten=True, rank=n_C) - - # Additionally, we test the generation of re-used terms. - X0 = np.ones(n_T)[:, None] - D, F, run_TRs, n_run_returned = brsa._prepare_DF( - n_T, scan_onsets=scan_onsets) - assert np.shape(D) == (n_T, n_T), 'D has wrong shape' - assert np.shape(F) == (n_T, n_T), 'F has wrong shape' - assert np.sum(D) == (n_T - n_run) * 2, 'D is initialized incorrectly.' - assert np.sum(F) == n_T - n_run * 2, 'F is initialized incorrectly.' - assert n_run_returned == n_run, ( - 'There is mistake in counting number of runs') - assert np.sum(run_TRs) == n_T, ( - 'The segmentation of the total experiment duration is wrong') - (XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX - ) = brsa._prepare_data_XY(design.design_task, Y, D, F) - (X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, X_base, - n_X0, idx_DC - ) = brsa._prepare_data_XYX0(design.design_task, Y, X0, - np.random.randn(n_T)[:, None], D, F, run_TRs, - no_DC=False) - assert (np.shape(XTY) == (n_C, n_V) - and np.shape(XTDY) == (n_C, n_V) - and np.shape(XTFY) == (n_C, n_V) - ), 'Dimension of XTY etc. returned from _prepare_data is wrong' - assert (np.ndim(YTY_diag) == 1 - and np.ndim(YTDY_diag) == 1 - and np.ndim(YTFY_diag) == 1 - ), ("Dimension of YTY_diag etc. returned from _prepare_data is " - "wrong") - assert (np.ndim(XTX) == 2 - and np.ndim(XTDX) == 2 - and np.ndim(XTFX) == 2 - ), 'Dimension of XTX etc. returned from _prepare_data is wrong' - assert (np.ndim(X0TX0) == 2 - and np.ndim(X0TDX0) == 2 - and np.ndim(X0TFX0) == 2 - ), 'Dimension of X0TX0 etc. returned from _prepare_data is wrong' - assert (np.ndim(XTX0) == 2 - and np.ndim(XTDX0) == 2 - and np.ndim(XTFX0) == 2 - ), 'Dimension of XTX0 etc. returned from _prepare_data is wrong' - assert (np.ndim(X0TY) == 2 - and np.ndim(X0TDY) == 2 - and np.ndim(X0TFY) == 2 - ), 'Dimension of X0TY etc. returned from _prepare_data is wrong' - assert (np.shape(X0) == (n_T, n_X0) - and np.shape(X_base) == (n_T, np.size(idx_DC)) - and np.max(idx_DC) < n_X0 - and np.size(idx_DC) + 1 == n_X0 - ), ("Dimension of X0 or X_base, or n_X0 or indices of DC " - "components are wrong.") - l_idx = np.tril_indices(n_C) - n_l = np.size(l_idx[0]) - - # Make sure all the fields are in the indices. - idx_param_sing, idx_param_fitU, idx_param_fitV = brsa._build_index_param( - n_l, n_V, 2) - assert 'Cholesky' in idx_param_sing and 'a1' in idx_param_sing, \ - 'The dictionary for parameter indexing misses some keys' - assert 'Cholesky' in idx_param_fitU and 'a1' in idx_param_fitU, \ - 'The dictionary for parameter indexing misses some keys' - assert 'log_SNR2' in idx_param_fitV and 'c_space' in idx_param_fitV \ - and 'c_inten' in idx_param_fitV and 'c_both' in idx_param_fitV, \ - 'The dictionary for parameter indexing misses some keys' - - # Initial parameters are correct parameters with some perturbation - param0_fitU = np.random.randn(n_l + n_V) * 0.1 - param0_fitV = np.random.randn(n_V + 1) * 0.1 - param0_sing = np.random.randn(n_l + 1) * 0.1 - param0_sing[idx_param_sing['a1']] += np.mean(np.tan(rho1 * np.pi / 2)) - param0_fitV[idx_param_fitV['log_SNR2']] += np.log(snr[:n_V - 1]) * 2 - param0_fitV[idx_param_fitV['c_space']] += np.log(smooth_width) * 2 - param0_fitV[idx_param_fitV['c_inten']] += np.log(inten_kernel) * 2 - - # test if the gradients are correct - # log likelihood and derivative of the _singpara function - - ll0, deriv0 = brsa._loglike_AR1_singpara(param0_sing, XTX, XTDX, XTFX, - YTY_diag, YTDY_diag, YTFY_diag, - XTY, XTDY, XTFY, X0TX0, X0TDX0, - X0TFX0, XTX0, XTDX0, XTFX0, X0TY, - X0TDY, X0TFY, l_idx, n_C, n_T, - n_V, n_run, n_X0, idx_param_sing) - # We test the gradient to the Cholesky factor - vec = np.zeros(np.size(param0_sing)) - vec[idx_param_sing['Cholesky'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, - XTDX0, XTFX0, X0TY, X0TDY, X0TFY, - l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_sing)[0], - param0_sing, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of singpara wrt Cholesky is incorrect') - - # We test the gradient to a1 - vec = np.zeros(np.size(param0_sing)) - vec[idx_param_sing['a1']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, - XTDX0, XTFX0, X0TY, X0TDY, X0TFY, - l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_sing)[0], - param0_sing, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), - rtol=1e-5), 'gradient of singpara wrt a1 is incorrect' - - # log likelihood and derivative of the fitU function. - ll0, deriv0 = brsa._loglike_AR1_diagV_fitU(param0_fitU, XTX, XTDX, XTFX, - YTY_diag, YTDY_diag, YTFY_diag, - XTY, XTDY, XTFY, X0TX0, X0TDX0, - X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, np.log(snr) - * 2, l_idx, n_C, n_T, n_V, - n_run, n_X0, idx_param_fitU, - n_C) - - # We test the gradient wrt the reparametrization of AR(1) coefficient of - # noise. - vec = np.zeros(np.size(param0_fitU)) - vec[idx_param_fitU['a1'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitU wrt to AR(1) coefficient incorrect') - - # We test if the numerical and analytical gradient wrt to the first - # element of Cholesky factor is correct - vec = np.zeros(np.size(param0_fitU)) - vec[idx_param_fitU['Cholesky'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitU wrt Cholesky factor incorrect') - - # Test on a random direction - vec = np.random.randn(np.size(param0_fitU)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), - rtol=1e-5), 'gradient of fitU incorrect' - - # We test the gradient of _fitV wrt to log(SNR^2) assuming no GP prior. - X0TAX0, XTAX0, X0TAY, X0TAX0_i, \ - XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL = \ - brsa._precompute_ar1_quad_forms(XTY, XTDY, XTFY, - YTY_diag, YTDY_diag, YTFY_diag, - XTX, XTDX, XTFX, - X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, - L_full, rho1, n_V, n_X0) - assert np.shape(XTAcorrX) == (n_V, n_C, n_C), ( - 'Dimension of XTAcorrX is wrong by _precompute_ar1_quad_forms()') - assert XTAcorrY.shape == XTY.shape, ( - 'Shape of XTAcorrY is wrong by _precompute_ar1_quad_forms()') - assert YTAcorrY.shape == YTY_diag.shape, ( - 'Shape of YTAcorrY is wrong by _precompute_ar1_quad_forms()') - assert np.shape(X0TAX0) == (n_V, n_X0, n_X0), ( - 'Dimension of X0TAX0 is wrong by _precompute_ar1_quad_forms()') - assert np.shape(XTAX0) == (n_V, n_C, n_X0), ( - 'Dimension of XTAX0 is wrong by _precompute_ar1_quad_forms()') - assert X0TAY.shape == X0TY.shape, ( - 'Shape of X0TAX0 is wrong by _precompute_ar1_quad_forms()') - assert np.all(np.isfinite(X0TAX0_i)), ( - 'Inverse of X0TAX0 includes NaN or Inf') - ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( - param0_fitV[idx_param_fitV['log_SNR2']], X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, - n_V, n_run, n_X0, idx_param_fitV, n_C, False, False) - vec = np.zeros(np.size(param0_fitV[idx_param_fitV['log_SNR2']])) - vec[idx_param_fitV['log_SNR2'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, - False, False)[0], - param0_fitV[idx_param_fitV['log_SNR2']], - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt log(SNR2) incorrect for model without GP') - - # We test the gradient of _fitV wrt to log(SNR^2) assuming GP prior. - ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( - param0_fitV, X0TAX0, XTAX0, X0TAY, X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_fitV, n_C, True, True, dist2, inten_diff2, 100, 100) - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['log_SNR2'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV srt log(SNR2) incorrect for model with GP') - - # We test the graident wrt spatial length scale parameter of GP prior - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['c_space']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt spatial length scale of GP incorrect') - - # We test the graident wrt intensity length scale parameter of GP prior - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['c_inten']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt intensity length scale of GP incorrect') - - # We test the graident on a random direction - vec = np.random.randn(np.size(param0_fitV)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV incorrect') - - -def test_nureg_determine(): - from brainiak.reprsimil.brsa import Ncomp_SVHT_MG_DLD_approx - import numpy as np - x = np.dot(np.random.randn(100, 5), np.random.randn(5, 40)) + \ - np.random.randn(100, 40) * 0.01 - ncomp = Ncomp_SVHT_MG_DLD_approx(x) - assert ncomp >= 3 and ncomp <= 8, ( - 'recovered number of components should be in a reasonable range') - - -def test_half_log_det(): - import numpy as np - from brainiak.reprsimil.brsa import BRSA - a = np.asarray([[1, 0.2], [0.2, 1]]) - brsa = BRSA() - half_log_det = np.log(np.linalg.det(a)) / 2 - assert np.isclose(half_log_det, brsa._half_log_det( - a)), 'half log determinant function is wrong' - - -def test_n_nureg(): - import brainiak.reprsimil.brsa - import numpy as np - # noise = np.random.randn(100,30) - noise = np.dot(np.random.randn(100, 8), np.random.randn( - 8, 30)) + np.random.randn(100, 30) * 0.01 - design = np.random.randn(100, 2) - s = brainiak.reprsimil.brsa.BRSA(n_iter=2) - s.fit(X=noise, design=design) - assert s.n_nureg_ > 2 and s.n_nureg_ < 16, ( - 'n_nureg_ estimation is wrong in BRSA') diff --git a/tests/reprsimil/test_gbrsa.py b/tests/reprsimil/test_gbrsa.py deleted file mode 100644 index 7038d35b4..000000000 --- a/tests/reprsimil/test_gbrsa.py +++ /dev/null @@ -1,630 +0,0 @@ -# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, -# Princeton University -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def test_can_instantiate(): - import brainiak.reprsimil.brsa - s = brainiak.reprsimil.brsa.GBRSA() - assert s, "Invalid GBRSA instance!" - - s = brainiak.reprsimil.brsa.GBRSA(n_iter=40, rank=4, auto_nuisance=False, - nureg_method='PCA', - baseline_single=False, logS_range=1.0, - SNR_bins=11, rho_bins=40, tol=2e-3, - optimizer='CG', random_state=0, - anneal_speed=20, SNR_prior='unif') - assert s, "Invalid GBRSA instance!" - - -def test_fit(): - from brainiak.reprsimil.brsa import GBRSA - import brainiak.utils.utils as utils - import scipy.stats - import numpy as np - import os.path - np.random.seed(10) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 1, 2, and 3 times, mimicking different length - # of experiments for different participants - n_run = [2, 1, 1] - design_mat = [None] * 3 - n_T = [None] * 3 - n_V = [40, 60, 60] - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - - # start simulating some data - n_C = np.size(design_mat[0], axis=1) - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = [None] * 3 - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = [None] * 3 - - # generating noise - noise = [None] * 3 - - # baseline - inten = [None] * 3 - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - n_T[i] = n_run[i] * design.n_TR - noise_level[i] = np.random.rand( - n_V[i]) * (noise_top - noise_bot) + noise_bot - # noise level is random. - rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot - - noise[i] = np.zeros([n_T[i], n_V[i]]) - noise[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ - np.random.randn(n_V[i]) * noise_level[i] - noise[i] = noise[i] + \ - np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) - inten[i] = np.random.rand(n_V[i]) * 20.0 - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_top = 5.0 # test with high SNR - snr_bot = 1.0 - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - - snr = [None] * 3 - signal = [None] * 3 - betas_simulated = [None] * 3 - scan_onsets = [None] * 3 - Y = [None] * 3 - for i in range(3): - snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot - sqrt_v = noise_level[i] * snr[i] - betas_simulated[i] = np.dot( - L_full, np.random.randn(n_C, n_V[i])) * sqrt_v - signal[i] = np.dot(design_mat[i], betas_simulated[i]) - - # Adding noise to signal as data - Y[i] = signal[i] + noise[i] + inten[i] - - scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) - - # Test fitting. - n_nureg = 2 - gbrsa = GBRSA(n_iter=15, auto_nuisance=True, logS_range=0.5, SNR_bins=11, - rho_bins=16, n_nureg=n_nureg, optimizer='L-BFGS-B') - - gbrsa.fit(X=Y, design=design_mat, scan_onsets=scan_onsets) - - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = gbrsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], - u_i[np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(gbrsa.nSNR_[0], snr[0])[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - p = scipy.stats.pearsonr(gbrsa.sigma_[1], noise_level[1])[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(gbrsa.rho_[2], rho1[2])[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - assert np.shape(gbrsa.X0_[1]) == (n_T[1], n_nureg + 1), "Wrong size of X0" - - Y_new = [None] * 3 - noise_new = [None] * 3 - for i in range(3): - noise_new[i] = np.zeros([n_T[i], n_V[i]]) - noise_new[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise_new[i][i_t, :] = noise_new[i][i_t - 1, :] * \ - rho1[i] + np.random.randn(n_V[i]) * noise_level[i] - - Y_new[i] = signal[i] + noise_new[i] + inten[i] - ts, ts0 = gbrsa.transform(Y_new, scan_onsets=scan_onsets) - [score, score_null] = gbrsa.score( - X=Y_new, design=design_mat, scan_onsets=scan_onsets) - [score_noise, score_null_noise] = gbrsa.score( - X=noise_new, design=design_mat, scan_onsets=scan_onsets) - for i in range(3): - assert np.shape(ts[i]) == (n_T[i], n_C) and np.shape( - ts0[i]) == (n_T[i], n_nureg + 1) - p = scipy.stats.pearsonr(ts[i][:, 0], design_mat[i][:, 0])[1] - assert p < 0.01, ( - "Recovered time series does not correlate with true time series!") - - assert score[i] > score_null[i], ( - "Full model does not win over null model on data containing " - "signal") - - assert score_noise[i] < score_null_noise[i], ( - "Null model does not win over full model on data without signal") - - [score, score_null] = gbrsa.score( - X=[None] * 3, design=design_mat, scan_onsets=scan_onsets) - assert score == [None] * 3 and score_null == [None] * \ - 3, "score did not return list of None when data is None" - ts, ts0 = gbrsa.transform(X=[None] * 3, scan_onsets=scan_onsets) - assert ts == [None] * 3 and ts0 == [None] * \ - 3, "transform did not return list of None when data is None" - - -def test_gradient(): - from brainiak.reprsimil.brsa import GBRSA - import brainiak.utils.utils as utils - import numpy as np - import os.path - import numdifftools as nd - - np.random.seed(100) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 1, 2, and 3 times, mimicking different length - # of experiments for different participants - n_run = [1, 2, 1] - design_mat = [None] * 3 - n_T = [None] * 3 - n_V = [30, 30, 20] - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - n_T[i] = n_run[i] * design.n_TR - - # start simulating some data - n_C = np.size(design_mat[0], axis=1) - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = [None] * 3 - for i in range(3): - noise_level[i] = np.random.rand( - n_V[i]) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = [None] * 3 - - # generating noise - noise = [None] * 3 - - # baseline - inten = [None] * 3 - for i in range(3): - rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot - noise[i] = np.zeros([n_T[i], n_V[i]]) - noise[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ - np.random.randn(n_V[i]) * noise_level[i] - noise[i] = noise[i] + \ - np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) - inten[i] = np.random.rand(n_V[i]) * 20.0 - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_top = 5.0 # test with high SNR - snr_bot = 1.0 - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - - snr = [None] * 3 - signal = [None] * 3 - betas_simulated = [None] * 3 - scan_onsets = [None] * 3 - Y = [None] * 3 - for i in range(3): - snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot - sqrt_v = noise_level[i] * snr[i] - betas_simulated[i] = np.dot( - L_full, np.random.randn(n_C, n_V[i])) * sqrt_v - signal[i] = np.dot(design_mat[i], betas_simulated[i]) - - # Adding noise to signal as data - Y[i] = signal[i] + noise[i] + inten[i] - - scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) - - # Get some initial fitting. - SNR_bins = 11 - rho_bins = 20 - gbrsa = GBRSA(n_iter=3, rank=n_C, SNR_bins=SNR_bins, - rho_bins=rho_bins, logS_range=0.5) - - n_grid = SNR_bins * rho_bins - half_log_det_X0TAX0 = [np.random.randn(n_grid) for i in range(3)] - log_weights = np.random.randn(n_grid) - log_fixed_terms = [np.random.randn(n_grid) for i in range(3)] - l_idx = np.tril_indices(n_C) - L_vec = np.random.randn(int(n_C * (n_C + 1) / 2)) - n_X0 = [2, 2, 2] - s = np.linspace(1, SNR_bins, n_grid) - a = np.linspace(0.5, 1, n_grid) - s2XTAcorrX = [None] * 3 - YTAcorrY_diag = [None] * 3 - sXTAcorrY = [None] * 3 - # The calculations below are quite arbitrary and do not conform - # to the model. They simply conform to the symmetry property and shape of - # the matrix indicated by the model - for i in range(3): - YTAcorrY_diag[i] = np.sum(Y[i] * Y[i], axis=0) * a[:, None] - s2XTAcorrX[i] = np.dot(design_mat[i].T, design_mat[ - i]) * s[:, None, None]**2 * a[:, None, None] - sXTAcorrY[i] = np.dot(design_mat[i].T, Y[i]) * \ - s[:, None, None] * a[:, None, None] - - # test if the gradients are correct - print(log_fixed_terms) - ll0, deriv0 = gbrsa._sum_loglike_marginalized(L_vec, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, - log_weights, log_fixed_terms, - l_idx, n_C, n_T, n_V, n_X0, - n_grid, rank=None) - # We test the gradient to the Cholesky factor - vec = np.random.randn(np.size(L_vec)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: gbrsa._sum_loglike_marginalized(x, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, - half_log_det_X0TAX0, - log_weights, log_fixed_terms, - l_idx, n_C, n_T, n_V, n_X0, - n_grid, rank=None)[0], - L_vec, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), 'gradient incorrect' - - -def test_SNR_grids(): - import brainiak.reprsimil.brsa - import numpy as np - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='unif', SNR_bins=10) - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.isclose(np.sum(SNR_weights), 1) - and np.isclose(np.std(SNR_weights[1:-1]), 0) - and np.all(SNR_weights > 0) - and np.isclose(np.min(SNR_grids), 0) - and np.all(SNR_grids >= 0) - and np.isclose(np.max(SNR_grids), 1) - ), 'SNR_weights or SNR_grids are incorrect for uniform prior' - assert np.isclose(np.ptp(np.diff(SNR_grids[1:-1])), 0), \ - 'SNR grids are not equally spaced for uniform prior' - assert (np.size(SNR_grids) == np.size(SNR_weights) - and np.size(SNR_grids) == 10 - ), ("size of SNR_grids or SNR_weights is not correct for uniform " - "prior") - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='lognorm', SNR_bins=35) - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids >= 0) - and np.isclose(np.sum(SNR_weights), 1) - and np.all(SNR_weights > 0) - and np.all(np.diff(SNR_grids) > 0) - ), 'SNR_grids or SNR_weights not correct for log normal prior' - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='exp') - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids >= 0) - and np.isclose(np.sum(SNR_weights), 1) - and np.all(SNR_weights > 0) - and np.all(np.diff(SNR_grids) > 0) - ), 'SNR_grids or SNR_weights not correct for exponential prior' - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='equal') - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids == 1) - and np.all(SNR_weights == 1) - and np.size(SNR_grids) == 1 - ), 'SNR_grids or SNR_weights not correct for equal prior' - - -def test_n_nureg(): - import brainiak.reprsimil.brsa - import numpy as np - noise = np.dot(np.random.randn(100, 8), np.random.randn( - 8, 30)) + np.random.randn(100, 30) * 0.001 - design = np.random.randn(100, 2) - s = brainiak.reprsimil.brsa.GBRSA(n_iter=2) - s.fit(X=noise, design=design) - assert s.n_nureg_[0] == 8, 'n_nureg_ estimation is wrong in GBRSA' - - -def test_grid_flatten_num_int(): - # Check for numeric integration of SNR, and correctly flattening 2-D grids - # to 1-D grid. - import brainiak.reprsimil.brsa - import brainiak.utils.utils as utils - import numpy as np - import scipy.special - n_V = 30 - n_T = 50 - n_C = 3 - design = np.random.randn(n_T, n_C) - U_simu = np.asarray([[1.0, 0.1, 0.0], [0.1, 1.0, 0.2], [0.0, 0.2, 1.0]]) - L_simu = np.linalg.cholesky(U_simu) - SNR = np.random.exponential(size=n_V) - beta = np.dot(L_simu, np.random.randn(n_C, n_V)) * SNR - noise = np.random.randn(n_T, n_V) - Y = np.dot(design, beta) + noise - X = design - X_base = None - scan_onsets = [0] - - s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, - SNR_prior='exp') - s.fit(X=[Y], design=[design]) - rank = n_C - l_idx, rank = s._chol_idx(n_C, rank) - L = np.zeros((n_C, rank)) - n_l = np.size(l_idx[0]) - current_vec_U_chlsk_l = s.random_state_.randn(n_l) * 10 - L[l_idx] = current_vec_U_chlsk_l - - # Now we change the grids for SNR and rho for testing. - s.SNR_bins = 2 - s.rho_bins = 2 - SNR_grids, SNR_weights = s._set_SNR_grids() - # rho_grids, rho_weights = s._set_rho_grids() - rho_grids = np.ones(2) * 0.1 - rho_weights = np.ones(2) / 2 - # We purposefully set all rhos to be equal to test flattening of - # grids. - n_grid = s.SNR_bins * s.rho_bins - - D, F, run_TRs, n_run = s._prepare_DF( - n_T, scan_onsets=scan_onsets) - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX \ - = s._prepare_data_XY(X, Y, D, F) - X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, \ - X_base, n_X0, idx_DC = s._prepare_data_XYX0( - X, Y, X_base, None, D, F, run_TRs, no_DC=False) - - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] - and half_log_det_X0TAX0[2] == half_log_det_X0TAX0[3] - and half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] - ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' - assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) - and np.array_equal(X0TAX0[2, :, :], X0TAX0[3, :, :]) - and np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0' - assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) - and np.array_equal(X0TAX0_i[2, :, :], X0TAX0_i[3, :, :]) - and np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0_i' - assert np.allclose( - np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), - np.eye(n_X0) - ), 'X0TAX0_i is not inverse of X0TAX0' - assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[1, :]) - and np.array_equal(YTAcorrY_diag[2, :], YTAcorrY_diag[3, :]) - and np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) - ), '_matrix_flattened_grid has mistake YTAcorrY_diag' - assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) - and np.array_equal(sXTAcorrY[2, :, :], sXTAcorrY[3, :, :]) - and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) - ), '_matrix_flattened_grid has mistake sXTAcorrY' - assert (np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) - and np.array_equal(X0TAY[2, :, :], X0TAY[3, :, :]) - and np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAY' - assert (np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) - and np.array_equal(XTAX0[2, :, :], XTAX0[3, :, :]) - and np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) - ), '_matrix_flattened_grid has mistake XTAX0' - - # Now we test the other way - rho_grids, rho_weights = s._set_rho_grids() - # rho_grids, rho_weights = s._set_rho_grids() - SNR_grids = np.ones(2) * 0.1 - SNR_weights = np.ones(2) / 2 - # We purposefully set all SNR to be equal to test flattening of - # grids. - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] - and half_log_det_X0TAX0[1] == half_log_det_X0TAX0[3] - and not half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] - ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' - assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) - and np.array_equal(X0TAX0[1, :, :], X0TAX0[3, :, :]) - and not np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0' - assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) - and np.array_equal(X0TAX0_i[1, :, :], X0TAX0_i[3, :, :]) - and not np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0_i' - assert np.allclose( - np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), - np.eye(n_X0) - ), 'X0TAX0_i is not inverse of X0TAX0' - assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) - and np.array_equal(YTAcorrY_diag[1, :], YTAcorrY_diag[3, :]) - and not np.array_equal(YTAcorrY_diag[0, :], - YTAcorrY_diag[1, :]) - ), '_matrix_flattened_grid has mistake YTAcorrY_diag' - assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) - and np.array_equal(sXTAcorrY[1, :, :], sXTAcorrY[3, :, :]) - and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) - ), '_matrix_flattened_grid has mistake sXTAcorrY' - assert (np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) - and np.array_equal(X0TAY[1, :, :], X0TAY[3, :, :]) - and not np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAY' - assert (np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) - and np.array_equal(XTAX0[1, :, :], XTAX0[3, :, :]) - and not np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) - ), '_matrix_flattened_grid has mistake XTAX0' - - # Now test the integration over SNR - s.SNR_bins = 50 - s.rho_bins = 1 - SNR_grids, SNR_weights = s._set_SNR_grids() - rho_grids, rho_weights = s._set_rho_grids() - n_grid = s.SNR_bins * s.rho_bins - - def setup_for_test(): - # This function will be re-used to set up the variables necessary for - # testing. - - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - log_weights = np.reshape( - np.log(SNR_weights[:, None]) + np.log(rho_weights), n_grid) - all_rho_grids = np.reshape(np.repeat( - rho_grids[None, :], s.SNR_bins, axis=0), n_grid) - log_fixed_terms = - (n_T - n_X0) / 2 * np.log(2 * np.pi) + n_run \ - / 2 * np.log(1 - all_rho_grids**2) + scipy.special.gammaln( - (n_T - n_X0 - 2) / 2) + (n_T - n_X0 - 2) / 2 * np.log(2) - return s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, \ - log_weights, log_fixed_terms - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, log_weights, - log_fixed_terms, l_idx, n_C, n_T, - n_V, n_X0, n_grid, rank=rank) - LL_total = - LL_total - # Now we re-calculate using scipy.integrate - s.SNR_bins = 100 - SNR_grids = np.linspace(0, 12, s.SNR_bins) - SNR_weights = np.exp(- SNR_grids) - SNR_weights = SNR_weights / np.sum(SNR_weights) - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, half_log_det_X0TAX0, - log_weights, log_fixed_terms, - n_C, n_T, n_V, n_X0, - n_grid, rank) - result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) - scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) - LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) - - tol = 1e-3 - assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ - 'Error of log likelihood calculation exceeds the tolerance' - - # Now test the log normal prior - s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, - SNR_prior='lognorm') - s.SNR_bins = 50 - s.rho_bins = 1 - SNR_grids, SNR_weights = s._set_SNR_grids() - rho_grids, rho_weights = s._set_rho_grids() - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, log_weights, - log_fixed_terms, l_idx, n_C, n_T, - n_V, n_X0, n_grid, rank=rank) - LL_total = - LL_total - # Now we re-calculate using scipy.integrate - s.SNR_bins = 400 - SNR_grids = np.linspace(1e-8, 20, s.SNR_bins) - log_SNR_weights = scipy.stats.lognorm.logpdf(SNR_grids, s=s.logS_range) - result_sum, max_value, result_exp = utils.sumexp_stable( - log_SNR_weights[:, None]) - SNR_weights = np.squeeze(result_exp / result_sum) - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, half_log_det_X0TAX0, - log_weights, log_fixed_terms, - n_C, n_T, n_V, n_X0, - n_grid, rank) - result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) - scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) - LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) - - tol = 1e-3 - assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ - 'Error of log likelihood calculation exceeds the tolerance' diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py deleted file mode 100644 index 7d472e6f5..000000000 --- a/tests/searchlight/test_searchlight.py +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections import namedtuple - -import numpy as np -from mpi4py import MPI - -from brainiak.searchlight.searchlight import Searchlight -from brainiak.searchlight.searchlight import Diamond, Ball - -"""Distributed Searchlight Test -""" - - -def cube_sfn(l, msk, myrad, bcast_var): - if np.all(msk) and np.any(msk): - return 1.0 - return None - - -def test_searchlight_with_cube(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = True - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(cube_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def test_searchlight_with_cube_poolsize_1(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = True - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(cube_sfn, pool_size=1) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def diamond_sfn(l, msk, myrad, bcast_var): - assert not np.any(msk[~Diamond(3).mask_]) - if np.all(msk[Diamond(3).mask_]): - return 1.0 - return None - - -def test_searchlight_with_diamond(): - sl = Searchlight(sl_rad=3, shape=Diamond) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = Diamond(3).mask_ - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(diamond_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def ball_sfn(l, msk, myrad, bcast_var): - x, y, z = np.mgrid[-myrad:myrad+1, -myrad:myrad+1, -myrad:myrad+1] - correct_mask = np.square(x) + np.square(y) + np.square(z) <= myrad ** 2 - assert not np.any(msk[~Ball(3).mask_]) - if np.all(correct_mask == msk): - return 1.0 - return None - - -def test_searchlight_with_ball(): - sl = Searchlight(sl_rad=3, shape=Ball) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = Ball(3).mask_ - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(ball_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -MaskRadBcast = namedtuple("MaskRadBcast", "mask rad") - - -def test_instantiate(): - sl = Searchlight(sl_rad=5, max_blk_edge=10) - assert sl - - -def voxel_test_sfn(l, msk, myrad, bcast): - rad = bcast.rad - # Check each point - for subj in l: - for _tr in range(subj.shape[3]): - tr = subj[:, :, :, _tr] - midpt = tr[rad, rad, rad] - for d0 in range(tr.shape[0]): - for d1 in range(tr.shape[1]): - for d2 in range(tr.shape[2]): - assert np.array_equal(tr[d0, d1, d2] - midpt, - np.array([d0-rad, d1-rad, - d2-rad, 0])) - - # Determine midpoint - midpt = l[0][rad, rad, rad, 0] - midpt = (midpt[0], midpt[1], midpt[2]) - - for d0 in range(msk.shape[0]): - for d1 in range(msk.shape[1]): - for d2 in range(msk.shape[2]): - pt = (midpt[0] - rad + d0, midpt[1] - rad + d1, - midpt[2] - rad + d2) - assert bcast.mask[pt] == msk[d0, d1, d2] - - # Return midpoint - return midpt - - -def block_test_sfn(l, msk, myrad, bcast_var, extra_params): - outmat = l[0][:, :, :, 0] - outmat[~msk] = None - return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] - - -def test_correctness(): # noqa: C901 - def voxel_test(data, mask, max_blk_edge, rad): - - comm = MPI.COMM_WORLD - rank = comm.rank - - (dim0, dim1, dim2) = mask.shape - - # Initialize dataset with known pattern - for subj in data: - if subj is not None: - for tr in range(subj.shape[3]): - for d1 in range(dim0): - for d2 in range(dim1): - for d3 in range(dim2): - subj[d1, d2, d3, tr] = np.array( - [d1, d2, d3, tr]) - - sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) - sl.distribute(data, mask) - sl.broadcast(MaskRadBcast(mask, rad)) - global_outputs = sl.run_searchlight(voxel_test_sfn) - - if rank == 0: - for d0 in range(rad, global_outputs.shape[0]-rad): - for d1 in range(rad, global_outputs.shape[1]-rad): - for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: - assert np.array_equal( - np.array(global_outputs[d0, d1, d2]), - np.array([d0, d1, d2])) - - def block_test(data, mask, max_blk_edge, rad): - - comm = MPI.COMM_WORLD - rank = comm.rank - - (dim0, dim1, dim2) = mask.shape - - # Initialize dataset with known pattern - for subj in data: - if subj is not None: - for tr in range(subj.shape[3]): - for d1 in range(dim0): - for d2 in range(dim1): - for d3 in range(dim2): - subj[d1, d2, d3, tr] = np.array( - [d1, d2, d3, tr]) - - sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) - sl.distribute(data, mask) - sl.broadcast(mask) - global_outputs = sl.run_block_function(block_test_sfn) - - if rank == 0: - for d0 in range(rad, global_outputs.shape[0]-rad): - for d1 in range(rad, global_outputs.shape[1]-rad): - for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: - assert np.array_equal( - np.array(global_outputs[d0, d1, d2]), - np.array([d0, d1, d2, 0])) - - # Create dataset - def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - mask = np.random.choice([True, False], (dim0, dim1, dim2)) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - voxel_test(data, mask, max_blk_edge, rad) - block_test(data, mask, max_blk_edge, rad) - - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=1) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=1, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=0, dim1=10, dim2=8, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) diff --git a/tests/utils/example_design.1D b/tests/utils/example_design.1D deleted file mode 100755 index 7385edea6..000000000 --- a/tests/utils/example_design.1D +++ /dev/null @@ -1,272 +0,0 @@ -# - 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 - 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 - 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 - 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 - 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 - 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 - 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 - 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 - 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 - 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 - 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 - 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 - 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 - 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 - 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 - 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 - 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 - 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 - 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 - 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 - 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 - 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 - 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 - 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 - 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 - 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 - 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 - 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 - 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 - 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 - 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 - 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 - 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 - 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 - 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 - 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 - 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 - 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 - 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 - 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 - 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 - 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 - 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 - 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 - 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 - 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 - 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 - 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 - 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 - 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 - 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 - 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 - 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 - 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 - 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 - 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 - 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 - 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 - 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 - 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 - 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 - 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 - 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 - 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 - 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 - 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 - 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 - 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 - 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 - 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 - 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 - 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 - 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 - 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 - 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 - 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 - 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 - 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 - 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 - 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 - 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 - 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 - 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 - 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 - 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 - 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 - 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 - 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 - 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 - 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 - 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 - 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 - 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 - 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 - 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 - 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 - 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 - 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 - 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 - 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 - 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 - 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 - 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 - 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 - 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 - 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 - 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 - 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 - 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 - 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 - 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 - 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 - 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 - 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 - 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 - 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 - 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 - 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 - 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 - 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 - 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 - 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 - 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 - 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 - 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 - 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 - 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 - 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 - 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 - 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 - 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 - 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 - 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 - 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 - 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 - 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 - 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 - 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 - 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 - 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 - 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 - 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 - 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 - 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 - 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 - 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 - 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 - 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 - 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 - 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 - 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 - 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 - 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 - 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 - 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 - 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 - 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 - 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 - 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 - 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 - 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 - 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 - 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 - 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 - 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 - 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 - 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 - 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 - 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 - 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 - 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 - 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 - 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 - 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 - 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 - 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 - 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 - 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 - 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 - 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 - 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 - 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 - 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 - 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 - 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 - 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 -# - diff --git a/tests/utils/example_stimtime_1_AFNI.txt b/tests/utils/example_stimtime_1_AFNI.txt deleted file mode 100644 index 48200041d..000000000 --- a/tests/utils/example_stimtime_1_AFNI.txt +++ /dev/null @@ -1,2 +0,0 @@ -5.2*2.0:2.0 40.0*4.0:1.5 -2.0*2.0:1.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_1_FSL.txt b/tests/utils/example_stimtime_1_FSL.txt deleted file mode 100644 index ce14b1d55..000000000 --- a/tests/utils/example_stimtime_1_FSL.txt +++ /dev/null @@ -1,3 +0,0 @@ -5.2 2.0 2.0 -40.0 1.5 4.0 -50.0 1.0 2.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_2_AFNI.txt b/tests/utils/example_stimtime_2_AFNI.txt deleted file mode 100644 index 39d03d9ee..000000000 --- a/tests/utils/example_stimtime_2_AFNI.txt +++ /dev/null @@ -1 +0,0 @@ --10.0 diff --git a/tests/utils/example_stimtime_2_FSL.txt b/tests/utils/example_stimtime_2_FSL.txt deleted file mode 100644 index e60beb2a6..000000000 --- a/tests/utils/example_stimtime_2_FSL.txt +++ /dev/null @@ -1,3 +0,0 @@ -5.2 2.0 -40.0 1.5 2.0 -50.0 \ No newline at end of file diff --git a/tests/utils/test_fmrisim.py b/tests/utils/test_fmrisim.py deleted file mode 100644 index 7c442661a..000000000 --- a/tests/utils/test_fmrisim.py +++ /dev/null @@ -1,858 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""fMRI Simulator test script - -Test script for generating a run of a participant's data. - - Authors: Cameron Ellis (Princeton) 2016 -""" -import numpy as np -import math -from brainiak.utils import fmrisim as sim -import pytest -from itertools import product - - -def test_generate_signal(): - - # Inputs for generate_signal - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [3] - feature_type = ['cube'] - feature_coordinates = np.array([[5, 5, 5]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - assert np.all(volume.shape == dimensions), "Check signal shape" - assert np.max(volume) == signal_magnitude, "Check signal magnitude" - assert np.sum(volume > 0) == math.pow(feature_size[0], 3), ( - "Check feature size") - assert volume[5, 5, 5] == signal_magnitude, "Check signal location" - assert volume[5, 5, 1] == 0, "Check noise location" - - feature_coordinates = np.array( - [[5, 5, 5], [3, 3, 3], [7, 7, 7]]) - - # Check feature size is correct - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=['loop', 'cavity', 'sphere'], - feature_size=[3], - signal_magnitude=signal_magnitude) - assert volume[5, 5, 5] == 0, "Loop is empty" - assert volume[3, 3, 3] == 0, "Cavity is empty" - assert volume[7, 7, 7] != 0, "Sphere is not empty" - - # Check feature size manipulation - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=['loop', 'cavity', 'sphere'], - feature_size=[1], - signal_magnitude=signal_magnitude) - assert volume[5, 6, 6] == 0, "Loop is too big" - assert volume[3, 5, 5] == 0, "Cavity is too big" - assert volume[7, 9, 9] == 0, "Sphere is too big" - - # Check that out of bounds feature coordinates are corrected - feature_coordinates = np.array([0, 2, dimensions[2]]) - x, y, z = sim._insert_idxs(feature_coordinates, feature_size[0], - dimensions) - assert x[1] - x[0] == 2, "x min not corrected" - assert y[1] - y[0] == 3, "y was corrected when it shouldn't be" - assert z[1] - z[0] == 1, "z max not corrected" - - # Check that signal patterns are created - feature_coordinates = np.array([[5, 5, 5]]) - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - signal_constant=0, - ) - assert volume[4:7, 4:7, 4:7].std() > 0, "Signal is constant" - - -def test_generate_stimfunction(): - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 100 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - assert stimfunction.shape[0] == duration * 100, "stimfunc incorrect length" - eventNumber = np.sum(event_durations * len(onsets)) * 100 - assert np.sum(stimfunction) == eventNumber, "Event number" - - # Create the signal function - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - stim_dur = stimfunction.shape[0] / (tr_duration * 100) - assert signal_function.shape[0] == stim_dur, "The length did not change" - - # Test - onsets = [0] - tr_duration = 1 - event_durations = [1] - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - max_response = np.where(signal_function != 0)[0].max() - assert 25 < max_response <= 30, "HRF has the incorrect length" - assert np.sum(signal_function < 0) > 0, "No values below zero" - - # Export a stimfunction - sim.export_3_column(stimfunction, - 'temp.txt', - ) - - # Load in the stimfunction - stimfunc_new = sim.generate_stimfunction(onsets=None, - event_durations=None, - total_time=duration, - timing_file='temp.txt', - ) - - assert np.all(stimfunc_new == stimfunction), "Export/import failed" - - # Break the timing precision of the generation - stimfunc_new = sim.generate_stimfunction(onsets=None, - event_durations=None, - total_time=duration, - timing_file='temp.txt', - temporal_resolution=0.5, - ) - - assert stimfunc_new.sum() == 0, "Temporal resolution not working right" - - # Set the duration to be too short so you should get an error - onsets = [10, 30, 50, 70, 90] - event_durations = [5] - with pytest.raises(ValueError): - sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=89, - ) - - # Clip the event offset - stimfunc_new = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=95, - ) - assert stimfunc_new[-1] == 1, 'Event offset was not clipped' - - # Test exporting a group of participants to an epoch file - cond_a = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=110, - ) - - cond_b = sim.generate_stimfunction(onsets=[x + 5 for x in onsets], - event_durations=event_durations, - total_time=110, - ) - - stimfunction_group = [np.hstack((cond_a, cond_b))] * 2 - sim.export_epoch_file(stimfunction_group, - 'temp.txt', - tr_duration, - ) - - # Check that convolve throws a warning when the shape is wrong - sim.convolve_hrf(stimfunction=np.hstack((cond_a, cond_b)).T, - tr_duration=tr_duration, - temporal_resolution=1, - ) - - -def test_apply_signal(): - - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[5, 5, 5]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 100 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - # Check that you can compute signal change appropriately - # Preset a bunch of things - stimfunction_tr = stimfunction[::int(tr_duration * 100)] - mask, template = sim.mask_brain(dimensions, mask_self=False) - noise_dict = sim._noise_dict_update({}) - noise = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=noise_dict, - iterations=[0, 0] - ) - coords = feature_coordinates[0] - noise_function_a = noise[coords[0], coords[1], coords[2], :] - noise_function_a = noise_function_a.reshape(duration // tr_duration, 1) - - noise_function_b = noise[coords[0] + 1, coords[1], coords[2], :] - noise_function_b = noise_function_b.reshape(duration // tr_duration, 1) - - # Create the calibrated signal with PSC - method = 'PSC' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - sig_b = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [1.0], - method, - ) - - assert sig_b.max() / sig_a.max() == 2, 'PSC modulation failed' - - # Create the calibrated signal with SFNR - method = 'SFNR' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a / (noise_function_a.mean() / noise_dict['sfnr']) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b / (noise_function_b.mean() / noise_dict['sfnr']) - - assert scaled_b.max() / scaled_a.max() == 2, 'SFNR modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-SD - method = 'CNR_Amp/Noise-SD' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a / noise_function_a.std() - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b / noise_function_b.std() - - assert scaled_b.max() / scaled_a.max() == 2, 'CNR_Amp modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-Var_dB - method = 'CNR_Amp2/Noise-Var_dB' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = np.log(sig_a.max() / noise_function_a.std()) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = np.log(sig_b.max() / noise_function_b.std()) - - assert np.round(scaled_b / scaled_a) == 2, 'CNR_Amp dB modulation failed' - - # Create the calibrated signal with CNR_Signal-SD/Noise-SD - method = 'CNR_Signal-SD/Noise-SD' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a.std() / noise_function_a.std() - sig_b = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b.std() / noise_function_a.std() - - assert (scaled_b / scaled_a) == 2, 'CNR signal modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-Var_dB - method = 'CNR_Signal-Var/Noise-Var_dB' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - - scaled_a = np.log(sig_a.std() / noise_function_a.std()) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = np.log(sig_b.std() / noise_function_b.std()) - - assert np.round(scaled_b / scaled_a) == 2, 'CNR signal dB modulation ' \ - 'failed' - - # Convolve the HRF with the stimulus sequence - signal = sim.apply_signal(signal_function=signal_function, - volume_signal=volume, - ) - - assert signal.shape == (dimensions[0], dimensions[1], dimensions[2], - duration / tr_duration), "The output is the " \ - "wrong size" - - signal = sim.apply_signal(signal_function=stimfunction, - volume_signal=volume, - ) - - assert np.any(signal == signal_magnitude), "The stimfunction is not binary" - - # Check that there is an error if the number of signal voxels doesn't - # match the number of non zero brain voxels - with pytest.raises(IndexError): - sig_vox = (volume > 0).sum() - vox_pattern = np.tile(stimfunction, (1, sig_vox - 1)) - sim.apply_signal(signal_function=vox_pattern, - volume_signal=volume, - ) - - -def test_generate_noise(): - - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[5, 5, 5]]) - signal_magnitude = [1] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 200 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - # Convolve the HRF with the stimulus sequence - signal = sim.apply_signal(signal_function=signal_function, - volume_signal=volume, - ) - - # Generate the mask of the signal - mask, template = sim.mask_brain(signal, - mask_self=None) - - assert min(mask[mask > 0]) > 0.1, "Mask thresholding did not work" - assert len(np.unique(template) > 2), "Template creation did not work" - - stimfunction_tr = stimfunction[::int(tr_duration * 100)] - - # Create the noise volumes (using the default parameters) - noise = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - iterations=[1, 0], - ) - - assert signal.shape == noise.shape, "The dimensions of signal and noise " \ - "the same" - - noise_high = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={'sfnr': 50, 'snr': 25}, - iterations=[1, 0], - ) - - noise_low = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={'sfnr': 100, 'snr': 25}, - iterations=[1, 0], - ) - - system_high = np.std(noise_high[mask > 0], 1).mean() - system_low = np.std(noise_low[mask > 0], 1).mean() - - assert system_low < system_high, "SFNR noise could not be manipulated" - - # Check that you check for the appropriate template values - with pytest.raises(ValueError): - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template * 2, - mask=mask, - noise_dict={}, - ) - - # Check that iterations does what it should - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={}, - iterations=[0, 0], - ) - - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={}, - iterations=None, - ) - - # Test drift noise - trs = 1000 - period = 100 - drift = sim._generate_noise_temporal_drift(trs, - tr_duration, - 'sine', - period, - ) - - # Check that the max frequency is the appropriate frequency - power = abs(np.fft.fft(drift))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs - period_freq = np.where(freq == 1 / (period // tr_duration)) - max_freq = np.argmax(power) - - assert period_freq == max_freq, 'Max frequency is not where it should be' - - # Do the same but now with cosine basis functions, answer should be close - drift = sim._generate_noise_temporal_drift(trs, - tr_duration, - 'discrete_cos', - period, - ) - - # Check that the appropriate frequency is peaky (may not be the max) - power = abs(np.fft.fft(drift))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs - period_freq = np.where(freq == 1 / (period // tr_duration))[0][0] - - assert power[period_freq] > power[period_freq + 1], 'Power is low' - assert power[period_freq] > power[period_freq - 1], 'Power is low' - - # Check it gives a warning if the duration is too short - drift = sim._generate_noise_temporal_drift(50, - tr_duration, - 'discrete_cos', - period, - ) - - # Test physiological noise (using unrealistic parameters so that it's easy) - timepoints = list(np.linspace(0, (trs - 1) * tr_duration, trs)) - resp_freq = 0.2 - heart_freq = 1.17 - phys = sim._generate_noise_temporal_phys(timepoints, - resp_freq, - heart_freq, - ) - - # Check that the max frequency is the appropriate frequency - power = abs(np.fft.fft(phys))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / (trs * tr_duration) - peaks = (power > (power.mean() + power.std())) # Where are the peaks - peak_freqs = freq[peaks] - - assert np.any(resp_freq == peak_freqs), 'Resp frequency not found' - assert len(peak_freqs) == 2, 'Two peaks not found' - - # Test task noise - sim._generate_noise_temporal_task(stimfunction_tr, - motion_noise='gaussian', - ) - sim._generate_noise_temporal_task(stimfunction_tr, - motion_noise='rician', - ) - - # Test ARMA noise - with pytest.raises(ValueError): - noise_dict = {'fwhm': 4, 'auto_reg_rho': [1], 'ma_rho': [1, 1]} - sim._generate_noise_temporal_autoregression(stimfunction_tr, - noise_dict, - dimensions, - mask, - ) - - # Generate spatial noise - vol = sim._generate_noise_spatial(np.array([10, 10, 10, trs])) - assert len(vol.shape) == 3, 'Volume was not reshaped to ignore TRs' - - # Switch some of the noise types on - noise_dict = dict(physiological_sigma=1, drift_sigma=1, task_sigma=1, - auto_reg_sigma=0) - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=noise_dict, - iterations=[0, 0], - ) - - -def test_generate_noise_spatial(): - - # Set up the inputs - dimensions = np.array([10, 5, 10]) - mask = np.ones(dimensions) - vol = sim._generate_noise_spatial(dimensions, mask) - - # Run the analysis from _calc_FHWM but for th elast step of aggregating - # across dimensions - v_count = 0 - v_sum = 0 - v_sq = 0 - - d_sum = [0.0, 0.0, 0.0] - d_sq = [0.0, 0.0, 0.0] - d_count = [0, 0, 0] - - # Pull out all the voxel coordinates - coordinates = list(product(range(dimensions[0]), - range(dimensions[1]), - range(dimensions[2]))) - - # Find the sum of squared error for the non-masked voxels in the brain - for i in list(range(len(coordinates))): - - # Pull out this coordinate - x, y, z = coordinates[i] - - # Is this within the mask? - if mask[x, y, z] > 0: - - # Find the the volume sum and squared values - v_count += 1 - v_sum += vol[x, y, z] - v_sq += vol[x, y, z] ** 2 - - # Get the volume variance - v_var = (v_sq - ((v_sum ** 2) / v_count)) / (v_count - 1) - - for i in list(range(len(coordinates))): - - # Pull out this coordinate - x, y, z = coordinates[i] - - # Is this within the mask? - if mask[x, y, z] > 0: - # For each xyz dimension calculate the squared - # difference of this voxel and the next - - in_range = (x < dimensions[0] - 1) - in_mask = in_range and (mask[x + 1, y, z] > 0) - included = in_mask and (~np.isnan(vol[x + 1, y, z])) - if included: - d_sum[0] += vol[x, y, z] - vol[x + 1, y, z] - d_sq[0] += (vol[x, y, z] - vol[x + 1, y, z]) ** 2 - d_count[0] += 1 - - in_range = (y < dimensions[1] - 1) - in_mask = in_range and (mask[x, y + 1, z] > 0) - included = in_mask and (~np.isnan(vol[x, y + 1, z])) - if included: - d_sum[1] += vol[x, y, z] - vol[x, y + 1, z] - d_sq[1] += (vol[x, y, z] - vol[x, y + 1, z]) ** 2 - d_count[1] += 1 - - in_range = (z < dimensions[2] - 1) - in_mask = in_range and (mask[x, y, z + 1] > 0) - included = in_mask and (~np.isnan(vol[x, y, z + 1])) - if included: - d_sum[2] += vol[x, y, z] - vol[x, y, z + 1] - d_sq[2] += (vol[x, y, z] - vol[x, y, z + 1]) ** 2 - d_count[2] += 1 - - # Find the variance - d_var = np.divide((d_sq - np.divide(np.power(d_sum, 2), - d_count)), (np.add(d_count, -1))) - - o_var = np.divide(-1, (4 * np.log(1 - (0.5 * d_var / v_var)))) - fwhm3 = np.sqrt(o_var) * 2 * np.sqrt(2 * np.log(2)) - - # Calculate the proportion of std relative to the mean - std_proportion = np.nanstd(fwhm3) / np.nanmean(fwhm3) - print(fwhm3) - assert std_proportion < 0.25, 'Variance is inconsistent across dim' - - -def test_mask_brain(): - - # Inputs for generate_signal - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[4, 4, 4]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Mask the volume to be the same shape as a brain - mask, _ = sim.mask_brain(dimensions, mask_self=None,) - brain = volume * mask - - assert np.sum(brain != 0) == np.sum(volume != 0), "Masking did not work" - assert brain[0, 0, 0] == 0, "Masking did not work" - assert brain[4, 4, 4] != 0, "Masking did not work" - - feature_coordinates = np.array( - [[1, 1, 1]]) - - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Mask the volume to be the same shape as a brain - mask, _ = sim.mask_brain(dimensions, mask_self=None, ) - brain = volume * mask - - assert np.sum(brain != 0) < np.sum(volume != 0), "Masking did not work" - - # Test that you can load the default - dimensions = np.array([100, 100, 100]) - mask, template = sim.mask_brain(dimensions, mask_self=False) - - assert mask[20, 80, 50] == 0, 'Masking didn''t work' - assert mask[25, 80, 50] == 1, 'Masking didn''t work' - assert int(template[25, 80, 50] * 100) == 57, 'Template not correct' - - # Check that you can mask self - mask_self, template_self = sim.mask_brain(template, mask_self=True) - - assert (template_self - template).sum() < 1e2, 'Mask self error' - assert (mask_self - mask).sum() == 0, 'Mask self error' - - -def test_calc_noise(): - - # Inputs for functions - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 200 - temporal_res = 100 - tr_number = int(np.floor(duration / tr_duration)) - dimensions_tr = np.array([10, 10, 10, tr_number]) - - # Preset the noise dict - nd_orig = sim._noise_dict_update({}) - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - temporal_resolution=temporal_res, - ) - - # Mask the volume to be the same shape as a brain - mask, template = sim.mask_brain(dimensions_tr, mask_self=None) - stimfunction_tr = stimfunction[::int(tr_duration * temporal_res)] - - nd_orig['matched'] = 0 - noise = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - ) - - # Check the spatial noise match - nd_orig['matched'] = 1 - noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - iterations=[50, 0] - ) - - # Calculate the noise parameters from this newly generated volume - nd_new = sim.calc_noise(noise, mask, template) - nd_matched = sim.calc_noise(noise_matched, mask, template) - - # Check the values are reasonable" - assert nd_new['snr'] > 0, 'snr out of range' - assert nd_new['sfnr'] > 0, 'sfnr out of range' - assert nd_new['auto_reg_rho'][0] > 0, 'ar out of range' - - # Check that the dilation increases SNR - no_dilation_snr = sim._calc_snr(noise_matched, - mask, - dilation=0, - reference_tr=tr_duration, - ) - - assert nd_new['snr'] > no_dilation_snr, "Dilation did not increase SNR" - - # Check that template size is in bounds - with pytest.raises(ValueError): - sim.calc_noise(noise, mask, template * 2) - - # Check that Mask is set is checked - with pytest.raises(ValueError): - sim.calc_noise(noise, None, template) - - # Check that it can deal with missing noise parameters - temp_nd = sim.calc_noise(noise, mask, template, noise_dict={}) - assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' - - temp_nd = sim.calc_noise(noise, mask, template, noise_dict=None) - assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' - - # Check that the fitting worked - snr_diff = abs(nd_orig['snr'] - nd_new['snr']) - snr_diff_match = abs(nd_orig['snr'] - nd_matched['snr']) - assert snr_diff > snr_diff_match, 'snr fit incorrectly' - - # Test that you can generate rician and exponential noise - sim._generate_noise_system(dimensions_tr, - 1, - 1, - spatial_noise_type='exponential', - temporal_noise_type='rician', - ) - - # Check the temporal noise match - nd_orig['matched'] = 1 - noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - iterations=[0, 50] - ) - - nd_matched = sim.calc_noise(noise_matched, mask, template) - - sfnr_diff = abs(nd_orig['sfnr'] - nd_new['sfnr']) - sfnr_diff_match = abs(nd_orig['sfnr'] - nd_matched['sfnr']) - assert sfnr_diff > sfnr_diff_match, 'sfnr fit incorrectly' - - ar1_diff = abs(nd_orig['auto_reg_rho'][0] - nd_new['auto_reg_rho'][0]) - ar1_diff_match = abs(nd_orig['auto_reg_rho'][0] - nd_matched[ - 'auto_reg_rho'][0]) - assert ar1_diff > ar1_diff_match, 'AR1 fit incorrectly' - - # Check that you can calculate ARMA for a single voxel - vox = noise[5, 5, 5, :] - arma = sim._calc_ARMA_noise(vox, - None, - sample_num=2, - ) - assert len(arma) == 2, "Two outputs not given by ARMA" diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py deleted file mode 100644 index f37c51a7e..000000000 --- a/tests/utils/test_utils.py +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - - -def test_tri_sym_convert(): - from brainiak.utils.utils import from_tri_2_sym, from_sym_2_tri - import numpy as np - - sym = np.random.rand(3, 3) - tri = from_sym_2_tri(sym) - assert tri.shape[0] == 6,\ - "from_sym_2_tri returned wrong result!" - sym1 = from_tri_2_sym(tri, 3) - assert sym1.shape[0] == sym1.shape[1],\ - "from_tri_2_sym returned wrong shape!" - tri1 = from_sym_2_tri(sym1) - assert np.array_equiv(tri, tri1),\ - "from_sym_2_tri returned wrong result!" - - -def test_sumexp(): - from brainiak.utils.utils import sumexp_stable - import numpy as np - - data = np.array([[1, 1], [0, 1]]) - sums, maxs, exps = sumexp_stable(data) - assert sums.size == data.shape[1], ( - "Invalid sum(exp(v)) computation (wrong # samples in sums)") - assert exps.shape[0] == data.shape[0], ( - "Invalid exp(v) computation (wrong # features)") - assert exps.shape[1] == data.shape[1], ( - "Invalid exp(v) computation (wrong # samples)") - assert maxs.size == data.shape[1], ( - "Invalid max computation (wrong # samples in maxs)") - - -def test_concatenate_not_none(): - from brainiak.utils.utils import concatenate_not_none - import numpy as np - arrays = [None] * 5 - - arrays[1] = np.array([0, 1, 2]) - arrays[3] = np.array([3, 4]) - - r = concatenate_not_none(arrays, axis=0) - - assert np.all(np.arange(5) == r), ( - "Invalid concatenation of a list of arrays") - - -def test_cov2corr(): - from brainiak.utils.utils import cov2corr - import numpy as np - cov = np.array([[4, 3, 0], [3, 9, 0], [0, 0, 1]]) - corr = cov2corr(cov) - assert np.allclose(corr, - np.array([[1, 0.5, 0], [0.5, 1, 0], [0, 0, 1]])), ( - "Converting from covariance matrix to correlation incorrect") - - -def test_ReadDesign(): - from brainiak.utils.utils import ReadDesign - import numpy as np - import os.path - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - design = ReadDesign(fname=file_path, include_orth=False, - include_pols=False) - assert design, 'Failed to read design matrix' - assert design.reg_nuisance is None, \ - 'Nuiance regressor is not None when include_orth and include_pols are'\ - ' both set to False' - read = ReadDesign() - assert read, 'Failed to initialize an instance of the class' - design = ReadDesign(fname=file_path, include_orth=True, include_pols=True) - assert np.size(design.cols_nuisance) == 10, \ - 'Mistake in counting the number of nuiance regressors' - assert np.size(design.cols_task) == 17, \ - 'Mistake in counting the number of task conditions' - assert (np.shape(design.reg_nuisance)[0] - == np.shape(design.design_task)[0] - ), 'The number of time points in nuiance regressor does not match'\ - ' that of task response' - - -def test_gen_design(): - from brainiak.utils.utils import gen_design - import numpy as np - import os.path - files = {'FSL1': 'example_stimtime_1_FSL.txt', - 'FSL2': 'example_stimtime_2_FSL.txt', - 'AFNI1': 'example_stimtime_1_AFNI.txt', - 'AFNI2': 'example_stimtime_2_AFNI.txt'} - for key in files.keys(): - files[key] = os.path.join(os.path.dirname(__file__), files[key]) - design1 = gen_design(stimtime_files=files['FSL1'], scan_duration=[48, 20], - TR=2, style='FSL') - assert design1.shape == (34, 1), 'Returned design matrix has wrong shape' - assert design1[24] == 0, ( - "gen_design should generated design matrix for each run separately " - "and concatenate them.") - design2 = gen_design(stimtime_files=[files['FSL1'], files['FSL2']], - scan_duration=[48, 20], TR=2, style='FSL') - assert design2.shape == (34, 2), 'Returned design matrix has wrong shape' - design3 = gen_design(stimtime_files=files['FSL1'], scan_duration=68, TR=2, - style='FSL') - assert design3[24] != 0, ( - 'design matrix should be non-zero 8 seconds after an event onset.') - design4 = gen_design(stimtime_files=[files['FSL2']], - scan_duration=[48, 20], TR=2, style='FSL') - assert np.all(np.isclose(design1 * 0.5, design4)), ( - 'gen_design does not treat missing values correctly') - design5 = gen_design(stimtime_files=[files['FSL2']], - scan_duration=[48, 20], TR=1) - assert (np.abs(design4 - design5[::2])).mean() < 0.1, ( - 'design matrices sampled at different frequency do not match' - ' at corresponding time points') - design6 = gen_design(stimtime_files=[files['AFNI1']], - scan_duration=[48, 20], TR=2, style='AFNI') - assert np.all(np.isclose(design1, design6)), ( - 'design matrices generated from AFNI style and FSL style do not match') - design7 = gen_design(stimtime_files=[files['AFNI2']], - scan_duration=[48], TR=2, style='AFNI') - assert np.all(design7 == 0.0), ( - 'A negative stimulus onset of AFNI style should result in an all-zero' - + ' design matrix') - - -def test_center_mass_exp(): - from brainiak.utils.utils import center_mass_exp - import numpy as np - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp([1, 2]) - assert ('interval must be a tuple' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((1, 2, 3)) - assert ('interval must be length two' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((-2, -1)) - assert ('interval_left must be non-negative' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((-2, 3)) - assert ('interval_left must be non-negative' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((3, 3)) - assert ('interval_right must be bigger than interval_left' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((1, 2), -1) - assert ('scale must be positive' - in str(excinfo.value)) - - result = center_mass_exp((0, np.inf), 2.0) - assert np.isclose(result, 2.0), 'center of mass '\ - 'incorrect for the whole distribution' - result = center_mass_exp((1.0, 1.0+2e-10)) - assert np.isclose(result, 1.0+1e-10), 'for a small '\ - 'enough interval, the center of mass should be '\ - 'close to its mid-point' - - -def test_p_from_null(): - import numpy as np - from brainiak.utils.utils import p_from_null - - # Create random null and observed value in tail - null = np.random.randn(10000) - observed = np.ceil(np.percentile(null, 97.5) * 1000) / 1000 - - # Check that we catch improper side - with pytest.raises(ValueError): - _ = p_from_null(observed, null, side='wrong') - - # Check two-tailed p-value for observed - p_ts = p_from_null(observed, null) - assert np.isclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for observed - p_right = p_from_null(observed, null, side='right') - assert np.isclose(p_right, 0.025, atol=1e-02) - assert np.isclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for observed - p_left = p_from_null(observed, null, side='left') - assert np.isclose(p_left, 0.975, atol=1e-02) - assert np.isclose(1 - p_left, p_right, atol=1e-02) - assert np.isclose(1 - p_left, p_ts / 2, atol=1e-02) - - # Check 2-dimensional input (i.e., samples by voxels) - null = np.random.randn(10000, 3) - observed = np.ceil(np.percentile(null, 97.5, axis=0) * 1000) / 1000 - - # Check two-tailed p-value for observed - p_ts = p_from_null(observed, null, axis=0) - assert np.allclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for observed - p_right = p_from_null(observed, null, side='right', axis=0) - assert np.allclose(p_right, 0.025, atol=1e-02) - assert np.allclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for observed - p_left = p_from_null(observed, null, side='left', axis=0) - assert np.allclose(p_left, 0.975, atol=1e-02) - assert np.allclose(1 - p_left, p_right, atol=1e-02) - assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) - - # Check for exact test - p_ts = p_from_null(observed, null, exact=True, axis=0) - assert np.allclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for exact - p_right = p_from_null(observed, null, side='right', - exact=True, axis=0) - assert np.allclose(p_right, 0.025, atol=1e-02) - assert np.allclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for exact - p_left = p_from_null(observed, null, side='left', - exact=True, axis=0) - assert np.allclose(p_left, 0.975, atol=1e-02) - assert np.allclose(1 - p_left, p_right, atol=1e-02) - assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) - - -def test_phase_randomize(): - import numpy as np - from scipy.fftpack import fft - from scipy.stats import pearsonr - from brainiak.utils.utils import phase_randomize - - data = np.repeat(np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], - 30, axis=1), - 20, axis=2) - assert np.array_equal(data[..., 0], data[..., 1]) - - # Phase-randomize data across subjects (same across voxels) - shifted_data = phase_randomize(data, voxelwise=False, random_state=1) - assert shifted_data.shape == data.shape - assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) - assert not np.array_equal(shifted_data[..., 0], data[..., 0]) - - # Check that uneven n_TRs doesn't explode - _ = phase_randomize(data[:-1, ...]) - - # Check that random_state returns same shifts - shifted_data_ = phase_randomize(data, voxelwise=False, random_state=1) - assert np.array_equal(shifted_data, shifted_data_) - - shifted_data_ = phase_randomize(data, voxelwise=False, random_state=2) - assert not np.array_equal(shifted_data, shifted_data_) - - # Phase-randomize subjects and voxels - shifted_data = phase_randomize(data, voxelwise=True, random_state=1) - assert shifted_data.shape == data.shape - assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) - assert not np.array_equal(shifted_data[..., 0], data[..., 0]) - assert not np.array_equal(shifted_data[:, 0, 0], shifted_data[:, 1, 0]) - - # Try with 2-dimensional input - shifted_data = phase_randomize(data[..., 0], - voxelwise=True, - random_state=1) - assert shifted_data.ndim == 2 - assert not np.array_equal(shifted_data[:, 0], shifted_data[:, 1]) - - # Create correlated noisy data - corr_data = np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], - 2, axis=2) + np.random.randn(60, 1, 2) - - # Get correlation and frequency domain for data - corr_r = pearsonr(corr_data[:, 0, 0], - corr_data[:, 0, 1])[0] - corr_freq = fft(corr_data, axis=0) - - # Phase-randomize time series and get correlation/frequency - shifted_data = phase_randomize(corr_data) - shifted_r = pearsonr(shifted_data[:, 0, 0], - shifted_data[:, 0, 1])[0] - shifted_freq = fft(shifted_data, axis=0) - - # Check that phase-randomization reduces correlation - assert np.abs(shifted_r) < np.abs(corr_r) - - # Check that amplitude spectrum is preserved - assert np.allclose(np.abs(shifted_freq), np.abs(corr_freq)) - - -def test_check_timeseries_input(): - import numpy as np - from itertools import combinations - from brainiak.utils.utils import _check_timeseries_input - - # Set a fixed vector for comparison - vector = np.random.randn(60) - - # List of subjects with one voxel/ROI - list_1d = [vector for _ in np.arange(10)] - (data_list_1d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_1d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Array of subjects with one voxel/ROI - array_2d = np.hstack([vector[:, np.newaxis] - for _ in np.arange(10)]) - (data_array_2d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_2d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # List of 2-dimensional arrays - list_2d = [vector[:, np.newaxis] for _ in np.arange(10)] - (data_list_2d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_2d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Check if lists have mismatching size - list_bad = [list_2d[0][:-1, :]] + list_2d[1:] - with pytest.raises(ValueError): - (data_list_bad, _, _, _) = _check_timeseries_input(list_bad) - - # List of 3-dimensional arrays - list_3d = [vector[:, np.newaxis, np.newaxis] - for _ in np.arange(10)] - (data_list_3d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_3d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # 3-dimensional array - array_3d = np.dstack([vector[:, np.newaxis] - for _ in np.arange(10)]) - (data_array_3d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_3d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Check that 4-dimensional input array throws error - array_4d = array_3d[..., np.newaxis] - with pytest.raises(ValueError): - (data_array_4d, _, _, _) = _check_timeseries_input(array_4d) - - # Check they're the same - for pair in combinations([data_list_1d, data_array_2d, - data_list_2d, data_list_3d, - data_array_3d], 2): - assert np.array_equal(pair[0], pair[1]) - - # List of multivoxel arrays - matrix = np.random.randn(60, 30) - list_mv = [matrix - for _ in np.arange(10)] - (data_list_mv, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_mv) - assert n_TRs == 60 - assert n_voxels == 30 - assert n_subjects == 10 - - # 3-dimensional array with multiple voxels - array_mv = np.dstack([matrix for _ in np.arange(10)]) - (data_array_mv, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_mv) - assert n_TRs == 60 - assert n_voxels == 30 - assert n_subjects == 10 - - assert np.array_equal(data_list_mv, data_array_mv) From ca9bdfb97a246f93bf42060655f9ea85c3a54f2e Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 17 Apr 2019 17:57:10 +0200 Subject: [PATCH 04/24] Revert "Add fastSRM algorithm and dependencies" This reverts commit f0135eb0f54a3cd50680a2e000e7c5d401a149cf. --- brainiak/funcalign/fastsrm.py | 824 --------------- requirements-dev.txt | 3 - tests/eventseg/test_event.py | 155 +++ tests/factoranalysis/test_htfa.py | 168 +++ tests/factoranalysis/test_tfa.py | 110 ++ tests/fcma/data/expected_processed_data.npy | Bin 0 -> 2672 bytes tests/fcma/data/expected_raw_data.npy | Bin 0 -> 5264 bytes .../expected_searchlight_processed_data.npy | Bin 0 -> 1704016 bytes tests/fcma/test_classification.py | 222 ++++ tests/fcma/test_mvpa_voxel_selection.py | 51 + tests/fcma/test_preprocessing.py | 109 ++ tests/fcma/test_util.py | 59 ++ tests/fcma/test_voxel_selection.py | 116 +++ tests/hyperparamopt/test_hpo.py | 86 ++ tests/image/test_image.py | 172 ++++ tests/io/data/epoch_labels.npy | Bin 0 -> 160 bytes tests/io/data/mask.nii.gz | Bin 0 -> 634 bytes tests/io/data/subject1_bet.nii.gz | Bin 0 -> 7149 bytes tests/io/data/subject2_bet.nii.gz | Bin 0 -> 7112 bytes tests/io/test_io.py | 106 ++ tests/isc/test_isc.py | 956 ++++++++++++++++++ tests/reprsimil/example_design.1D | 272 +++++ tests/reprsimil/test_brsa.py | 651 ++++++++++++ tests/reprsimil/test_gbrsa.py | 630 ++++++++++++ tests/searchlight/test_searchlight.py | 293 ++++++ tests/utils/example_design.1D | 272 +++++ tests/utils/example_stimtime_1_AFNI.txt | 2 + tests/utils/example_stimtime_1_FSL.txt | 3 + tests/utils/example_stimtime_2_AFNI.txt | 1 + tests/utils/example_stimtime_2_FSL.txt | 3 + tests/utils/test_fmrisim.py | 858 ++++++++++++++++ tests/utils/test_utils.py | 396 ++++++++ 32 files changed, 5691 insertions(+), 827 deletions(-) delete mode 100644 brainiak/funcalign/fastsrm.py create mode 100644 tests/eventseg/test_event.py create mode 100644 tests/factoranalysis/test_htfa.py create mode 100644 tests/factoranalysis/test_tfa.py create mode 100644 tests/fcma/data/expected_processed_data.npy create mode 100644 tests/fcma/data/expected_raw_data.npy create mode 100644 tests/fcma/data/expected_searchlight_processed_data.npy create mode 100644 tests/fcma/test_classification.py create mode 100644 tests/fcma/test_mvpa_voxel_selection.py create mode 100644 tests/fcma/test_preprocessing.py create mode 100644 tests/fcma/test_util.py create mode 100644 tests/fcma/test_voxel_selection.py create mode 100644 tests/hyperparamopt/test_hpo.py create mode 100644 tests/image/test_image.py create mode 100644 tests/io/data/epoch_labels.npy create mode 100755 tests/io/data/mask.nii.gz create mode 100644 tests/io/data/subject1_bet.nii.gz create mode 100644 tests/io/data/subject2_bet.nii.gz create mode 100644 tests/io/test_io.py create mode 100644 tests/isc/test_isc.py create mode 100755 tests/reprsimil/example_design.1D create mode 100755 tests/reprsimil/test_brsa.py create mode 100644 tests/reprsimil/test_gbrsa.py create mode 100644 tests/searchlight/test_searchlight.py create mode 100755 tests/utils/example_design.1D create mode 100644 tests/utils/example_stimtime_1_AFNI.txt create mode 100644 tests/utils/example_stimtime_1_FSL.txt create mode 100644 tests/utils/example_stimtime_2_AFNI.txt create mode 100644 tests/utils/example_stimtime_2_FSL.txt create mode 100644 tests/utils/test_fmrisim.py create mode 100644 tests/utils/test_utils.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py deleted file mode 100644 index c093a1be3..000000000 --- a/brainiak/funcalign/fastsrm.py +++ /dev/null @@ -1,824 +0,0 @@ -"""Fast Shared Response Model (FastSRM) - -The implementations are based on the following publications: - -.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", - P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge - Advances in Neural Information Processing Systems (NIPS), 2015. - http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model - -.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging - Datasets", - Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. - Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, - Kenneth A. Norman, - IEEE International Conference on Big Data, 2016. - https://doi.org/10.1109/BigData.2016.7840719 -""" - -# Author: Hugo Richard (INRIA - Parietal) -# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) and Bertrand Thirion (Inria - Parietal) -# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience Institute) and Javier Turek (Intel Labs) - -import logging - -import numpy as np -import scipy -from sklearn.base import BaseEstimator, TransformerMixin -from sklearn.utils import assert_all_finite -from sklearn.exceptions import NotFittedError -from mpi4py import MPI -import sys -from joblib import Parallel, delayed -import os -import glob -import hashlib - -__all__ = [ - "FastSRM", -] - -logger = logging.getLogger(__name__) - - -def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir=None): - """Reduce data using given atlas - - Parameters - ---------- - - img : str - path to data. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - atlas : array, shape=[n_supervoxels, n_voxels] or None or array, shape=[n_voxels] - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - - inv_atlas : array, shape=[n_voxels, n_supervoxels] - Pseudo inverse of the atlas (only for probabilistic atlases) - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and number of sessions are large - - Returns - ------- - - reduced_data : array, shape=[n_timeframes, n_supervoxels] - reduced data - """ - if atlas is None and inv_atlas is None: - AssertionError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") - - if inv_atlas is None and atlas is not None: - atlas_values = np.unique(atlas) - if 0 in atlas_values: - atlas_values = atlas_values[1:] - data = np.load(img) - reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T - else: - data = np.load(img) - reduced_data = data.dot(inv_atlas) - - if low_ram: - name = hashlib.md5(img.encode()).hexdigest() - path = os.path.join(temp_dir, "reduced_data_" + name) - np.save(path, reduced_data) - return path - else: - return reduced_data - - -def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): - """Reduce data using given atlas. - Work done in parallel across subjects. - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - - n_jobs : integer, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and/or sessions is large - - Returns - ------- - - reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - """ - - if len(atlas.shape) == 2: - A = None - A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) - else: - A = atlas - A_inv = None - - n_subjects, n_sessions = imgs.shape - - reduced_data_list = Parallel(n_jobs=n_jobs)( - delayed(reduce_data_single)( - img, - atlas=A, - inv_atlas=A_inv, - low_ram=low_ram, - temp_dir=temp_dir - ) for img in imgs.flatten()) - - if low_ram: - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) - else: - n_timeframes, n_voxels = reduced_data_list[0].shape - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_voxels)) - - return reduced_data_list - - -def _reduced_space_compute_shared_response(reduced_data_list, - reduced_basis_list, - n_components=50): - """Compute shared response with basis fixed in reduced space - - Parameters - ---------- - - reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - reduced_basis_list : None or list of array, element i has shape=[n_components, n_supervoxels] - each subject's reduced basis - if None the basis will be generated on the fly - - n_components : int or None - number of components - - Returns - ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - - """ - n_subjects, n_sessions = reduced_data_list.shape[:2] - if type(reduced_data_list[0, 0]) == np.ndarray: - low_ram = False - elif type(reduced_data_list[0, 0]) == str: - low_ram = True - else: - AssertionError("Reduced data are stored using type %s which is neither an ndarray or str" - % type(reduced_data_list[0, 0])) - low_ram = False - - s = [None] * n_sessions - - for n in range(n_subjects): - for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] - - if reduced_basis_list is None: - n_timeframes, n_supervoxels = data_nm.shape - reduced_basis_list = [] - for subject in range(n_subjects): - q = np.eye(n_components, n_supervoxels) - reduced_basis_list.append(q) - - basis_n = reduced_basis_list[n] - if s[m] is None: - s[m] = data_nm.dot(basis_n.T) - else: - s[m] = s + data_nm.dot(basis_n.T) - - for m in range(n_sessions): - s[m] = float(s[m]) / n_subjects - - return s - - -def _compute_and_save_corr_mat(img, shared_response, temp_dir): - """computes correlation matrix and stores it - - Parameters - ---------- - img : str - path to data. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - shared_response : array, shape=[n_timeframes, n_components] - shared response - """ - data = np.load(img) - name = hashlib.md5(img.encode()).hexdigest() - path = os.path.join(temp_dir, "corr_mat_" + name) - np.save(path, shared_response.T.dot(data)) - - -def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): - """computes correlation matrix for all sessions - - Parameters - ---------- - - subject_number: int - Number that identifies the subject. Basis will be stored in [temp_dir]/basis_[subject_number].npy - - sessions : array of str - Element i of the array is a path to the data collected during session i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - Returns - ------- - - basis: array, shape=[n_component, n_voxels] or str - basis of subject [subject_number] or path to this basis - """ - corr_mat = None - for session in sessions: - name = hashlib.md5(session.encode()).hexdigest() - path = os.path.join(temp_dir, "corr_mat_" + name + ".npy") - if corr_mat is None: - corr_mat = np.load(path) - else: - corr_mat += np.load(path) - basis_i = _compute_subject_basis(corr_mat) - - if temp_dir is None: - return basis_i - else: - path = os.path.join(temp_dir, "basis_" % subject_number) - np.save(path, basis_i) - return path - - -def _compute_subject_basis(corr_mat): - """From correlation matrix between shared response and subject data, - Finds subject's basis - - Parameters - ---------- - - corr_mat: array, shape=[n_component, n_voxels] or shape=[n_components, n_supervoxels] - correlation matrix between shared response and subject data or subject reduced data - element k, v is given by S.T.dot(X_i) where S is the shared response and - X_i the data of subject i. - - Returns - ------- - - basis: array, shape=[n_components, n_voxels] or shape=[n_components, n_supervoxels] - basis of subject or reduced_basis of subject - """ - if corr_mat.shape[0] == corr_mat.shape[1]: - U, _, V = scipy.linalg.svd( - corr_mat + 1.e-18 * np.eye(corr_mat.shape[0]), - full_matrices=False - ) - else: - U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) - return U.dot(V) - - -def fast_srm(reduced_data_list, n_iter=10, n_components=None): - """Computes shared response and basis in reduced space - - Parameters - ---------- - - reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - n_iter : int - Number of iterations performed - - n_components : int or None - number of components - - Returns - ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - - if type(reduced_data_list[0, 0]) == np.ndarray: - low_ram = False - elif type(reduced_data_list[0, 0]) == str: - low_ram = True - else: - AssertionError("Reduced data are stored using type %s which is neither np.ndarray or str" - % type(reduced_data_list[0, 0])) - low_ram = False - - - n_subjects, n_sessions = reduced_data_list.shape[:2] - shared_response = _reduced_space_compute_shared_response( - reduced_data_list, - None, - n_components - ) - - reduced_basis = [None] * n_subjects - for _ in range(n_iter): - for n in range(n_subjects): - cov = None - for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] - if cov is None: - cov = shared_response[m].T.dot(data_nm) - else: - cov += shared_response[m].T.dot(data_nm) - reduced_basis[n] = _compute_subject_basis(cov) - - shared_response = _reduced_space_compute_shared_response( - reduced_data_list, - reduced_basis, - n_components - ) - - return shared_response - - -def _compute_basis_subject_online(sessions, shared_response_list): - """Computes subject's basis with shared response fixed - - Parameters - ---------- - - sessions : array of str - Element i of the array is a path to the data collected during session i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - - Returns - ------- - - basis: array, shape=[n_components, n_voxels] - basis - """ - - basis_i = None - i = 0 - for session in sessions: - data = np.load(session) - if basis_i is None: - basis_i = shared_response_list[i].T.dot(data) - else: - basis_i += shared_response_list[i].T.dot(data) - i += 1 - del data - return _compute_subject_basis(basis_i) - - -def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subjects_indexes): - """Computes shared response during one session with basis fixed - - Parameters - ---------- - - subjects : array of str - Element i of the array is a path to the data of subject i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - basis_list : None or list of array, element i has shape=[n_components, n_voxels] - basis of all subjects, element i is the basis of subject i - - temp_dir : None or str - path to basis folder where file basis_%i.npy contains the basis of subject i - - subjects_indexes : list of int or None - list of indexes corresponding to the subjects to use to compute shared response - - Returns - ------- - - shared_response : array, shape=[n_timeframes, n_components] - shared response - """ - n = 0 - shared_response = None - for k, i in enumerate(subjects_indexes): - subject = subjects[k] - data = np.load(subject) - if temp_dir is None: - basis_i = basis_list[i] - else: - basis_i = np.load(os.path.join(temp_dir, "basis_%i.npy" % i)) - - if shared_response is None: - shared_response = data.dot(basis_i.T) - else: - shared_response += data.dot(basis_i.T) - - n += 1 - return shared_response / float(n) - - -def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects_indexes): - """Computes shared response with basis fixed - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - basis_list : None or list of array, element i has shape=[n_components, n_voxels] - basis of all subjects, element i is the basis of subject i - - temp_dir : None or str - path to basis folder where file basis_%i.npy contains the basis of subject i - - n_jobs : integer, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. - - subjects_indexes : list or None - list of indexes corresponding to the subjects to use to compute shared response - - Returns - ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - shared_response_list = Parallel(n_jobs=n_jobs)( - delayed(_compute_shared_response_online_single)( - subjects, - basis_list, - temp_dir, - subjects_indexes - ) for subjects in imgs.T) - - return shared_response_list - - -class FastSRM(BaseEstimator, TransformerMixin): - """SRM decomposition using a very low amount of memory and computational power - - Given multi-subject data, factorize it as a shared response S among all - subjects and an orthogonal transform (basis) W per subject: - - .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N - - Parameters - ---------- - - atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - - n_components : int - Number of timecourses of the shared coordinates - - n_iter : int - Number of iterations to perform - - temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large - - low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and / or sessions is large - - random_state : int or RandomState - Pseudo number generator state used for random sampling. - - n_jobs : int, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. - - verbose : bool or "warn" - if True, logs are enabled. - if False, logs are disabled. - if "warn" only warnings are printed. - - Attributes - ---------- - - `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str - basis of all subjects, element i is the basis of subject i - or path to basis of all subjects, element i is the path to the basis of subject i - """ - def __init__(self, - atlas, - n_components=20, - n_iter=100, - temp_dir=None, - low_ram=False, - random_state=None, - n_jobs=1, - verbose="warn",): - - self.random_state = random_state - self.n_jobs = n_jobs - self.verbose = verbose - self.n_components = n_components - self.n_iter = n_iter - self.atlas = atlas - - self.basis_list = None - - if temp_dir is None: - if self.verbose == "warn" or self.verbose is True: - logger.warning("temp_dir has value None. All basis (spatial maps) and " - "reconstructed data will therefore be kept in memory." - "This can lead to memory errors when the number of subjects " - "and/or sessions is large.") - - if temp_dir is not None: - if not os.path.exists(os.path.join(temp_dir, "fastsrm")): - os.mkdir(os.path.join(temp_dir, "fastsrm")) - self.temp_dir = os.path.join(temp_dir, "fastsrm") - - # Remove files in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) - for path in paths: - os.remove(path) - - self.low_ram = low_ram - - def fit(self, imgs): - """Computes basis across subjects from input imgs - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - Returns - ------- - self : object - Returns the instance itself. Contains attributes listed - at the object level. - """ - - if self.temp_dir is not None: - # Remove former basis in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "basis") + "*") - for path in paths: - os.remove(path) - - if type(imgs) != np.ndarray: - AssertionError("imgs should be of type np.ndarray but is of type %s" - % type(imgs)) - - if len(imgs.shape) != 2: - AssertionError("imgs should be an array of shape [n_subjects, n_sessions] " - "but its shape is of size %i" - % len(imgs.shape)) - - if self.verbose is True: - n_subjects, n_sessions = imgs.shape - logger.info("Fitting using %i subjects and %i sessions per subject" - % (n_subjects, n_sessions)) - - if self.verbose is True: - logger.info("[FastSRM.fit] Reducing data") - - reduced_data = reduce_data( - imgs, - atlas=self.atlas, - n_jobs=self.n_jobs, - low_ram=self.low_ram, - temp_dir=self.temp_dir - ) - - if self.verbose: - logger.info("[FastSRM.fit] Finds shared response using reduced data") - - shared_response_list = fast_srm( - reduced_data, - n_iter=self.n_iter, - n_components=self.n_components, - ) - - if self.verbose: - print("[FastSRM.fit] Finds basis using full data and shared response") - - if self.n_jobs == 1: - basis = [] - for i, sessions in enumerate(imgs): - basis_i = _compute_basis_subject_online(sessions, shared_response_list) - if self.temp_dir is None: - basis.append(basis_i) - else: - path = os.path.join(self.temp_dir, "basis_%i" % i) - np.save(path, basis_i) - basis.append(path) - del basis_i - else: - Parallel(n_jobs=self.n_jobs)( - delayed(_compute_and_save_corr_mat)( - subject, - shared_response_list[m], - self.temp_dir - ) - for m, subjects in enumerate(imgs.T) - for subject in subjects - ) - - basis = Parallel(n_jobs=self.n_jobs)(delayed(_compute_and_save_subject_basis)(i, sessions, self.temp_dir) - for i, sessions in enumerate(imgs)) - - self.basis_list = basis - return self - - def fit_transform(self, imgs, **fit_params): - """Computes basis across subjects and shared response from input imgs - return shared response. - - Parameters - ---------- - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - Returns - -------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - self.fit(imgs) - return self.transform(imgs) - - def transform(self, imgs, subjects_indexes=None): - """From data in imgs and basis from training data, - computes shared response. - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - - subjects_indexes : list or None: - if None imgs[i] will be transformed using basis[i] - otherwise imgs[i] will be transformed using basis[subjects_index[i]] - - Returns - ------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - """ - if subjects_indexes is None: - subjects_indexes = np.arange(len(imgs)) - else: - subjects_indexes = np.array(subjects_indexes) - - shared_response = _compute_shared_response_online( - imgs, - self.basis_list, - self.temp_dir, - self.n_jobs, - subjects_indexes - ) - - return shared_response - - def inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None): - """From shared response and basis from training data reconstruct subject's data - - Parameters - ---------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i - - subjects_indexes: list or None: - if None reconstructs data of all subjects' used during train - otherwise reconstructs data using subject's specified by subjects_indexes - - sessions_indexes: list or None: - if None reconstructs data using all sessions - otherwise uses only specified sessions - - Returns - ------- - reconstructed_data: array shape=[len(subjects_indexes), len(sessions_indexes), n_timeframes, n_voxels] - Reconstructed data for chosen subjects and sessions - """ - n_subjects = len(self.basis_list) - - if subjects_indexes is None: - subjects_indexes = np.arange(n_subjects) - else: - subjects_indexes = np.array(subjects_indexes) - - if sessions_indexes is None: - sessions_indexes = np.arange(len(shared_response_list)) - else: - sessions_indexes = np.array(sessions_indexes) - - data = [] - for i in subjects_indexes: - data_ = [] - if self.temp_dir is None: - basis_i = self.basis_list[i] - else: - basis_i = np.load(os.path.join(self.temp_dir, "basis_%i.npy" % i)) - - for j in sessions_indexes: - data_.append(shared_response_list[j].dot(basis_i)) - - data.append(np.array(data_)) - return np.array(data) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9132617e1..bf2baa3eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,9 +8,6 @@ setuptools_scm sphinx sphinx_rtd_theme towncrier -joblib -os -glob # testing # Also add to .conda/meta.yaml diff --git a/tests/eventseg/test_event.py b/tests/eventseg/test_event.py new file mode 100644 index 000000000..a82de27bd --- /dev/null +++ b/tests/eventseg/test_event.py @@ -0,0 +1,155 @@ +from brainiak.eventseg.event import EventSegment +from scipy.special import comb +import numpy as np +import pytest +from sklearn.exceptions import NotFittedError + + +def test_create_event_segmentation(): + es = EventSegment(5) + assert es, "Invalid EventSegment instance" + + +def test_fit_shapes(): + K = 5 + V = 3 + T = 10 + es = EventSegment(K, n_iter=2) + sample_data = np.random.rand(V, T) + es.fit(sample_data.T) + + assert es.segments_[0].shape == (T, K), "Segmentation from fit " \ + "has incorrect shape" + assert np.isclose(np.sum(es.segments_[0], axis=1), np.ones(T)).all(), \ + "Segmentation from learn_events not correctly normalized" + + T2 = 15 + sample_data2 = np.random.rand(V, T2) + test_segments, test_ll = es.find_events(sample_data2.T) + + assert test_segments.shape == (T2, K), "Segmentation from find_events " \ + "has incorrect shape" + assert np.isclose(np.sum(test_segments, axis=1), np.ones(T2)).all(), \ + "Segmentation from find_events not correctly normalized" + + es_invalid = EventSegment(K) + with pytest.raises(ValueError, message="T < K should cause error"): + es_invalid.model_prior(K-1) + with pytest.raises(ValueError, message="#Events < K should cause error"): + es_invalid.set_event_patterns(np.zeros((V, K-1))) + + +def test_simple_boundary(): + es = EventSegment(2) + random_state = np.random.RandomState(0) + + sample_data = np.array([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + \ + random_state.rand(2, 7) * 10 + es.fit(sample_data.T) + + events = np.argmax(es.segments_[0], axis=1) + assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ + "Failed to correctly segment two events" + + events_predict = es.predict(sample_data.T) + assert np.array_equal(events_predict, [0, 0, 0, 1, 1, 1, 1]), \ + "Error in predict interface" + + +def test_event_transfer(): + es = EventSegment(2) + sample_data = np.asarray([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + + with pytest.raises(NotFittedError, message="Should need to set variance"): + seg = es.find_events(sample_data.T)[0] + + with pytest.raises(NotFittedError, message="Should need to set patterns"): + seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] + + es.set_event_patterns(np.asarray([[1, 0], [0, 1]])) + seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] + + events = np.argmax(seg, axis=1) + assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ + "Failed to correctly transfer two events to new data" + + +def test_weighted_var(): + es = EventSegment(2) + + D = np.zeros((8, 4)) + for t in range(4): + D[t, :] = (1/np.sqrt(4/3)) * np.array([-1, -1, 1, 1]) + for t in range(4, 8): + D[t, :] = (1 / np.sqrt(4 / 3)) * np.array([1, 1, -1, -1]) + mean_pat = D[[0, 4], :].T + + weights = np.zeros((8, 2)) + weights[:, 0] = [1, 1, 1, 1, 0, 0, 0, 0] + weights[:, 1] = [0, 0, 0, 0, 1, 1, 1, 1] + assert np.array_equal( + es.calc_weighted_event_var(D, weights, mean_pat), [0, 0]),\ + "Failed to compute variance with 0/1 weights" + + weights[:, 0] = [1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5] + weights[:, 1] = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] + true_var = (4 * 0.5 * 12)/(6 - 5/6) * np.ones(2) / 4 + assert np.allclose( + es.calc_weighted_event_var(D, weights, mean_pat), true_var),\ + "Failed to compute variance with fractional weights" + + +def test_sym(): + es = EventSegment(4) + + evpat = np.repeat(np.arange(10).reshape(-1, 1), 4, axis=1) + es.set_event_patterns(evpat) + + D = np.repeat(np.arange(10).reshape(1, -1), 20, axis=0) + ev = es.find_events(D, var=1)[0] + + # Check that events 1-4 and 2-3 are symmetric + assert np.all(np.isclose(ev[:, :2], np.fliplr(np.flipud(ev[:, 2:])))),\ + "Fit with constant data is not symmetric" + + +def test_chains(): + es = EventSegment(5, event_chains=np.array(['A', 'A', 'B', 'B', 'B'])) + + es.set_event_patterns(np.array([[1, 1, 0, 0, 0], + [0, 0, 1, 1, 1]])) + sample_data = np.array([[0, 0, 0], [1, 1, 1]]) + seg = es.find_events(sample_data.T, 0.1)[0] + + ev = np.nonzero(seg > 0.99)[1] + assert np.array_equal(ev, [2, 3, 4]),\ + "Failed to fit with multiple chains" + + +def test_prior(): + K = 10 + T = 100 + + es = EventSegment(K) + mp = es.model_prior(T)[0] + + p_bound = np.zeros((T, K-1)) + norm = comb(T-1, K-1) + for t in range(T-1): + for k in range(K-1): + # See supplementary material of Neuron paper + # https://doi.org/10.1016/j.neuron.2017.06.041 + p_bound[t+1, k] = comb(t, k) * comb(T-t-2, K-k-2) / norm + p_bound = np.cumsum(p_bound, axis=0) + + mp_gt = np.zeros((T, K)) + for k in range(K): + if k == 0: + mp_gt[:, k] = 1 - p_bound[:, 0] + elif k == K - 1: + mp_gt[:, k] = p_bound[:, k-1] + else: + mp_gt[:, k] = p_bound[:, k-1] - p_bound[:, k] + + assert np.all(np.isclose(mp, mp_gt)),\ + "Prior does not match analytic solution" diff --git a/tests/factoranalysis/test_htfa.py b/tests/factoranalysis/test_htfa.py new file mode 100644 index 000000000..58591b130 --- /dev/null +++ b/tests/factoranalysis/test_htfa.py @@ -0,0 +1,168 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_R(): + from brainiak.factoranalysis.htfa import HTFA + with pytest.raises(TypeError) as excinfo: + HTFA() + assert "missing 2 required positional arguments" in str(excinfo.value) + + +def test_X(): + from brainiak.factoranalysis.htfa import HTFA + import numpy as np + + n_voxel = 100 + n_tr = 20 + K = 5 + max_global_iter = 3 + max_local_iter = 3 + max_voxel = n_voxel + max_tr = n_tr + + R = [] + n_subj = 2 + for s in np.arange(n_subj): + R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) + + htfa = HTFA( + K, + n_subj=n_subj, + max_global_iter=max_global_iter, + max_local_iter=max_local_iter, + max_voxel=max_voxel, + max_tr=max_tr) + + X = np.random.rand(n_voxel, n_tr) + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "Input data should be a list" in str(excinfo.value) + + X = [] + # Check that does NOT run with wrong array dimension + with pytest.raises(ValueError) as excinfo: + htfa.fit(X, R=R) + assert "Need at leat one subject to train the model" in str(excinfo.value) + + X = [] + X.append([1, 2, 3]) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "data should be an array" in str(excinfo.value) + + X = [] + X.append(np.random.rand(n_voxel)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "subject data should be 2D array" in str(excinfo.value) + + X = [] + for s in np.arange(n_subj): + X.append(np.random.rand(n_voxel, n_tr)) + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "Coordinates should be a list" in str(excinfo.value) + + R = [] + R.append([1, 2, 3]) + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("Each scanner coordinate matrix should be an array" + in str(excinfo.value)) + + R = [] + R.append(np.random.rand(n_voxel)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("Each scanner coordinate matrix should be 2D array" + in str(excinfo.value)) + + R = [] + for s in np.arange(n_subj): + R.append(np.random.rand(n_voxel - 1, 3)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("n_voxel should be the same in X[idx] and R[idx]" + in str(excinfo.value)) + + +def test_can_run(): + import numpy as np + from brainiak.factoranalysis.htfa import HTFA + from mpi4py import MPI + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + + n_voxel = 100 + n_tr = 20 + K = 5 + max_global_iter = 3 + max_local_iter = 3 + max_voxel = n_voxel + max_tr = n_tr + R = [] + n_subj = 2 + for s in np.arange(n_subj): + R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) + my_R = [] + for idx in np.arange(n_subj): + if idx % size == rank: + my_R.append(R[idx]) + + htfa = HTFA( + K, + n_subj=n_subj, + max_global_iter=max_global_iter, + max_local_iter=max_local_iter, + max_voxel=max_voxel, + max_tr=max_tr, + verbose=True) + assert htfa, "Invalid HTFA instance!" + + X = [] + for s in np.arange(n_subj): + X.append(np.random.rand(n_voxel, n_tr)) + my_data = [] + for idx in np.arange(n_subj): + if idx % size == rank: + my_data.append(X[idx]) + + if rank == 0: + htfa.fit(my_data, R=my_R) + assert True, "Root successfully running HTFA" + assert htfa.global_prior_.shape[0] == htfa.prior_bcast_size,\ + "Invalid result of HTFA! (wrong # element in global_prior)" + assert htfa.global_posterior_.shape[0] == htfa.prior_bcast_size,\ + "Invalid result of HTFA! (wrong # element in global_posterior)" + + else: + htfa.fit(my_data, R=my_R) + assert True, "worker successfully running HTFA" + print(htfa.local_weights_.shape) + assert htfa.local_weights_.shape[0] == n_tr * K,\ + "Invalid result of HTFA! (wrong # element in local_weights)" + assert htfa.local_posterior_.shape[0] == htfa.prior_size,\ + "Invalid result of HTFA! (wrong # element in local_posterior)" diff --git a/tests/factoranalysis/test_tfa.py b/tests/factoranalysis/test_tfa.py new file mode 100644 index 000000000..041d03cbd --- /dev/null +++ b/tests/factoranalysis/test_tfa.py @@ -0,0 +1,110 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_tfa(): + from brainiak.factoranalysis.tfa import TFA + import numpy as np + + n_voxel = 100 + n_tr = 20 + K = 5 + max_iter = 5 + max_num_voxel = n_voxel + max_num_tr = n_tr + tfa = TFA( + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + assert tfa, "Invalid TFA instance!" + + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + X = [1, 2, 3] + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "Input data should be an array" in str(excinfo.value) + + X = np.random.rand(n_voxel) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "Input data should be 2D array" in str(excinfo.value) + + X = np.random.rand(n_voxel, n_tr) + R = [1, 2, 3] + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "coordinate matrix should be an array" in str(excinfo.value) + + R = np.random.rand(n_voxel) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "coordinate matrix should be 2D array" in str(excinfo.value) + + R = np.random.randint(2, high=102, size=(n_voxel - 1, 3)) + # Check that does NOT run if n_voxel in X and R does not match + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "The number of voxels should be the same in X and R" in str( + excinfo.value) + + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + tfa.fit(X, R=R) + assert True, "Success running TFA with one subject!" + posterior_size = K * (tfa.n_dim + 1) + assert tfa.local_posterior_.shape[ + 0] == posterior_size,\ + "Invalid result of TFA! (wrong # element in local_posterior)" + + weight_method = 'ols' + tfa = TFA( + weight_method=weight_method, + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + assert tfa, "Invalid TFA instance!" + + X = np.random.rand(n_voxel, n_tr) + tfa.fit(X, R=R) + assert True, "Success running TFA with one subject!" + + template_prior, _, _ = tfa.get_template(R) + tfa.set_K(K) + tfa.set_seed(200) + tfa.fit(X, R=R, template_prior=template_prior) + assert True, "Success running TFA with one subject and template prior!" + assert tfa.local_posterior_.shape[ + 0] == posterior_size,\ + "Invalid result of TFA! (wrong # element in local_posterior)" + + weight_method = 'odd' + tfa = TFA( + weight_method=weight_method, + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + with pytest.raises(ValueError) as excinfo: + tfa.fit(X, R=R) + assert "'rr' and 'ols' are accepted as weight_method!" in str( + excinfo.value) diff --git a/tests/fcma/data/expected_processed_data.npy b/tests/fcma/data/expected_processed_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..460553dff13f0839b8571f9a0943cfb4eb237fe0 GIT binary patch literal 2672 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 Niah$^!R=(7{0Gj{kZb?| literal 0 HcmV?d00001 diff --git a/tests/fcma/data/expected_searchlight_processed_data.npy b/tests/fcma/data/expected_searchlight_processed_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..e23174f2c7f6c3458e2fcb6f9bee6b5ecf7970c7 GIT binary patch literal 1704016 zcmeI*F>BjE6aZj%?XS@07CaS`p%k)ZYsu1~Q!$N0htd?gTM6Vp^auH`RZj06_!&i& zX%c}xfq8zXJL&vApGMv94v{Bli0hZA z4i5_))gk#+-)P?*?zdIPceTg3j`;BGoUQ&HzI{TqKH2}o>-F8w`9pzPzwFt)4mm*`;g>*DSwhl zPxvOC^(uAfqlvmkzsS3;8~wz86UdG+sygq=eoQ&WI*RtlBX+dM^~L`qkn1-zk6Cn$ zi}T{+2xRwsm%0vPU8R`&%Lx!5K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfWR08>ihWZUonPq9L=HDr|g4}JM^AUb16VFKAZ=fke1Hvs|!2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF c5FkK+009C72oNAZfB*pk1PBlyKwzlAA8SKUk^lez literal 0 HcmV?d00001 diff --git a/tests/fcma/test_classification.py b/tests/fcma/test_classification.py new file mode 100644 index 000000000..3225fa80d --- /dev/null +++ b/tests/fcma/test_classification.py @@ -0,0 +1,222 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.classifier import Classifier +from scipy.stats.mstats import zscore +from sklearn import svm +from sklearn.linear_model import LogisticRegression +import numpy as np +import math +from numpy.random import RandomState +from scipy.spatial.distance import hamming + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def create_epoch(idx, num_voxels): + row = 12 + col = num_voxels + mat = prng.rand(row, col).astype(np.float32) + # impose a pattern to even epochs + if idx % 2 == 0: + mat = np.sort(mat, axis=0) + mat = zscore(mat, axis=0, ddof=0) + # if zscore fails (standard deviation is zero), + # set all values to be zero + mat = np.nan_to_num(mat) + mat = mat / math.sqrt(mat.shape[0]) + return mat + + +def test_classification(): + fake_raw_data = [create_epoch(i, 5) for i in range(20)] + labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # 5 subjects, 4 epochs per subject + epochs_per_subj = 4 + # svm + svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + training_data = fake_raw_data[0:12] + clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data)), labels[0:12]) + expected_confidence = np.array([-1.18234421, 0.97403604, -1.04005679, + 0.92403019, -0.95567738, 1.11746593, + -0.83275891, 0.9486868]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM does not provide correct results' + confidence = clf.decision_function(list(zip(fake_raw_data[12:], + fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence) + ) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM without recomputation ' \ + 'does not provide correct results' + y = [0, 1, 0, 1, 0, 1, 0, 1] + score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data[12:])), y) + assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ + 'the prediction score is incorrect' + # svm with partial similarity matrix computation + clf = Classifier(svm_clf, num_processed_voxels=2, + epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(fake_raw_data, fake_raw_data)), + labels, + num_training_samples=12) + y_pred = clf.predict() + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM (partial sim) does not ' \ + 'provide correct results' + confidence = clf.decision_function() + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM (partial sim) without recomputation ' \ + 'does not provide correct results' + # logistic regression + lr_clf = LogisticRegression() + clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data)), labels[0:12]) + expected_confidence = np.array([-4.49666484, 3.73025553, -4.04181695, + 3.73027436, -3.77043872, 4.42613412, + -3.35616616, 3.77716609]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via logistic regression ' \ + 'does not provide correct results' + confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence) + ) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression without precomputation ' \ + 'does not provide correct results' + + +def test_classification_with_two_components(): + fake_raw_data = [create_epoch(i, 5) for i in range(20)] + fake_raw_data2 = [create_epoch(i, 6) for i in range(20)] + labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # 5 subjects, 4 epochs per subject + epochs_per_subj = 4 + # svm + svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + training_data = fake_raw_data[0: 12] + training_data2 = fake_raw_data2[0: 12] + clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data2)), labels[0:12]) + expected_confidence = np.array([-1.23311606, 1.02440964, -0.93898336, + 1.07028798, -1.04420007, 0.97647772, + -1.0498268, 1.04970111]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM does not provide correct results' + confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM without recomputation ' \ + 'does not provide correct results' + y = [0, 1, 0, 1, 0, 1, 0, 1] + score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data2[12:])), y) + assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ + 'the prediction score is incorrect' + # svm with partial similarity matrix computation + clf = Classifier(svm_clf, num_processed_voxels=2, + epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(fake_raw_data, fake_raw_data2)), + labels, + num_training_samples=12) + y_pred = clf.predict() + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM (partial sim) does not ' \ + 'provide correct results' + confidence = clf.decision_function() + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM (partial sim) without recomputation ' \ + 'does not provide correct results' + # logistic regression + lr_clf = LogisticRegression() + clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) + # specifying num_training_samples is for coverage + clf.fit(list(zip(training_data, training_data2)), + labels[0:12], + num_training_samples=12) + expected_confidence = np.array([-4.90819848, 4.22548132, -3.76255726, + 4.46505975, -4.19933099, 4.08313584, + -4.23070437, 4.31779758]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via logistic regression ' \ + 'does not provide correct results' + confidence = clf.decision_function(list(zip(fake_raw_data[12:], + fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression without precomputation ' \ + 'does not provide correct results' + + +if __name__ == '__main__': + test_classification() + test_classification_with_two_components() diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py new file mode 100644 index 000000000..d551ff5ee --- /dev/null +++ b/tests/fcma/test_mvpa_voxel_selection.py @@ -0,0 +1,51 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector +from brainiak.searchlight.searchlight import Searchlight +from sklearn import svm +import numpy as np +from mpi4py import MPI +from numpy.random import RandomState + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def test_mvpa_voxel_selection(): + data = prng.rand(5, 5, 5, 8).astype(np.float32) + # all MPI processes read the mask; the mask file is small + mask = np.ones([5, 5, 5], dtype=np.bool) + mask[0, 0, :] = False + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + sl = Searchlight(sl_rad=1) + mvs = MVPAVoxelSelector(data, mask, labels, 2, sl) + # for cross validation, use SVM with precomputed kernel + + clf = svm.SVC(kernel='rbf', C=10) + result_volume, results = mvs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [] + for tuple in results: + if tuple[1] > 0: + output.append(int(8*tuple[1])) + expected_output = [6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + + +if __name__ == '__main__': + test_mvpa_voxel_selection() diff --git a/tests/fcma/test_preprocessing.py b/tests/fcma/test_preprocessing.py new file mode 100644 index 000000000..0ee38db49 --- /dev/null +++ b/tests/fcma/test_preprocessing.py @@ -0,0 +1,109 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +import numpy as np + +from brainiak.fcma.preprocessing import (prepare_fcma_data, prepare_mvpa_data, + prepare_searchlight_mvpa_data) +from brainiak import io + +data_dir = Path(__file__).parents[1] / 'io' / 'data' +expected_dir = Path(__file__).parent / 'data' +suffix = 'bet.nii.gz' +mask_file = data_dir / 'mask.nii.gz' +epoch_file = data_dir / 'epoch_labels.npy' +expected_labels = np.array([0, 1, 0, 1]) + + +def test_prepare_fcma_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + mask = io.load_boolean_mask(mask_file) + conditions = io.load_labels(epoch_file) + raw_data, _, labels = prepare_fcma_data(images, conditions, mask) + expected_raw_data = np.load(expected_dir / 'expected_raw_data.npy') + assert len(raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + for idx in range(len(raw_data)): + assert np.allclose(raw_data[idx], expected_raw_data[idx]), \ + 'raw data do not match in test_prepare_fcma_data' + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_fcma_data' + from brainiak.fcma.preprocessing import RandomType + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, + random=RandomType.REPRODUCIBLE) + assert len(random_raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, + random=RandomType.UNREPRODUCIBLE) + assert len(random_raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + + +def test_prepare_mvpa_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + mask = io.load_boolean_mask(mask_file) + conditions = io.load_labels(epoch_file) + processed_data, labels = prepare_mvpa_data(images, conditions, mask) + expected_processed_data = np.load(expected_dir + / 'expected_processed_data.npy') + assert len(processed_data) == len(expected_processed_data), \ + 'numbers of epochs do not match in test_prepare_mvpa_data' + for idx in range(len(processed_data)): + assert np.allclose(processed_data[idx], + expected_processed_data[idx]), ( + 'raw data do not match in test_prepare_mvpa_data') + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_mvpa_data' + + +def test_prepare_searchlight_mvpa_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + conditions = io.load_labels(epoch_file) + processed_data, labels = prepare_searchlight_mvpa_data(images, + conditions) + expected_searchlight_processed_data = np.load( + expected_dir / 'expected_searchlight_processed_data.npy') + for idx in range(len(processed_data)): + assert np.allclose(processed_data[idx], + expected_searchlight_processed_data[idx]), ( + 'raw data do not match in test_prepare_searchlight_mvpa_data') + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_searchlight_mvpa_data' + from brainiak.fcma.preprocessing import RandomType + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_processed_data, _ = prepare_searchlight_mvpa_data( + images, + conditions, + random=RandomType.REPRODUCIBLE) + assert (len(random_processed_data) + == len(expected_searchlight_processed_data)), ( + 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_processed_data, _ = prepare_searchlight_mvpa_data( + images, + conditions, + random=RandomType.UNREPRODUCIBLE) + assert (len(random_processed_data) + == len(expected_searchlight_processed_data)), ( + 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') + + +if __name__ == '__main__': + test_prepare_fcma_data() + test_prepare_mvpa_data() + test_prepare_searchlight_mvpa_data() diff --git a/tests/fcma/test_util.py b/tests/fcma/test_util.py new file mode 100644 index 000000000..6508d49b1 --- /dev/null +++ b/tests/fcma/test_util.py @@ -0,0 +1,59 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from numpy.random import RandomState +from brainiak.fcma.util import compute_correlation + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def test_correlation_computation(): + row1 = 5 + col = 10 + row2 = 6 + mat1 = prng.rand(row1, col).astype(np.float32) + mat2 = prng.rand(row2, col).astype(np.float32) + corr = compute_correlation(mat1, mat1) + expected_corr = np.corrcoef(mat1) + assert np.allclose(corr, expected_corr, atol=1e-5), ( + "high performance correlation computation does not provide correct " + "correlation results within the same set") + corr = compute_correlation(mat1, mat2) + mat = np.concatenate((mat1, mat2), axis=0) + expected_corr = np.corrcoef(mat)[0:row1, row1:] + assert np.allclose(corr, expected_corr, atol=1e-5), ( + "high performance correlation computation does not provide correct " + "correlation results between two sets") + + +def test_correlation_nans(): + row1 = 5 + col = 10 + row2 = 6 + mat1 = prng.rand(row1, col).astype(np.float32) + mat2 = prng.rand(row2, col).astype(np.float32) + mat1[0, 0] = np.nan + corr = compute_correlation(mat1, mat2, return_nans=False) + assert np.all(corr == 0, axis=1)[0] + assert np.sum(corr == 0) == row2 + corr = compute_correlation(mat1, mat2, return_nans=True) + assert np.all(np.isnan(corr), axis=1)[0] + assert np.sum(np.isnan(corr)) == row2 + + +if __name__ == '__main__': + test_correlation_computation() + test_correlation_nans() diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py new file mode 100644 index 000000000..98fadc4ea --- /dev/null +++ b/tests/fcma/test_voxel_selection.py @@ -0,0 +1,116 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.voxelselector import VoxelSelector +from scipy.stats.mstats import zscore +from sklearn import svm +from sklearn.linear_model import LogisticRegression +import numpy as np +import math +from mpi4py import MPI +from numpy.random import RandomState + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def create_epoch(): + row = 12 + col = 5 + mat = prng.rand(row, col).astype(np.float32) + mat = zscore(mat, axis=0, ddof=0) + # if zscore fails (standard deviation is zero), + # set all values to be zero + mat = np.nan_to_num(mat) + mat = mat / math.sqrt(mat.shape[0]) + return mat + + +def test_voxel_selection(): + fake_raw_data = [create_epoch() for i in range(8)] + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + vs = VoxelSelector(labels, 4, 2, fake_raw_data, voxel_unit=1) + # test scipy normalization + fake_corr = prng.rand(1, 4, 5).astype(np.float32) + fake_corr = vs._correlation_normalization(fake_corr) + if MPI.COMM_WORLD.Get_rank() == 0: + expected_fake_corr = [[[1.06988919, 0.51641309, -0.46790636, + -1.31926763, 0.2270218], + [-1.22142744, -1.39881694, -1.2979387, + 1.05702305, -0.6525566], + [0.89795232, 1.27406132, 0.36460185, + 0.87538344, 1.5227468], + [-0.74641371, -0.39165771, 1.40124381, + -0.61313909, -1.0972116]]] + assert np.allclose(fake_corr, expected_fake_corr), \ + 'within-subject normalization does not provide correct results' + # for cross validation, use SVM with precomputed kernel + # no shrinking, set C=1 + clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [7, 4, 6, 4, 4] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + # for cross validation, use logistic regression + clf = LogisticRegression() + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [6, 3, 6, 4, 4] + assert np.allclose(output, expected_output, atol=1), ( + "voxel selection via logistic regression does not provide correct " + "results") + + +def test_voxel_selection_with_two_masks(): + fake_raw_data1 = [create_epoch() for i in range(8)] + fake_raw_data2 = [create_epoch() for i in range(8)] + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + vs = VoxelSelector(labels, 4, 2, fake_raw_data1, + raw_data2=fake_raw_data2, voxel_unit=1) + # for cross validation, use SVM with precomputed kernel + # no shrinking, set C=1 + clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [3, 3, 3, 6, 6] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + # for cross validation, use logistic regression + clf = LogisticRegression() + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [3, 4, 4, 6, 6] + assert np.allclose(output, expected_output, atol=1), ( + "voxel selection via logistic regression does not provide correct " + "results") + + +if __name__ == '__main__': + test_voxel_selection() + test_voxel_selection_with_two_masks() diff --git a/tests/hyperparamopt/test_hpo.py b/tests/hyperparamopt/test_hpo.py new file mode 100644 index 000000000..3904458ee --- /dev/null +++ b/tests/hyperparamopt/test_hpo.py @@ -0,0 +1,86 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest +import numpy as np +import scipy.stats as st +from brainiak.hyperparamopt.hpo import gmm_1d_distribution, fmin + + +def test_simple_gmm(): + x = np.array([1., 1., 2., 3., 1.]) + d = gmm_1d_distribution(x, min_limit=0., max_limit=4.) + assert d(1.1) > d(3.5), "GMM distribution not behaving correctly" + assert d(2.0) > d(3.0), "GMM distribution not behaving correctly" + assert d(-1.0) == 0, "GMM distribution out of bounds error" + assert d(9.0) == 0, "GMM distribution out of bounds error" + + samples = d.get_samples(n=25) + np.testing.assert_array_less(samples, 4.) + np.testing.assert_array_less(0., samples) + + +def test_simple_gmm_weights(): + x = np.array([1., 1., 2., 3., 1., 3.]) + d = gmm_1d_distribution(x) + + x2 = np.array([1., 2., 3.]) + w = np.array([3., 1., 2.]) + d2 = gmm_1d_distribution(x2, weights=w) + y2 = d2(np.array([1.1, 2.0])) + + assert d2(1.1) == y2[0],\ + "GMM distribution array & scalar results don't match" + assert np.abs(d(1.1) - d2(1.1)) < 1e-5,\ + "GMM distribution weights not handled correctly" + assert np.abs(d(2.0) - d2(2.0)) < 1e-5,\ + "GMM distribution weights not handled correctly" + + +def test_simple_hpo(): + + def f(args): + x = args['x'] + return x*x + + s = {'x': {'dist': st.uniform(loc=-10., scale=20), 'lo': -10., 'hi': 10.}} + trials = [] + + # Test fmin and ability to continue adding to trials + best = fmin(loss_fn=f, space=s, max_evals=40, trials=trials) + best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) + + assert len(trials) == 50, "HPO continuation trials not working" + + # Test verbose flag + best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) + + yarray = np.array([tr['loss'] for tr in trials]) + np.testing.assert_array_less(yarray, 100.) + + xarray = np.array([tr['x'] for tr in trials]) + np.testing.assert_array_less(np.abs(xarray), 10.) + + assert best['loss'] < 100., "HPO out of range" + assert np.abs(best['x']) < 10., "HPO out of range" + + # Test unknown distributions + s2 = {'x': {'dist': 'normal', 'mu': 0., 'sigma': 1.}} + trials2 = [] + with pytest.raises(ValueError) as excinfo: + fmin(loss_fn=f, space=s2, max_evals=40, trials=trials2) + assert "Unknown distribution type for variable" in str(excinfo.value) + + s3 = {'x': {'dist': st.norm(loc=0., scale=1.)}} + trials3 = [] + fmin(loss_fn=f, space=s3, max_evals=40, trials=trials3) diff --git a/tests/image/test_image.py b/tests/image/test_image.py new file mode 100644 index 000000000..4796bc3f1 --- /dev/null +++ b/tests/image/test_image.py @@ -0,0 +1,172 @@ +# Copyright 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Iterable, Sequence + +import numpy as np +import pytest + +from nibabel.nifti1 import Nifti1Pair +from nibabel.spatialimages import SpatialImage + +from brainiak.image import (mask_image, mask_images, MaskedMultiSubjectData, + multimask_images, SingleConditionSpec) + + +@pytest.fixture +def masked_multi_subject_data(masked_images): + return np.stack(masked_images, axis=-1) + + +class TestMaskedMultiSubjectData: + def test_from_masked_images(self, masked_images, + masked_multi_subject_data): + result = MaskedMultiSubjectData.from_masked_images(masked_images, + len(masked_images)) + assert np.array_equal(np.moveaxis(result, 1, 0), + masked_multi_subject_data) + + +@pytest.fixture +def condition_spec() -> SingleConditionSpec: + return np.array([[[1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]]], + dtype=np.int8).view(SingleConditionSpec) + + +class TestUniqueLabelConditionSpec: + def test_extract_labels(self, condition_spec: SingleConditionSpec + ) -> None: + assert np.array_equal(condition_spec.extract_labels(), + np.array([0, 1])) + + +@pytest.fixture +def spatial_image() -> SpatialImage: + return Nifti1Pair(np.array([[[[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]]]).reshape(4, 4, 4, 1), + np.eye(4)) + + +@pytest.fixture +def mask() -> np.ndarray: + return np.array([[[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]], dtype=np.bool) + + +@pytest.fixture +def masked_data() -> np.ndarray: + return np.array([[1, 0, 0, 1, 0, 1, 1, 0]]).reshape(8, 1) + + +@pytest.fixture +def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]: + images = [spatial_image] + image_data = spatial_image.get_data().copy() + image_data[1, 1, 1, 0] = 2 + images.append(Nifti1Pair(image_data, np.eye(4))) + return images + + +@pytest.fixture +def masks(mask: np.ndarray) -> Sequence[np.ndarray]: + masks = [mask] + mask2 = mask.copy() + mask2[0, 0, 0] = 1 + masks.append(mask2) + mask3 = mask.copy() + mask3[2, 2, 2] = 0 + masks.append(mask3) + return masks + + +@pytest.fixture +def multimasked_images(masked_data) -> Iterable[Iterable[np.ndarray]]: + masked_data_2 = np.concatenate(([[2]], masked_data[1:, :])) + return [[masked_data, np.concatenate(([[0]], masked_data)), + masked_data[:-1, :]], + [masked_data_2, np.concatenate(([[0]], masked_data_2)), + masked_data_2[:-1, :]]] + + +@pytest.fixture +def masked_images(multimasked_images) -> Iterable[np.ndarray]: + return [multimasked_image[0] for multimasked_image in multimasked_images] + + +def test_mask_image(spatial_image: SpatialImage, mask: np.ndarray, + masked_data: np.ndarray) -> None: + result = mask_image(spatial_image, mask) + assert np.array_equal(result, masked_data) + + +def test_mask_image_with_type(spatial_image: SpatialImage, mask: np.ndarray, + masked_data: np.ndarray) -> None: + masked_data_type = np.float32 + result = mask_image(spatial_image, mask, masked_data_type) + assert result.dtype == masked_data_type + assert np.allclose(result, masked_data) + + +def test_multimask_images( + images: Iterable[SpatialImage], + masks: Sequence[np.ndarray], + multimasked_images: Iterable[Iterable[np.ndarray]] + ) -> None: + result = multimask_images(images, masks) + for result_images, expected_images in zip(result, + multimasked_images): + for result_image, expected_image in zip(result_images, + expected_images): + assert np.array_equal(result_image, expected_image) + + +def test_mask_images( + images: Iterable[SpatialImage], + mask: np.ndarray, + masked_images: Iterable[np.ndarray] + ) -> None: + result = mask_images(images, mask) + for result_image, expected_image in zip(result, masked_images): + assert np.array_equal(result_image, expected_image) diff --git a/tests/io/data/epoch_labels.npy b/tests/io/data/epoch_labels.npy new file mode 100644 index 0000000000000000000000000000000000000000..50b0a4304f8d755e5ba6147e7c2ebe6021326848 GIT binary patch literal 160 zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7ItmbEXrQU1P^+Mzz{Lmz4EO<939;$`jT{x$ literal 0 HcmV?d00001 diff --git a/tests/io/data/mask.nii.gz b/tests/io/data/mask.nii.gz new file mode 100755 index 0000000000000000000000000000000000000000..687ab69749ac9e33b4aa23f6e3bf269c37b6d2c9 GIT binary patch literal 634 zcmb2|=3oE==C^kYvzQzuSRY7UV(CwqYA3*PLz^Y&=2G8@+%padSBUgK5L~<>QHRC) z|C8GLMjPH7dbe+W?){o?WzD748Qp&kbN`mC348qa__r_B|F&AJds~99kSj9e&V_GzaCYpfvaTyQ~&ez z32dn14y-5dKEL;-al)M16f~)V`jdO_e|h)Y@fXje&GVN&XGhW5pmk~Y{+B;DvO5%& Vp-9kx)6iAN5cDRYH=&V@0RSu0fKUJc literal 0 HcmV?d00001 diff --git a/tests/io/data/subject1_bet.nii.gz b/tests/io/data/subject1_bet.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..f34b35c1d201cfae247bc3e4857f17eaee53a5f4 GIT binary patch literal 7149 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 P=cU0(r*}}F+er8i>K-AX literal 0 HcmV?d00001 diff --git a/tests/io/test_io.py b/tests/io/test_io.py new file mode 100644 index 000000000..9f72550f6 --- /dev/null +++ b/tests/io/test_io.py @@ -0,0 +1,106 @@ +# Copyright 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Iterable, Sequence + +import nibabel as nib +import numpy as np +import pytest + +from brainiak import io + + +@pytest.fixture +def in_dir() -> Path: + return Path(__file__).parent / "data" + + +@pytest.fixture +def expected_image_data_shape() -> Sequence[int]: + return (64, 64, 26, 10) + + +@pytest.fixture +def mask_path(in_dir: Path) -> Path: + return in_dir / "mask.nii.gz" + + +@pytest.fixture +def labels_path(in_dir: Path) -> Path: + return in_dir / "epoch_labels.npy" + + +@pytest.fixture +def expected_condition_spec_shape() -> Sequence[int]: + return (2, 2, 10) + + +@pytest.fixture +def expected_n_subjects() -> int: + return 2 + + +@pytest.fixture +def image_paths(in_dir: Path) -> Iterable[Path]: + return (in_dir / "subject1_bet.nii.gz", in_dir / "subject2_bet.nii.gz") + + +def test_load_images_from_dir_data_shape( + in_dir: Path, + expected_image_data_shape: Sequence[int], + expected_n_subjects: int + ) -> None: + for i, image in enumerate(io.load_images_from_dir(in_dir, "bet.nii.gz")): + assert image.get_data().shape == (64, 64, 26, 10) + assert i + 1 == expected_n_subjects + + +def test_load_images_data_shape( + image_paths: Iterable[Path], + expected_image_data_shape: Sequence[int], + expected_n_subjects: int + ) -> None: + for i, image in enumerate(io.load_images(image_paths)): + assert image.get_data().shape == (64, 64, 26, 10) + assert i + 1 == expected_n_subjects + + +def test_load_boolean_mask(mask_path: Path) -> None: + mask = io.load_boolean_mask(mask_path) + assert mask.dtype == np.bool + + +def test_load_boolean_mask_predicate(mask_path: Path) -> None: + mask = io.load_boolean_mask(mask_path, lambda x: np.logical_not(x)) + expected_mask = np.logical_not(io.load_boolean_mask(mask_path)) + assert np.array_equal(mask, expected_mask) + + +def test_load_labels(labels_path: Path, + expected_condition_spec_shape: Sequence[int], + expected_n_subjects: int) -> None: + condition_specs = io.load_labels(labels_path) + i = 0 + for condition_spec in condition_specs: + assert condition_spec.shape == expected_condition_spec_shape + i += 1 + assert i == expected_n_subjects + + +def test_save_as_nifti_file(tmpdir) -> None: + out_file = str(tmpdir / "nifti.nii") + shape = (4, 4, 4) + io.save_as_nifti_file(np.ones(shape), np.eye(4), out_file) + assert nib.load(out_file).get_data().shape == shape diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py new file mode 100644 index 000000000..fc4ea17c1 --- /dev/null +++ b/tests/isc/test_isc.py @@ -0,0 +1,956 @@ +import numpy as np +import logging +import pytest +from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, + squareform_isfc, timeshift_isc, + phaseshift_isc) +from scipy.spatial.distance import squareform + +logger = logging.getLogger(__name__) + + +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data + + +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + correlated = True + while correlated: + uncorrelated = np.random.randn(n_TRs, + n_subjects)[:, np.newaxis, :] + unc_max = np.amax(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + unc_mean = np.mean(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + if unc_max < .3 and np.abs(unc_mean) < .001: + correlated = False + data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], + 20, axis=2) + data = np.concatenate((data, uncorrelated), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data + + +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC inputs") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + logger.info("Finished testing ISC inputs") + + +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) + + # Just two subjects + iscs_loo = isc(data[..., :2], pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_voxels,) + + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) + + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic='mean') + assert isc_mean.shape == (n_voxels,) + + isc_median = isc(data, pairwise=False, summary_statistic='median') + assert isc_median.shape == (n_voxels,) + + with pytest.raises(ValueError): + isc(data, pairwise=False, summary_statistic='min') + + logger.info("Finished testing ISC options") + + +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + logger.info("Testing ISC outputs") + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) + assert np.all(iscs[:, -1] < 1.) + + logger.info("Finished testing ISC outputs") + + +# Check for proper handling of NaNs in ISC +def test_isc_nans(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Inject NaNs into data + data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + iscs_loo = isc(data, pairwise=False, tolerate_nans=False) + assert np.sum(np.isnan(iscs_loo)) == n_subjects + + # Tolerate all NaNs, only subject with NaNs yields NaN + iscs_loo = isc(data, pairwise=False, tolerate_nans=True) + assert np.sum(np.isnan(iscs_loo)) == 1 + + # Pairwise approach shouldn't care + iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) + iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) + assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(iscs_pw_T)) == + np.sum(np.isnan(iscs_pw_F)) == + n_subjects - 1) + + # Set proportion of nans to reject (70% and 90% non-NaN) + data[0, 0, :] = np.nan + data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan + data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan + + iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) + iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) + iscs_loo_95 = isc(data, pairwise=False, tolerate_nans=.95) + iscs_loo_90 = isc(data, pairwise=False, tolerate_nans=.90) + iscs_loo_80 = isc(data, pairwise=False, tolerate_nans=.8) + iscs_loo_70 = isc(data, pairwise=False, tolerate_nans=.7) + iscs_loo_60 = isc(data, pairwise=False, tolerate_nans=.6) + + assert (np.sum(np.isnan(iscs_loo_F)) == + np.sum(np.isnan(iscs_loo_95)) == 60) + assert (np.sum(np.isnan(iscs_loo_80)) == + np.sum(np.isnan(iscs_loo_90)) == 42) + assert (np.sum(np.isnan(iscs_loo_T)) == + np.sum(np.isnan(iscs_loo_60)) == + np.sum(np.isnan(iscs_loo_70)) == 28) + assert np.array_equal(np.sum(np.isnan(iscs_loo_F), axis=0), + np.sum(np.isnan(iscs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(iscs_loo_80), axis=0), + np.sum(np.isnan(iscs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(iscs_loo_T), axis=0), + np.sum(np.isnan(iscs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(iscs_loo_T), axis=0), + np.sum(np.isnan(iscs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(iscs_loo_60), axis=0), + np.sum(np.isnan(iscs_loo_70), axis=0)))) + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Make sure voxel with NaNs across all subjects is always removed + data[0, 0, :] = np.nan + iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) + iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) + assert np.allclose(iscs_loo_T, iscs_loo_F, equal_nan=True) + assert (np.sum(np.isnan(iscs_loo_T)) == + np.sum(np.isnan(iscs_loo_F)) == + n_subjects) + + iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) + iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) + assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(iscs_pw_T)) == + np.sum(np.isnan(iscs_pw_F)) == + n_subjects * (n_subjects - 1) / 2) + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + n_bootstraps = 10 + + logger.info("Testing bootstrap hypothesis test") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Test one-sample bootstrap test with pairwise approach + n_bootstraps = 10 + + iscs = isc(data, pairwise=True, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Check random seeds + iscs = isc(data, pairwise=False, summary_statistic=None) + distributions = [] + for random_state in [42, 42, None]: + observed, ci, p, distribution = bootstrap_isc( + iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95, + random_state=random_state) + distributions.append(distribution) + assert np.array_equal(distributions[0], distributions[1]) + assert not np.array_equal(distributions[1], distributions[2]) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='median')) + + logger.info("Finished testing bootstrap hypothesis test") + + +# Test permutation test with group assignments +def test_permutation_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + group_assignment = [1] * 10 + [2] * 10 + + logger.info("Testing permutation test") + + # Create dataset with two groups in pairwise approach + data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=5, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=200) + + # Create data with two groups in leave-one-out approach + data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=10, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=200) + + # One-sample leave-one-out permutation test + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic='median', + n_permutations=200) + + # One-sample pairwise permutation test + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=True, + summary_statistic='median', + n_permutations=200) + + # Small one-sample pairwise exact test + data = simulated_timeseries(12, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median', + n_permutations=10000) + + # Small two-sample pairwise exact test (and unequal groups) + data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=10000) + + # Small two-sample leave-one-out exact test (and unequal groups) + data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=10000) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing permutaton test") + + +def test_timeshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing circular time-shift") + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, pairwise + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing circular time-shift") + + +# Phase randomization test +def test_phaseshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing phase randomization") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Phase randomization one-sample test, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing phase randomization") + + +# Test ISFC +def test_isfc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing ISFC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + isfcs, iscs = isfc(data, pairwise=False, summary_statistic=None) + assert isfcs.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) + assert iscs.shape == (n_subjects, n_voxels) + + # Without vectorized upper triangle + isfcs = isfc(data, pairwise=False, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_voxels) + + # Just two subjects + isfcs, iscs = isfc(data[..., :2], pairwise=False, summary_statistic=None) + assert isfcs.shape == (n_voxels * (n_voxels - 1) / 2,) + assert iscs.shape == (n_voxels,) + + isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_voxels, n_voxels) + + # ISFC with pairwise approach + isfcs, iscs = isfc(data, pairwise=True, summary_statistic=None) + assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels * (n_voxels - 1) / 2) + assert iscs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels) + + isfcs = isfc(data, pairwise=True, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels, n_voxels) + + # ISFC with summary statistics + isfcs, iscs = isfc(data, pairwise=True, summary_statistic='mean') + isfcs, iscs = isfc(data, pairwise=True, summary_statistic='median') + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) + assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) + assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) + + isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) + assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) + assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=False) + isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) + isfcs, iscs_v = isfc(data, pairwise=False) + assert np.allclose(iscs, iscs_v, rtol=1e-03) + + # Check that ISC and ISFC diagonal are identical (pairwise) + iscs = isc(data, pairwise=True) + isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) + isfcs, iscs_v = isfc(data, pairwise=True) + assert np.allclose(iscs, iscs_v, rtol=1e-03) + + # Generate 'targets' data and use for ISFC + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + n_targets = 15 + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Ensure 'square' output enforced + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=True) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Check list input for targets + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='list') + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Check that mismatching subjects / TRs breaks targets + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + + with pytest.raises(ValueError): + isfcs = isfc(data, targets=targets_data[..., :-1], + pairwise=False, vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + with pytest.raises(ValueError): + isfcs = isfc(data, targets=targets_data[:-1, ...], + pairwise=False, vectorize_isfcs=False) + + # Check targets for only 2 subjects + isfcs = isfc(data[..., :2], targets=targets_data[..., :2], + pairwise=False, summary_statistic=None) + assert isfcs.shape == (2, n_voxels, n_targets) + + isfcs = isfc(data[..., :2], targets=targets_data[..., :2], + pairwise=True, summary_statistic=None) + assert isfcs.shape == (2, n_voxels, n_targets) + + # Check that supplying targets enforces leave-one-out + isfcs_pw = isfc(data, targets=targets_data, pairwise=True, + vectorize_isfcs=False, tolerate_nans=False) + assert isfcs_pw.shape == (n_subjects, n_voxels, n_targets) + + logger.info("Finished testing ISFC options") + + +# Check for proper handling of NaNs in ISFC +def test_isfc_nans(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Inject NaNs into data + data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels * 2 - 1) + + # With vectorized ISFCs + isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels - 1) + + # Tolerate all NaNs, only subject with NaNs yields NaN + isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == n_voxels * 2 - 1 + + isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == n_voxels - 1 + + # Pairwise approach shouldn't care + isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_F)) == + (n_voxels * 2 - 1) * (n_subjects - 1)) + + isfcs_pw_T, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, + tolerate_nans=True) + isfcs_pw_F, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_F)) == + (n_voxels - 1) * (n_subjects - 1)) + + # Set proportion of nans to reject (70% and 90% non-NaN) + data[0, 0, :] = np.nan + data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan + data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan + + isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + isfcs_loo_95 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.95) + isfcs_loo_90 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.90) + isfcs_loo_80 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.8) + isfcs_loo_70 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.7) + isfcs_loo_60 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.6) + assert (np.sum(np.isnan(isfcs_loo_F)) == + np.sum(np.isnan(isfcs_loo_95)) == 3420) + assert (np.sum(np.isnan(isfcs_loo_80)) == + np.sum(np.isnan(isfcs_loo_90)) == 2430) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_60)) == + np.sum(np.isnan(isfcs_loo_70)) == 1632) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), + np.sum(np.isnan(isfcs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), + np.sum(np.isnan(isfcs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_60), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)))) + + isfcs_loo_T, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=True) + isfcs_loo_F, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=False) + isfcs_loo_95, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.95) + isfcs_loo_90, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.90) + isfcs_loo_80, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.8) + isfcs_loo_70, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.7) + isfcs_loo_60, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.6) + assert (np.sum(np.isnan(isfcs_loo_F)) == + np.sum(np.isnan(isfcs_loo_95)) == 1680) + assert (np.sum(np.isnan(isfcs_loo_80)) == + np.sum(np.isnan(isfcs_loo_90)) == 1194) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_60)) == + np.sum(np.isnan(isfcs_loo_70)) == 802) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), + np.sum(np.isnan(isfcs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), + np.sum(np.isnan(isfcs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_60), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)))) + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Make sure voxel with NaNs across all subjects is always removed + data[0, 0, :] = np.nan + isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_loo_T, isfcs_loo_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_F)) == + 1180) + + isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_T)) == + 11210) + + # Check for NaN-handling in targets + n_targets = 15 + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + + # Inject NaNs into targets_data + targets_data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + isfcs_loo = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False, tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == (n_subjects - 1) * (n_targets * 2) + + # Single NaN in targets will get averaged out with tolerate + isfcs_loo = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False, tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == 0 + + +def test_squareform_isfc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Generate square redundant ISFCs + isfcs_r = isfc(data, vectorize_isfcs=False) + assert isfcs_r.shape == (n_subjects, n_voxels, n_voxels) + + # Squareform these into condensed ISFCs and ISCs + isfcs_c, iscs_c = squareform_isfc(isfcs_r) + assert isfcs_c.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) + assert iscs_c.shape == (n_subjects, n_voxels) + + # Go back the other way and check it's the same + isfcs_new = squareform_isfc(isfcs_c, iscs_c) + assert np.array_equal(isfcs_r, isfcs_new) + + # Check against ISC function + assert np.allclose(isc(data), iscs_c, rtol=1e-03) + + # Check for two subjects + isfcs_r = isfc(data[..., :2], vectorize_isfcs=False) + assert isfcs_r.shape == (n_voxels, n_voxels) + isfcs_c, iscs_c = squareform_isfc(isfcs_r) + assert isfcs_c.shape == (n_voxels * (n_voxels - 1) / 2,) + assert iscs_c.shape == (n_voxels,) + assert np.array_equal(isfcs_r, squareform_isfc(isfcs_c, iscs_c)) + + +if __name__ == '__main__': + test_isc_input() + test_isc_options() + test_isc_output() + test_isc_nans() + test_bootstrap_isc() + test_permutation_isc() + test_timeshift_isc() + test_phaseshift_isc() + test_isfc_options() + test_isfc_nans() + test_squareform_isfc() + logger.info("Finished all ISC tests") diff --git a/tests/reprsimil/example_design.1D b/tests/reprsimil/example_design.1D new file mode 100755 index 000000000..7385edea6 --- /dev/null +++ b/tests/reprsimil/example_design.1D @@ -0,0 +1,272 @@ +# + 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 + 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 + 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 + 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 + 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 + 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 + 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 + 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 + 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 + 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 + 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 + 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 + 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 + 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 + 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 + 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 + 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 + 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 + 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 + 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 + 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 + 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 + 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 + 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 + 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 + 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 + 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 + 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 + 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 + 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 + 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 + 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 + 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 + 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 + 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 + 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 + 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 + 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 + 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 + 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 + 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 + 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 + 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 + 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 + 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 + 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 + 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 + 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 + 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 + 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 + 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 + 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 + 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 + 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 + 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 + 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 + 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 + 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 + 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 + 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 + 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 + 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 + 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 + 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 + 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 + 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 + 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 + 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 + 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 + 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 + 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 + 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 + 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 + 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 + 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 + 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 + 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 + 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 + 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 + 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 + 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 + 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 + 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 + 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 + 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 + 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 + 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 + 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 + 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 + 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 + 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 + 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 + 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 + 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 + 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 + 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 + 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 + 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 + 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 + 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 + 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 + 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 + 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 + 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 + 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 + 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 + 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 + 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 + 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 + 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 + 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 + 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 + 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 + 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 + 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 + 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 + 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 + 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 + 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 + 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 + 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 + 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 + 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 + 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 + 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 + 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 + 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 + 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 + 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 + 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 + 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 + 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 + 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 + 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 + 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 + 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 + 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 + 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 + 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 + 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 + 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 + 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 + 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 + 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 + 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 + 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 + 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 + 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 + 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 + 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 + 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 + 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 + 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 + 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 + 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 + 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 + 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 + 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 + 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 + 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 + 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 + 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 + 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 + 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 + 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 + 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 + 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 + 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 + 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 + 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 + 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 + 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 + 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 + 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 + 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 + 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 + 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 + 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 + 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 + 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 + 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 + 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 + 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 + 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 + 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 + 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 +# + diff --git a/tests/reprsimil/test_brsa.py b/tests/reprsimil/test_brsa.py new file mode 100755 index 000000000..fbf898b86 --- /dev/null +++ b/tests/reprsimil/test_brsa.py @@ -0,0 +1,651 @@ +# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, +# Princeton University +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_can_instantiate(): + import brainiak.reprsimil.brsa + s = brainiak.reprsimil.brsa.BRSA() + assert s, "Invalid BRSA instance!" + + s = brainiak.reprsimil.brsa.BRSA( + n_iter=50, rank=5, auto_nuisance=False, n_nureg=2, nureg_method='ICA', + baseline_single=False, init_iter=5, GP_space=True, GP_inten=True, + tol=2e-3, eta=0.001, space_smooth_range=10.0, inten_smooth_range=100.0, + tau_range=2.0, + tau2_prior=brainiak.reprsimil.brsa.prior_GP_var_inv_gamma, + optimizer='CG', random_state=100, anneal_speed=20) + assert s, "Invalid BRSA instance!" + + +def test_fit(): + from brainiak.reprsimil.brsa import BRSA + import brainiak.utils.utils as utils + import scipy.stats + import numpy as np + import os.path + np.random.seed(10) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 2 times, mimicking 2 runs of itenditcal timing + n_run = 2 + design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) + design.n_TR = design.n_TR * n_run + + # start simulating some data + n_V = 50 + n_C = np.size(design.design_task, axis=1) + n_T = design.n_TR + + noise_bot = 0.5 + noise_top = 5.0 + noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot + + # generating noise + noise = np.zeros([n_T, n_V]) + noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ + np.random.randn(n_V) * noise_level + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_level = 5.0 # test with high SNR + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends + # not only on beta but also on x. + inten = np.random.rand(n_V) * 20.0 + + # parameters of Gaussian process to generate pseuso SNR + tau = 1.0 + smooth_width = 5.0 + inten_kernel = 1.0 + + coords = np.arange(0, n_V)[:, None] + + dist2 = np.square(coords - coords.T) + + inten_tile = np.tile(inten, [n_V, 1]) + inten_diff2 = (inten_tile - inten_tile.T)**2 + + K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / + inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 + + L = np.linalg.cholesky(K) + snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level + sqrt_v = noise_level * snr + betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v + signal = np.dot(design.design_task, betas_simulated) + + # Adding noise to signal as data + Y = signal + noise + inten + + scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) + + # Test fitting with GP prior. + brsa = BRSA(GP_space=True, GP_inten=True, n_iter=5, + init_iter=10, auto_nuisance=False, tol=2e-3) + + # We also test that it can detect baseline regressor included in the + # design matrix for task conditions + wrong_design = np.insert(design.design_task, 0, 1, axis=1) + with pytest.raises(ValueError) as excinfo: + brsa.fit(X=Y, design=wrong_design, scan_onsets=scan_onsets, + coords=coords, inten=inten) + assert ('Your design matrix appears to have included baseline time series.' + in str(excinfo.value)) + # Now we fit with the correct design matrix. + brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets, + coords=coords, inten=inten) + + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], + u_i[np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + + noise_new = np.zeros([n_T, n_V]) + noise_new[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise_new[i_t, :] = noise_new[i_t - 1, :] * \ + rho1 + np.random.randn(n_V) * noise_level + + Y_new = signal + noise_new + inten + ts, ts0 = brsa.transform(Y_new, scan_onsets=scan_onsets) + p = scipy.stats.pearsonr(ts[:, 0], design.design_task[:, 0])[1] + assert p < 0.01, ( + "Recovered time series does not correlate with true time series!") + assert np.shape(ts) == (n_T, n_C) and np.shape(ts0) == (n_T, 1), ( + "Wrong shape in returned time series by transform function!") + + [score, score_null] = brsa.score( + X=Y_new, design=design.design_task, scan_onsets=scan_onsets) + assert score > score_null, ( + "Full model does not win over null model on data containing signal") + + [score, score_null] = brsa.score(X=noise_new + inten, + design=design.design_task, + scan_onsets=scan_onsets) + assert score < score_null, ( + "Null model does not win over full model on data without signal") + + # Test fitting with lower rank, nuisance regressors and without GP prior + rank = n_C - 1 + n_nureg = 1 + brsa = BRSA(rank=rank, n_nureg=n_nureg, tol=2e-3, + n_iter=8, init_iter=4, auto_nuisance=True) + brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets) + # u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ + np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + + assert (not hasattr(brsa, 'bGP_') + and not hasattr(brsa, 'lGPspace_') + and not hasattr(brsa, 'lGPinten_') + ), ("the BRSA object should not have parameters of GP if GP is " + "not requested.") + # GP parameters are not set if not requested + assert brsa.beta0_.shape[0] == n_nureg + 1, 'Shape of beta0 incorrect' + p = scipy.stats.pearsonr(brsa.beta0_[0, :], inten)[1] + assert p < 0.01, ( + 'recovered beta0 does not correlate with the baseline of voxels.') + assert np.shape(brsa.L_) == ( + n_C, rank), 'Cholesky factor should have shape of (n_C, rank)' + + # Test fitting with GP over just spatial coordinates. + brsa = BRSA(GP_space=True, baseline_single=False, + tol=2e-3, n_iter=4, init_iter=4) + brsa.fit(X=Y, design=design.design_task, + scan_onsets=scan_onsets, coords=coords) + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ + np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + assert not hasattr(brsa, 'lGPinten_'), ( + "the BRSA object should not have parameters of lGPinten_ if only " + "smoothness in space is requested.") + # GP parameters are not set if not requested + + +def test_gradient(): + from brainiak.reprsimil.brsa import BRSA + import brainiak.utils.utils as utils + import numpy as np + import os.path + import numdifftools as nd + + np.random.seed(100) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + n_run = 4 + # concatenate it by 4 times, mimicking 4 runs of itenditcal timing + design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) + design.n_TR = design.n_TR * n_run + + # start simulating some data + n_V = 30 + n_C = np.size(design.design_task, axis=1) + n_T = design.n_TR + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot + + # generating noise + noise = np.zeros([n_T, n_V]) + noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ + np.random.randn(n_V) * noise_level + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0, 0] = 0.2 + ideal_cov[5:9, 5:9] = 0.6 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_level = 5.0 # test with high SNR + inten = np.random.randn(n_V) * 20.0 + + # parameters of Gaussian process to generate pseuso SNR + tau = 0.8 + smooth_width = 5.0 + inten_kernel = 1.0 + + coords = np.arange(0, n_V)[:, None] + + dist2 = np.square(coords - coords.T) + + inten_tile = np.tile(inten, [n_V, 1]) + inten_diff2 = (inten_tile - inten_tile.T)**2 + + K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / + inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 + + L = np.linalg.cholesky(K) + snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + sqrt_v = noise_level * snr + betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v + signal = np.dot(design.design_task, betas_simulated) + + # Adding noise to signal as data + Y = signal + noise + + scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) + + # Test fitting with GP prior. + brsa = BRSA(GP_space=True, GP_inten=True, rank=n_C) + + # Additionally, we test the generation of re-used terms. + X0 = np.ones(n_T)[:, None] + D, F, run_TRs, n_run_returned = brsa._prepare_DF( + n_T, scan_onsets=scan_onsets) + assert np.shape(D) == (n_T, n_T), 'D has wrong shape' + assert np.shape(F) == (n_T, n_T), 'F has wrong shape' + assert np.sum(D) == (n_T - n_run) * 2, 'D is initialized incorrectly.' + assert np.sum(F) == n_T - n_run * 2, 'F is initialized incorrectly.' + assert n_run_returned == n_run, ( + 'There is mistake in counting number of runs') + assert np.sum(run_TRs) == n_T, ( + 'The segmentation of the total experiment duration is wrong') + (XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX + ) = brsa._prepare_data_XY(design.design_task, Y, D, F) + (X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, X_base, + n_X0, idx_DC + ) = brsa._prepare_data_XYX0(design.design_task, Y, X0, + np.random.randn(n_T)[:, None], D, F, run_TRs, + no_DC=False) + assert (np.shape(XTY) == (n_C, n_V) + and np.shape(XTDY) == (n_C, n_V) + and np.shape(XTFY) == (n_C, n_V) + ), 'Dimension of XTY etc. returned from _prepare_data is wrong' + assert (np.ndim(YTY_diag) == 1 + and np.ndim(YTDY_diag) == 1 + and np.ndim(YTFY_diag) == 1 + ), ("Dimension of YTY_diag etc. returned from _prepare_data is " + "wrong") + assert (np.ndim(XTX) == 2 + and np.ndim(XTDX) == 2 + and np.ndim(XTFX) == 2 + ), 'Dimension of XTX etc. returned from _prepare_data is wrong' + assert (np.ndim(X0TX0) == 2 + and np.ndim(X0TDX0) == 2 + and np.ndim(X0TFX0) == 2 + ), 'Dimension of X0TX0 etc. returned from _prepare_data is wrong' + assert (np.ndim(XTX0) == 2 + and np.ndim(XTDX0) == 2 + and np.ndim(XTFX0) == 2 + ), 'Dimension of XTX0 etc. returned from _prepare_data is wrong' + assert (np.ndim(X0TY) == 2 + and np.ndim(X0TDY) == 2 + and np.ndim(X0TFY) == 2 + ), 'Dimension of X0TY etc. returned from _prepare_data is wrong' + assert (np.shape(X0) == (n_T, n_X0) + and np.shape(X_base) == (n_T, np.size(idx_DC)) + and np.max(idx_DC) < n_X0 + and np.size(idx_DC) + 1 == n_X0 + ), ("Dimension of X0 or X_base, or n_X0 or indices of DC " + "components are wrong.") + l_idx = np.tril_indices(n_C) + n_l = np.size(l_idx[0]) + + # Make sure all the fields are in the indices. + idx_param_sing, idx_param_fitU, idx_param_fitV = brsa._build_index_param( + n_l, n_V, 2) + assert 'Cholesky' in idx_param_sing and 'a1' in idx_param_sing, \ + 'The dictionary for parameter indexing misses some keys' + assert 'Cholesky' in idx_param_fitU and 'a1' in idx_param_fitU, \ + 'The dictionary for parameter indexing misses some keys' + assert 'log_SNR2' in idx_param_fitV and 'c_space' in idx_param_fitV \ + and 'c_inten' in idx_param_fitV and 'c_both' in idx_param_fitV, \ + 'The dictionary for parameter indexing misses some keys' + + # Initial parameters are correct parameters with some perturbation + param0_fitU = np.random.randn(n_l + n_V) * 0.1 + param0_fitV = np.random.randn(n_V + 1) * 0.1 + param0_sing = np.random.randn(n_l + 1) * 0.1 + param0_sing[idx_param_sing['a1']] += np.mean(np.tan(rho1 * np.pi / 2)) + param0_fitV[idx_param_fitV['log_SNR2']] += np.log(snr[:n_V - 1]) * 2 + param0_fitV[idx_param_fitV['c_space']] += np.log(smooth_width) * 2 + param0_fitV[idx_param_fitV['c_inten']] += np.log(inten_kernel) * 2 + + # test if the gradients are correct + # log likelihood and derivative of the _singpara function + + ll0, deriv0 = brsa._loglike_AR1_singpara(param0_sing, XTX, XTDX, XTFX, + YTY_diag, YTDY_diag, YTFY_diag, + XTY, XTDY, XTFY, X0TX0, X0TDX0, + X0TFX0, XTX0, XTDX0, XTFX0, X0TY, + X0TDY, X0TFY, l_idx, n_C, n_T, + n_V, n_run, n_X0, idx_param_sing) + # We test the gradient to the Cholesky factor + vec = np.zeros(np.size(param0_sing)) + vec[idx_param_sing['Cholesky'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, + XTDX0, XTFX0, X0TY, X0TDY, X0TFY, + l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_sing)[0], + param0_sing, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of singpara wrt Cholesky is incorrect') + + # We test the gradient to a1 + vec = np.zeros(np.size(param0_sing)) + vec[idx_param_sing['a1']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, + XTDX0, XTFX0, X0TY, X0TDY, X0TFY, + l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_sing)[0], + param0_sing, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), + rtol=1e-5), 'gradient of singpara wrt a1 is incorrect' + + # log likelihood and derivative of the fitU function. + ll0, deriv0 = brsa._loglike_AR1_diagV_fitU(param0_fitU, XTX, XTDX, XTFX, + YTY_diag, YTDY_diag, YTFY_diag, + XTY, XTDY, XTFY, X0TX0, X0TDX0, + X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, np.log(snr) + * 2, l_idx, n_C, n_T, n_V, + n_run, n_X0, idx_param_fitU, + n_C) + + # We test the gradient wrt the reparametrization of AR(1) coefficient of + # noise. + vec = np.zeros(np.size(param0_fitU)) + vec[idx_param_fitU['a1'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitU wrt to AR(1) coefficient incorrect') + + # We test if the numerical and analytical gradient wrt to the first + # element of Cholesky factor is correct + vec = np.zeros(np.size(param0_fitU)) + vec[idx_param_fitU['Cholesky'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitU wrt Cholesky factor incorrect') + + # Test on a random direction + vec = np.random.randn(np.size(param0_fitU)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), + rtol=1e-5), 'gradient of fitU incorrect' + + # We test the gradient of _fitV wrt to log(SNR^2) assuming no GP prior. + X0TAX0, XTAX0, X0TAY, X0TAX0_i, \ + XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL = \ + brsa._precompute_ar1_quad_forms(XTY, XTDY, XTFY, + YTY_diag, YTDY_diag, YTFY_diag, + XTX, XTDX, XTFX, + X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, + L_full, rho1, n_V, n_X0) + assert np.shape(XTAcorrX) == (n_V, n_C, n_C), ( + 'Dimension of XTAcorrX is wrong by _precompute_ar1_quad_forms()') + assert XTAcorrY.shape == XTY.shape, ( + 'Shape of XTAcorrY is wrong by _precompute_ar1_quad_forms()') + assert YTAcorrY.shape == YTY_diag.shape, ( + 'Shape of YTAcorrY is wrong by _precompute_ar1_quad_forms()') + assert np.shape(X0TAX0) == (n_V, n_X0, n_X0), ( + 'Dimension of X0TAX0 is wrong by _precompute_ar1_quad_forms()') + assert np.shape(XTAX0) == (n_V, n_C, n_X0), ( + 'Dimension of XTAX0 is wrong by _precompute_ar1_quad_forms()') + assert X0TAY.shape == X0TY.shape, ( + 'Shape of X0TAX0 is wrong by _precompute_ar1_quad_forms()') + assert np.all(np.isfinite(X0TAX0_i)), ( + 'Inverse of X0TAX0 includes NaN or Inf') + ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( + param0_fitV[idx_param_fitV['log_SNR2']], X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, + n_V, n_run, n_X0, idx_param_fitV, n_C, False, False) + vec = np.zeros(np.size(param0_fitV[idx_param_fitV['log_SNR2']])) + vec[idx_param_fitV['log_SNR2'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, + False, False)[0], + param0_fitV[idx_param_fitV['log_SNR2']], + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt log(SNR2) incorrect for model without GP') + + # We test the gradient of _fitV wrt to log(SNR^2) assuming GP prior. + ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( + param0_fitV, X0TAX0, XTAX0, X0TAY, X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_fitV, n_C, True, True, dist2, inten_diff2, 100, 100) + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['log_SNR2'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV srt log(SNR2) incorrect for model with GP') + + # We test the graident wrt spatial length scale parameter of GP prior + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['c_space']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt spatial length scale of GP incorrect') + + # We test the graident wrt intensity length scale parameter of GP prior + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['c_inten']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt intensity length scale of GP incorrect') + + # We test the graident on a random direction + vec = np.random.randn(np.size(param0_fitV)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV incorrect') + + +def test_nureg_determine(): + from brainiak.reprsimil.brsa import Ncomp_SVHT_MG_DLD_approx + import numpy as np + x = np.dot(np.random.randn(100, 5), np.random.randn(5, 40)) + \ + np.random.randn(100, 40) * 0.01 + ncomp = Ncomp_SVHT_MG_DLD_approx(x) + assert ncomp >= 3 and ncomp <= 8, ( + 'recovered number of components should be in a reasonable range') + + +def test_half_log_det(): + import numpy as np + from brainiak.reprsimil.brsa import BRSA + a = np.asarray([[1, 0.2], [0.2, 1]]) + brsa = BRSA() + half_log_det = np.log(np.linalg.det(a)) / 2 + assert np.isclose(half_log_det, brsa._half_log_det( + a)), 'half log determinant function is wrong' + + +def test_n_nureg(): + import brainiak.reprsimil.brsa + import numpy as np + # noise = np.random.randn(100,30) + noise = np.dot(np.random.randn(100, 8), np.random.randn( + 8, 30)) + np.random.randn(100, 30) * 0.01 + design = np.random.randn(100, 2) + s = brainiak.reprsimil.brsa.BRSA(n_iter=2) + s.fit(X=noise, design=design) + assert s.n_nureg_ > 2 and s.n_nureg_ < 16, ( + 'n_nureg_ estimation is wrong in BRSA') diff --git a/tests/reprsimil/test_gbrsa.py b/tests/reprsimil/test_gbrsa.py new file mode 100644 index 000000000..7038d35b4 --- /dev/null +++ b/tests/reprsimil/test_gbrsa.py @@ -0,0 +1,630 @@ +# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, +# Princeton University +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_can_instantiate(): + import brainiak.reprsimil.brsa + s = brainiak.reprsimil.brsa.GBRSA() + assert s, "Invalid GBRSA instance!" + + s = brainiak.reprsimil.brsa.GBRSA(n_iter=40, rank=4, auto_nuisance=False, + nureg_method='PCA', + baseline_single=False, logS_range=1.0, + SNR_bins=11, rho_bins=40, tol=2e-3, + optimizer='CG', random_state=0, + anneal_speed=20, SNR_prior='unif') + assert s, "Invalid GBRSA instance!" + + +def test_fit(): + from brainiak.reprsimil.brsa import GBRSA + import brainiak.utils.utils as utils + import scipy.stats + import numpy as np + import os.path + np.random.seed(10) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 1, 2, and 3 times, mimicking different length + # of experiments for different participants + n_run = [2, 1, 1] + design_mat = [None] * 3 + n_T = [None] * 3 + n_V = [40, 60, 60] + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + + # start simulating some data + n_C = np.size(design_mat[0], axis=1) + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = [None] * 3 + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = [None] * 3 + + # generating noise + noise = [None] * 3 + + # baseline + inten = [None] * 3 + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + n_T[i] = n_run[i] * design.n_TR + noise_level[i] = np.random.rand( + n_V[i]) * (noise_top - noise_bot) + noise_bot + # noise level is random. + rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot + + noise[i] = np.zeros([n_T[i], n_V[i]]) + noise[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ + np.random.randn(n_V[i]) * noise_level[i] + noise[i] = noise[i] + \ + np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) + inten[i] = np.random.rand(n_V[i]) * 20.0 + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_top = 5.0 # test with high SNR + snr_bot = 1.0 + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + + snr = [None] * 3 + signal = [None] * 3 + betas_simulated = [None] * 3 + scan_onsets = [None] * 3 + Y = [None] * 3 + for i in range(3): + snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot + sqrt_v = noise_level[i] * snr[i] + betas_simulated[i] = np.dot( + L_full, np.random.randn(n_C, n_V[i])) * sqrt_v + signal[i] = np.dot(design_mat[i], betas_simulated[i]) + + # Adding noise to signal as data + Y[i] = signal[i] + noise[i] + inten[i] + + scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) + + # Test fitting. + n_nureg = 2 + gbrsa = GBRSA(n_iter=15, auto_nuisance=True, logS_range=0.5, SNR_bins=11, + rho_bins=16, n_nureg=n_nureg, optimizer='L-BFGS-B') + + gbrsa.fit(X=Y, design=design_mat, scan_onsets=scan_onsets) + + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = gbrsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], + u_i[np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(gbrsa.nSNR_[0], snr[0])[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + p = scipy.stats.pearsonr(gbrsa.sigma_[1], noise_level[1])[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(gbrsa.rho_[2], rho1[2])[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + assert np.shape(gbrsa.X0_[1]) == (n_T[1], n_nureg + 1), "Wrong size of X0" + + Y_new = [None] * 3 + noise_new = [None] * 3 + for i in range(3): + noise_new[i] = np.zeros([n_T[i], n_V[i]]) + noise_new[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise_new[i][i_t, :] = noise_new[i][i_t - 1, :] * \ + rho1[i] + np.random.randn(n_V[i]) * noise_level[i] + + Y_new[i] = signal[i] + noise_new[i] + inten[i] + ts, ts0 = gbrsa.transform(Y_new, scan_onsets=scan_onsets) + [score, score_null] = gbrsa.score( + X=Y_new, design=design_mat, scan_onsets=scan_onsets) + [score_noise, score_null_noise] = gbrsa.score( + X=noise_new, design=design_mat, scan_onsets=scan_onsets) + for i in range(3): + assert np.shape(ts[i]) == (n_T[i], n_C) and np.shape( + ts0[i]) == (n_T[i], n_nureg + 1) + p = scipy.stats.pearsonr(ts[i][:, 0], design_mat[i][:, 0])[1] + assert p < 0.01, ( + "Recovered time series does not correlate with true time series!") + + assert score[i] > score_null[i], ( + "Full model does not win over null model on data containing " + "signal") + + assert score_noise[i] < score_null_noise[i], ( + "Null model does not win over full model on data without signal") + + [score, score_null] = gbrsa.score( + X=[None] * 3, design=design_mat, scan_onsets=scan_onsets) + assert score == [None] * 3 and score_null == [None] * \ + 3, "score did not return list of None when data is None" + ts, ts0 = gbrsa.transform(X=[None] * 3, scan_onsets=scan_onsets) + assert ts == [None] * 3 and ts0 == [None] * \ + 3, "transform did not return list of None when data is None" + + +def test_gradient(): + from brainiak.reprsimil.brsa import GBRSA + import brainiak.utils.utils as utils + import numpy as np + import os.path + import numdifftools as nd + + np.random.seed(100) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 1, 2, and 3 times, mimicking different length + # of experiments for different participants + n_run = [1, 2, 1] + design_mat = [None] * 3 + n_T = [None] * 3 + n_V = [30, 30, 20] + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + n_T[i] = n_run[i] * design.n_TR + + # start simulating some data + n_C = np.size(design_mat[0], axis=1) + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = [None] * 3 + for i in range(3): + noise_level[i] = np.random.rand( + n_V[i]) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = [None] * 3 + + # generating noise + noise = [None] * 3 + + # baseline + inten = [None] * 3 + for i in range(3): + rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot + noise[i] = np.zeros([n_T[i], n_V[i]]) + noise[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ + np.random.randn(n_V[i]) * noise_level[i] + noise[i] = noise[i] + \ + np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) + inten[i] = np.random.rand(n_V[i]) * 20.0 + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_top = 5.0 # test with high SNR + snr_bot = 1.0 + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + + snr = [None] * 3 + signal = [None] * 3 + betas_simulated = [None] * 3 + scan_onsets = [None] * 3 + Y = [None] * 3 + for i in range(3): + snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot + sqrt_v = noise_level[i] * snr[i] + betas_simulated[i] = np.dot( + L_full, np.random.randn(n_C, n_V[i])) * sqrt_v + signal[i] = np.dot(design_mat[i], betas_simulated[i]) + + # Adding noise to signal as data + Y[i] = signal[i] + noise[i] + inten[i] + + scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) + + # Get some initial fitting. + SNR_bins = 11 + rho_bins = 20 + gbrsa = GBRSA(n_iter=3, rank=n_C, SNR_bins=SNR_bins, + rho_bins=rho_bins, logS_range=0.5) + + n_grid = SNR_bins * rho_bins + half_log_det_X0TAX0 = [np.random.randn(n_grid) for i in range(3)] + log_weights = np.random.randn(n_grid) + log_fixed_terms = [np.random.randn(n_grid) for i in range(3)] + l_idx = np.tril_indices(n_C) + L_vec = np.random.randn(int(n_C * (n_C + 1) / 2)) + n_X0 = [2, 2, 2] + s = np.linspace(1, SNR_bins, n_grid) + a = np.linspace(0.5, 1, n_grid) + s2XTAcorrX = [None] * 3 + YTAcorrY_diag = [None] * 3 + sXTAcorrY = [None] * 3 + # The calculations below are quite arbitrary and do not conform + # to the model. They simply conform to the symmetry property and shape of + # the matrix indicated by the model + for i in range(3): + YTAcorrY_diag[i] = np.sum(Y[i] * Y[i], axis=0) * a[:, None] + s2XTAcorrX[i] = np.dot(design_mat[i].T, design_mat[ + i]) * s[:, None, None]**2 * a[:, None, None] + sXTAcorrY[i] = np.dot(design_mat[i].T, Y[i]) * \ + s[:, None, None] * a[:, None, None] + + # test if the gradients are correct + print(log_fixed_terms) + ll0, deriv0 = gbrsa._sum_loglike_marginalized(L_vec, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, + log_weights, log_fixed_terms, + l_idx, n_C, n_T, n_V, n_X0, + n_grid, rank=None) + # We test the gradient to the Cholesky factor + vec = np.random.randn(np.size(L_vec)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: gbrsa._sum_loglike_marginalized(x, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, + half_log_det_X0TAX0, + log_weights, log_fixed_terms, + l_idx, n_C, n_T, n_V, n_X0, + n_grid, rank=None)[0], + L_vec, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), 'gradient incorrect' + + +def test_SNR_grids(): + import brainiak.reprsimil.brsa + import numpy as np + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='unif', SNR_bins=10) + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.isclose(np.sum(SNR_weights), 1) + and np.isclose(np.std(SNR_weights[1:-1]), 0) + and np.all(SNR_weights > 0) + and np.isclose(np.min(SNR_grids), 0) + and np.all(SNR_grids >= 0) + and np.isclose(np.max(SNR_grids), 1) + ), 'SNR_weights or SNR_grids are incorrect for uniform prior' + assert np.isclose(np.ptp(np.diff(SNR_grids[1:-1])), 0), \ + 'SNR grids are not equally spaced for uniform prior' + assert (np.size(SNR_grids) == np.size(SNR_weights) + and np.size(SNR_grids) == 10 + ), ("size of SNR_grids or SNR_weights is not correct for uniform " + "prior") + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='lognorm', SNR_bins=35) + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids >= 0) + and np.isclose(np.sum(SNR_weights), 1) + and np.all(SNR_weights > 0) + and np.all(np.diff(SNR_grids) > 0) + ), 'SNR_grids or SNR_weights not correct for log normal prior' + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='exp') + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids >= 0) + and np.isclose(np.sum(SNR_weights), 1) + and np.all(SNR_weights > 0) + and np.all(np.diff(SNR_grids) > 0) + ), 'SNR_grids or SNR_weights not correct for exponential prior' + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='equal') + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids == 1) + and np.all(SNR_weights == 1) + and np.size(SNR_grids) == 1 + ), 'SNR_grids or SNR_weights not correct for equal prior' + + +def test_n_nureg(): + import brainiak.reprsimil.brsa + import numpy as np + noise = np.dot(np.random.randn(100, 8), np.random.randn( + 8, 30)) + np.random.randn(100, 30) * 0.001 + design = np.random.randn(100, 2) + s = brainiak.reprsimil.brsa.GBRSA(n_iter=2) + s.fit(X=noise, design=design) + assert s.n_nureg_[0] == 8, 'n_nureg_ estimation is wrong in GBRSA' + + +def test_grid_flatten_num_int(): + # Check for numeric integration of SNR, and correctly flattening 2-D grids + # to 1-D grid. + import brainiak.reprsimil.brsa + import brainiak.utils.utils as utils + import numpy as np + import scipy.special + n_V = 30 + n_T = 50 + n_C = 3 + design = np.random.randn(n_T, n_C) + U_simu = np.asarray([[1.0, 0.1, 0.0], [0.1, 1.0, 0.2], [0.0, 0.2, 1.0]]) + L_simu = np.linalg.cholesky(U_simu) + SNR = np.random.exponential(size=n_V) + beta = np.dot(L_simu, np.random.randn(n_C, n_V)) * SNR + noise = np.random.randn(n_T, n_V) + Y = np.dot(design, beta) + noise + X = design + X_base = None + scan_onsets = [0] + + s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, + SNR_prior='exp') + s.fit(X=[Y], design=[design]) + rank = n_C + l_idx, rank = s._chol_idx(n_C, rank) + L = np.zeros((n_C, rank)) + n_l = np.size(l_idx[0]) + current_vec_U_chlsk_l = s.random_state_.randn(n_l) * 10 + L[l_idx] = current_vec_U_chlsk_l + + # Now we change the grids for SNR and rho for testing. + s.SNR_bins = 2 + s.rho_bins = 2 + SNR_grids, SNR_weights = s._set_SNR_grids() + # rho_grids, rho_weights = s._set_rho_grids() + rho_grids = np.ones(2) * 0.1 + rho_weights = np.ones(2) / 2 + # We purposefully set all rhos to be equal to test flattening of + # grids. + n_grid = s.SNR_bins * s.rho_bins + + D, F, run_TRs, n_run = s._prepare_DF( + n_T, scan_onsets=scan_onsets) + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX \ + = s._prepare_data_XY(X, Y, D, F) + X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, \ + X_base, n_X0, idx_DC = s._prepare_data_XYX0( + X, Y, X_base, None, D, F, run_TRs, no_DC=False) + + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] + and half_log_det_X0TAX0[2] == half_log_det_X0TAX0[3] + and half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] + ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' + assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) + and np.array_equal(X0TAX0[2, :, :], X0TAX0[3, :, :]) + and np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0' + assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) + and np.array_equal(X0TAX0_i[2, :, :], X0TAX0_i[3, :, :]) + and np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0_i' + assert np.allclose( + np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), + np.eye(n_X0) + ), 'X0TAX0_i is not inverse of X0TAX0' + assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[1, :]) + and np.array_equal(YTAcorrY_diag[2, :], YTAcorrY_diag[3, :]) + and np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) + ), '_matrix_flattened_grid has mistake YTAcorrY_diag' + assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) + and np.array_equal(sXTAcorrY[2, :, :], sXTAcorrY[3, :, :]) + and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) + ), '_matrix_flattened_grid has mistake sXTAcorrY' + assert (np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) + and np.array_equal(X0TAY[2, :, :], X0TAY[3, :, :]) + and np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAY' + assert (np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) + and np.array_equal(XTAX0[2, :, :], XTAX0[3, :, :]) + and np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) + ), '_matrix_flattened_grid has mistake XTAX0' + + # Now we test the other way + rho_grids, rho_weights = s._set_rho_grids() + # rho_grids, rho_weights = s._set_rho_grids() + SNR_grids = np.ones(2) * 0.1 + SNR_weights = np.ones(2) / 2 + # We purposefully set all SNR to be equal to test flattening of + # grids. + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] + and half_log_det_X0TAX0[1] == half_log_det_X0TAX0[3] + and not half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] + ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' + assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) + and np.array_equal(X0TAX0[1, :, :], X0TAX0[3, :, :]) + and not np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0' + assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) + and np.array_equal(X0TAX0_i[1, :, :], X0TAX0_i[3, :, :]) + and not np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0_i' + assert np.allclose( + np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), + np.eye(n_X0) + ), 'X0TAX0_i is not inverse of X0TAX0' + assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) + and np.array_equal(YTAcorrY_diag[1, :], YTAcorrY_diag[3, :]) + and not np.array_equal(YTAcorrY_diag[0, :], + YTAcorrY_diag[1, :]) + ), '_matrix_flattened_grid has mistake YTAcorrY_diag' + assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) + and np.array_equal(sXTAcorrY[1, :, :], sXTAcorrY[3, :, :]) + and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) + ), '_matrix_flattened_grid has mistake sXTAcorrY' + assert (np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) + and np.array_equal(X0TAY[1, :, :], X0TAY[3, :, :]) + and not np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAY' + assert (np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) + and np.array_equal(XTAX0[1, :, :], XTAX0[3, :, :]) + and not np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) + ), '_matrix_flattened_grid has mistake XTAX0' + + # Now test the integration over SNR + s.SNR_bins = 50 + s.rho_bins = 1 + SNR_grids, SNR_weights = s._set_SNR_grids() + rho_grids, rho_weights = s._set_rho_grids() + n_grid = s.SNR_bins * s.rho_bins + + def setup_for_test(): + # This function will be re-used to set up the variables necessary for + # testing. + + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + log_weights = np.reshape( + np.log(SNR_weights[:, None]) + np.log(rho_weights), n_grid) + all_rho_grids = np.reshape(np.repeat( + rho_grids[None, :], s.SNR_bins, axis=0), n_grid) + log_fixed_terms = - (n_T - n_X0) / 2 * np.log(2 * np.pi) + n_run \ + / 2 * np.log(1 - all_rho_grids**2) + scipy.special.gammaln( + (n_T - n_X0 - 2) / 2) + (n_T - n_X0 - 2) / 2 * np.log(2) + return s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, \ + log_weights, log_fixed_terms + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, log_weights, + log_fixed_terms, l_idx, n_C, n_T, + n_V, n_X0, n_grid, rank=rank) + LL_total = - LL_total + # Now we re-calculate using scipy.integrate + s.SNR_bins = 100 + SNR_grids = np.linspace(0, 12, s.SNR_bins) + SNR_weights = np.exp(- SNR_grids) + SNR_weights = SNR_weights / np.sum(SNR_weights) + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, half_log_det_X0TAX0, + log_weights, log_fixed_terms, + n_C, n_T, n_V, n_X0, + n_grid, rank) + result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) + scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) + LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) + + tol = 1e-3 + assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ + 'Error of log likelihood calculation exceeds the tolerance' + + # Now test the log normal prior + s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, + SNR_prior='lognorm') + s.SNR_bins = 50 + s.rho_bins = 1 + SNR_grids, SNR_weights = s._set_SNR_grids() + rho_grids, rho_weights = s._set_rho_grids() + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, log_weights, + log_fixed_terms, l_idx, n_C, n_T, + n_V, n_X0, n_grid, rank=rank) + LL_total = - LL_total + # Now we re-calculate using scipy.integrate + s.SNR_bins = 400 + SNR_grids = np.linspace(1e-8, 20, s.SNR_bins) + log_SNR_weights = scipy.stats.lognorm.logpdf(SNR_grids, s=s.logS_range) + result_sum, max_value, result_exp = utils.sumexp_stable( + log_SNR_weights[:, None]) + SNR_weights = np.squeeze(result_exp / result_sum) + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, half_log_det_X0TAX0, + log_weights, log_fixed_terms, + n_C, n_T, n_V, n_X0, + n_grid, rank) + result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) + scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) + LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) + + tol = 1e-3 + assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ + 'Error of log likelihood calculation exceeds the tolerance' diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py new file mode 100644 index 000000000..7d472e6f5 --- /dev/null +++ b/tests/searchlight/test_searchlight.py @@ -0,0 +1,293 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import namedtuple + +import numpy as np +from mpi4py import MPI + +from brainiak.searchlight.searchlight import Searchlight +from brainiak.searchlight.searchlight import Diamond, Ball + +"""Distributed Searchlight Test +""" + + +def cube_sfn(l, msk, myrad, bcast_var): + if np.all(msk) and np.any(msk): + return 1.0 + return None + + +def test_searchlight_with_cube(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = True + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(cube_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def test_searchlight_with_cube_poolsize_1(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = True + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(cube_sfn, pool_size=1) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def diamond_sfn(l, msk, myrad, bcast_var): + assert not np.any(msk[~Diamond(3).mask_]) + if np.all(msk[Diamond(3).mask_]): + return 1.0 + return None + + +def test_searchlight_with_diamond(): + sl = Searchlight(sl_rad=3, shape=Diamond) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = Diamond(3).mask_ + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(diamond_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def ball_sfn(l, msk, myrad, bcast_var): + x, y, z = np.mgrid[-myrad:myrad+1, -myrad:myrad+1, -myrad:myrad+1] + correct_mask = np.square(x) + np.square(y) + np.square(z) <= myrad ** 2 + assert not np.any(msk[~Ball(3).mask_]) + if np.all(correct_mask == msk): + return 1.0 + return None + + +def test_searchlight_with_ball(): + sl = Searchlight(sl_rad=3, shape=Ball) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = Ball(3).mask_ + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(ball_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +MaskRadBcast = namedtuple("MaskRadBcast", "mask rad") + + +def test_instantiate(): + sl = Searchlight(sl_rad=5, max_blk_edge=10) + assert sl + + +def voxel_test_sfn(l, msk, myrad, bcast): + rad = bcast.rad + # Check each point + for subj in l: + for _tr in range(subj.shape[3]): + tr = subj[:, :, :, _tr] + midpt = tr[rad, rad, rad] + for d0 in range(tr.shape[0]): + for d1 in range(tr.shape[1]): + for d2 in range(tr.shape[2]): + assert np.array_equal(tr[d0, d1, d2] - midpt, + np.array([d0-rad, d1-rad, + d2-rad, 0])) + + # Determine midpoint + midpt = l[0][rad, rad, rad, 0] + midpt = (midpt[0], midpt[1], midpt[2]) + + for d0 in range(msk.shape[0]): + for d1 in range(msk.shape[1]): + for d2 in range(msk.shape[2]): + pt = (midpt[0] - rad + d0, midpt[1] - rad + d1, + midpt[2] - rad + d2) + assert bcast.mask[pt] == msk[d0, d1, d2] + + # Return midpoint + return midpt + + +def block_test_sfn(l, msk, myrad, bcast_var, extra_params): + outmat = l[0][:, :, :, 0] + outmat[~msk] = None + return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] + + +def test_correctness(): # noqa: C901 + def voxel_test(data, mask, max_blk_edge, rad): + + comm = MPI.COMM_WORLD + rank = comm.rank + + (dim0, dim1, dim2) = mask.shape + + # Initialize dataset with known pattern + for subj in data: + if subj is not None: + for tr in range(subj.shape[3]): + for d1 in range(dim0): + for d2 in range(dim1): + for d3 in range(dim2): + subj[d1, d2, d3, tr] = np.array( + [d1, d2, d3, tr]) + + sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) + sl.distribute(data, mask) + sl.broadcast(MaskRadBcast(mask, rad)) + global_outputs = sl.run_searchlight(voxel_test_sfn) + + if rank == 0: + for d0 in range(rad, global_outputs.shape[0]-rad): + for d1 in range(rad, global_outputs.shape[1]-rad): + for d2 in range(rad, global_outputs.shape[2]-rad): + if mask[d0, d1, d2]: + assert np.array_equal( + np.array(global_outputs[d0, d1, d2]), + np.array([d0, d1, d2])) + + def block_test(data, mask, max_blk_edge, rad): + + comm = MPI.COMM_WORLD + rank = comm.rank + + (dim0, dim1, dim2) = mask.shape + + # Initialize dataset with known pattern + for subj in data: + if subj is not None: + for tr in range(subj.shape[3]): + for d1 in range(dim0): + for d2 in range(dim1): + for d3 in range(dim2): + subj[d1, d2, d3, tr] = np.array( + [d1, d2, d3, tr]) + + sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) + sl.distribute(data, mask) + sl.broadcast(mask) + global_outputs = sl.run_block_function(block_test_sfn) + + if rank == 0: + for d0 in range(rad, global_outputs.shape[0]-rad): + for d1 in range(rad, global_outputs.shape[1]-rad): + for d2 in range(rad, global_outputs.shape[2]-rad): + if mask[d0, d1, d2]: + assert np.array_equal( + np.array(global_outputs[d0, d1, d2]), + np.array([d0, d1, d2, 0])) + + # Create dataset + def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + mask = np.random.choice([True, False], (dim0, dim1, dim2)) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + voxel_test(data, mask, max_blk_edge, rad) + block_test(data, mask, max_blk_edge, rad) + + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=1) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=1, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=0, dim1=10, dim2=8, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) diff --git a/tests/utils/example_design.1D b/tests/utils/example_design.1D new file mode 100755 index 000000000..7385edea6 --- /dev/null +++ b/tests/utils/example_design.1D @@ -0,0 +1,272 @@ +# + 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 + 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 + 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 + 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 + 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 + 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 + 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 + 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 + 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 + 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 + 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 + 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 + 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 + 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 + 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 + 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 + 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 + 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 + 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 + 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 + 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 + 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 + 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 + 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 + 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 + 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 + 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 + 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 + 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 + 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 + 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 + 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 + 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 + 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 + 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 + 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 + 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 + 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 + 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 + 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 + 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 + 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 + 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 + 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 + 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 + 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 + 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 + 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 + 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 + 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 + 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 + 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 + 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 + 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 + 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 + 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 + 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 + 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 + 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 + 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 + 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 + 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 + 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 + 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 + 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 + 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 + 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 + 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 + 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 + 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 + 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 + 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 + 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 + 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 + 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 + 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 + 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 + 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 + 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 + 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 + 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 + 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 + 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 + 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 + 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 + 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 + 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 + 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 + 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 + 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 + 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 + 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 + 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 + 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 + 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 + 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 + 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 + 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 + 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 + 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 + 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 + 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 + 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 + 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 + 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 + 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 + 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 + 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 + 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 + 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 + 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 + 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 + 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 + 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 + 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 + 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 + 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 + 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 + 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 + 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 + 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 + 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 + 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 + 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 + 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 + 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 + 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 + 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 + 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 + 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 + 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 + 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 + 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 + 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 + 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 + 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 + 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 + 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 + 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 + 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 + 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 + 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 + 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 + 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 + 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 + 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 + 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 + 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 + 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 + 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 + 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 + 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 + 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 + 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 + 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 + 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 + 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 + 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 + 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 + 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 + 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 + 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 + 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 + 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 + 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 + 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 + 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 + 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 + 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 + 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 + 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 + 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 + 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 + 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 + 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 + 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 + 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 + 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 + 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 + 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 + 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 + 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 + 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 + 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 + 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 + 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 +# + diff --git a/tests/utils/example_stimtime_1_AFNI.txt b/tests/utils/example_stimtime_1_AFNI.txt new file mode 100644 index 000000000..48200041d --- /dev/null +++ b/tests/utils/example_stimtime_1_AFNI.txt @@ -0,0 +1,2 @@ +5.2*2.0:2.0 40.0*4.0:1.5 +2.0*2.0:1.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_1_FSL.txt b/tests/utils/example_stimtime_1_FSL.txt new file mode 100644 index 000000000..ce14b1d55 --- /dev/null +++ b/tests/utils/example_stimtime_1_FSL.txt @@ -0,0 +1,3 @@ +5.2 2.0 2.0 +40.0 1.5 4.0 +50.0 1.0 2.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_2_AFNI.txt b/tests/utils/example_stimtime_2_AFNI.txt new file mode 100644 index 000000000..39d03d9ee --- /dev/null +++ b/tests/utils/example_stimtime_2_AFNI.txt @@ -0,0 +1 @@ +-10.0 diff --git a/tests/utils/example_stimtime_2_FSL.txt b/tests/utils/example_stimtime_2_FSL.txt new file mode 100644 index 000000000..e60beb2a6 --- /dev/null +++ b/tests/utils/example_stimtime_2_FSL.txt @@ -0,0 +1,3 @@ +5.2 2.0 +40.0 1.5 2.0 +50.0 \ No newline at end of file diff --git a/tests/utils/test_fmrisim.py b/tests/utils/test_fmrisim.py new file mode 100644 index 000000000..7c442661a --- /dev/null +++ b/tests/utils/test_fmrisim.py @@ -0,0 +1,858 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""fMRI Simulator test script + +Test script for generating a run of a participant's data. + + Authors: Cameron Ellis (Princeton) 2016 +""" +import numpy as np +import math +from brainiak.utils import fmrisim as sim +import pytest +from itertools import product + + +def test_generate_signal(): + + # Inputs for generate_signal + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [3] + feature_type = ['cube'] + feature_coordinates = np.array([[5, 5, 5]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + assert np.all(volume.shape == dimensions), "Check signal shape" + assert np.max(volume) == signal_magnitude, "Check signal magnitude" + assert np.sum(volume > 0) == math.pow(feature_size[0], 3), ( + "Check feature size") + assert volume[5, 5, 5] == signal_magnitude, "Check signal location" + assert volume[5, 5, 1] == 0, "Check noise location" + + feature_coordinates = np.array( + [[5, 5, 5], [3, 3, 3], [7, 7, 7]]) + + # Check feature size is correct + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=['loop', 'cavity', 'sphere'], + feature_size=[3], + signal_magnitude=signal_magnitude) + assert volume[5, 5, 5] == 0, "Loop is empty" + assert volume[3, 3, 3] == 0, "Cavity is empty" + assert volume[7, 7, 7] != 0, "Sphere is not empty" + + # Check feature size manipulation + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=['loop', 'cavity', 'sphere'], + feature_size=[1], + signal_magnitude=signal_magnitude) + assert volume[5, 6, 6] == 0, "Loop is too big" + assert volume[3, 5, 5] == 0, "Cavity is too big" + assert volume[7, 9, 9] == 0, "Sphere is too big" + + # Check that out of bounds feature coordinates are corrected + feature_coordinates = np.array([0, 2, dimensions[2]]) + x, y, z = sim._insert_idxs(feature_coordinates, feature_size[0], + dimensions) + assert x[1] - x[0] == 2, "x min not corrected" + assert y[1] - y[0] == 3, "y was corrected when it shouldn't be" + assert z[1] - z[0] == 1, "z max not corrected" + + # Check that signal patterns are created + feature_coordinates = np.array([[5, 5, 5]]) + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + signal_constant=0, + ) + assert volume[4:7, 4:7, 4:7].std() > 0, "Signal is constant" + + +def test_generate_stimfunction(): + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 100 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + assert stimfunction.shape[0] == duration * 100, "stimfunc incorrect length" + eventNumber = np.sum(event_durations * len(onsets)) * 100 + assert np.sum(stimfunction) == eventNumber, "Event number" + + # Create the signal function + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + stim_dur = stimfunction.shape[0] / (tr_duration * 100) + assert signal_function.shape[0] == stim_dur, "The length did not change" + + # Test + onsets = [0] + tr_duration = 1 + event_durations = [1] + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + max_response = np.where(signal_function != 0)[0].max() + assert 25 < max_response <= 30, "HRF has the incorrect length" + assert np.sum(signal_function < 0) > 0, "No values below zero" + + # Export a stimfunction + sim.export_3_column(stimfunction, + 'temp.txt', + ) + + # Load in the stimfunction + stimfunc_new = sim.generate_stimfunction(onsets=None, + event_durations=None, + total_time=duration, + timing_file='temp.txt', + ) + + assert np.all(stimfunc_new == stimfunction), "Export/import failed" + + # Break the timing precision of the generation + stimfunc_new = sim.generate_stimfunction(onsets=None, + event_durations=None, + total_time=duration, + timing_file='temp.txt', + temporal_resolution=0.5, + ) + + assert stimfunc_new.sum() == 0, "Temporal resolution not working right" + + # Set the duration to be too short so you should get an error + onsets = [10, 30, 50, 70, 90] + event_durations = [5] + with pytest.raises(ValueError): + sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=89, + ) + + # Clip the event offset + stimfunc_new = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=95, + ) + assert stimfunc_new[-1] == 1, 'Event offset was not clipped' + + # Test exporting a group of participants to an epoch file + cond_a = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=110, + ) + + cond_b = sim.generate_stimfunction(onsets=[x + 5 for x in onsets], + event_durations=event_durations, + total_time=110, + ) + + stimfunction_group = [np.hstack((cond_a, cond_b))] * 2 + sim.export_epoch_file(stimfunction_group, + 'temp.txt', + tr_duration, + ) + + # Check that convolve throws a warning when the shape is wrong + sim.convolve_hrf(stimfunction=np.hstack((cond_a, cond_b)).T, + tr_duration=tr_duration, + temporal_resolution=1, + ) + + +def test_apply_signal(): + + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[5, 5, 5]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 100 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + # Check that you can compute signal change appropriately + # Preset a bunch of things + stimfunction_tr = stimfunction[::int(tr_duration * 100)] + mask, template = sim.mask_brain(dimensions, mask_self=False) + noise_dict = sim._noise_dict_update({}) + noise = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=noise_dict, + iterations=[0, 0] + ) + coords = feature_coordinates[0] + noise_function_a = noise[coords[0], coords[1], coords[2], :] + noise_function_a = noise_function_a.reshape(duration // tr_duration, 1) + + noise_function_b = noise[coords[0] + 1, coords[1], coords[2], :] + noise_function_b = noise_function_b.reshape(duration // tr_duration, 1) + + # Create the calibrated signal with PSC + method = 'PSC' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + sig_b = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [1.0], + method, + ) + + assert sig_b.max() / sig_a.max() == 2, 'PSC modulation failed' + + # Create the calibrated signal with SFNR + method = 'SFNR' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a / (noise_function_a.mean() / noise_dict['sfnr']) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b / (noise_function_b.mean() / noise_dict['sfnr']) + + assert scaled_b.max() / scaled_a.max() == 2, 'SFNR modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-SD + method = 'CNR_Amp/Noise-SD' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a / noise_function_a.std() + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b / noise_function_b.std() + + assert scaled_b.max() / scaled_a.max() == 2, 'CNR_Amp modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-Var_dB + method = 'CNR_Amp2/Noise-Var_dB' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = np.log(sig_a.max() / noise_function_a.std()) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = np.log(sig_b.max() / noise_function_b.std()) + + assert np.round(scaled_b / scaled_a) == 2, 'CNR_Amp dB modulation failed' + + # Create the calibrated signal with CNR_Signal-SD/Noise-SD + method = 'CNR_Signal-SD/Noise-SD' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a.std() / noise_function_a.std() + sig_b = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b.std() / noise_function_a.std() + + assert (scaled_b / scaled_a) == 2, 'CNR signal modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-Var_dB + method = 'CNR_Signal-Var/Noise-Var_dB' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + + scaled_a = np.log(sig_a.std() / noise_function_a.std()) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = np.log(sig_b.std() / noise_function_b.std()) + + assert np.round(scaled_b / scaled_a) == 2, 'CNR signal dB modulation ' \ + 'failed' + + # Convolve the HRF with the stimulus sequence + signal = sim.apply_signal(signal_function=signal_function, + volume_signal=volume, + ) + + assert signal.shape == (dimensions[0], dimensions[1], dimensions[2], + duration / tr_duration), "The output is the " \ + "wrong size" + + signal = sim.apply_signal(signal_function=stimfunction, + volume_signal=volume, + ) + + assert np.any(signal == signal_magnitude), "The stimfunction is not binary" + + # Check that there is an error if the number of signal voxels doesn't + # match the number of non zero brain voxels + with pytest.raises(IndexError): + sig_vox = (volume > 0).sum() + vox_pattern = np.tile(stimfunction, (1, sig_vox - 1)) + sim.apply_signal(signal_function=vox_pattern, + volume_signal=volume, + ) + + +def test_generate_noise(): + + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[5, 5, 5]]) + signal_magnitude = [1] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 200 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + # Convolve the HRF with the stimulus sequence + signal = sim.apply_signal(signal_function=signal_function, + volume_signal=volume, + ) + + # Generate the mask of the signal + mask, template = sim.mask_brain(signal, + mask_self=None) + + assert min(mask[mask > 0]) > 0.1, "Mask thresholding did not work" + assert len(np.unique(template) > 2), "Template creation did not work" + + stimfunction_tr = stimfunction[::int(tr_duration * 100)] + + # Create the noise volumes (using the default parameters) + noise = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + iterations=[1, 0], + ) + + assert signal.shape == noise.shape, "The dimensions of signal and noise " \ + "the same" + + noise_high = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={'sfnr': 50, 'snr': 25}, + iterations=[1, 0], + ) + + noise_low = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={'sfnr': 100, 'snr': 25}, + iterations=[1, 0], + ) + + system_high = np.std(noise_high[mask > 0], 1).mean() + system_low = np.std(noise_low[mask > 0], 1).mean() + + assert system_low < system_high, "SFNR noise could not be manipulated" + + # Check that you check for the appropriate template values + with pytest.raises(ValueError): + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template * 2, + mask=mask, + noise_dict={}, + ) + + # Check that iterations does what it should + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={}, + iterations=[0, 0], + ) + + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={}, + iterations=None, + ) + + # Test drift noise + trs = 1000 + period = 100 + drift = sim._generate_noise_temporal_drift(trs, + tr_duration, + 'sine', + period, + ) + + # Check that the max frequency is the appropriate frequency + power = abs(np.fft.fft(drift))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs + period_freq = np.where(freq == 1 / (period // tr_duration)) + max_freq = np.argmax(power) + + assert period_freq == max_freq, 'Max frequency is not where it should be' + + # Do the same but now with cosine basis functions, answer should be close + drift = sim._generate_noise_temporal_drift(trs, + tr_duration, + 'discrete_cos', + period, + ) + + # Check that the appropriate frequency is peaky (may not be the max) + power = abs(np.fft.fft(drift))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs + period_freq = np.where(freq == 1 / (period // tr_duration))[0][0] + + assert power[period_freq] > power[period_freq + 1], 'Power is low' + assert power[period_freq] > power[period_freq - 1], 'Power is low' + + # Check it gives a warning if the duration is too short + drift = sim._generate_noise_temporal_drift(50, + tr_duration, + 'discrete_cos', + period, + ) + + # Test physiological noise (using unrealistic parameters so that it's easy) + timepoints = list(np.linspace(0, (trs - 1) * tr_duration, trs)) + resp_freq = 0.2 + heart_freq = 1.17 + phys = sim._generate_noise_temporal_phys(timepoints, + resp_freq, + heart_freq, + ) + + # Check that the max frequency is the appropriate frequency + power = abs(np.fft.fft(phys))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / (trs * tr_duration) + peaks = (power > (power.mean() + power.std())) # Where are the peaks + peak_freqs = freq[peaks] + + assert np.any(resp_freq == peak_freqs), 'Resp frequency not found' + assert len(peak_freqs) == 2, 'Two peaks not found' + + # Test task noise + sim._generate_noise_temporal_task(stimfunction_tr, + motion_noise='gaussian', + ) + sim._generate_noise_temporal_task(stimfunction_tr, + motion_noise='rician', + ) + + # Test ARMA noise + with pytest.raises(ValueError): + noise_dict = {'fwhm': 4, 'auto_reg_rho': [1], 'ma_rho': [1, 1]} + sim._generate_noise_temporal_autoregression(stimfunction_tr, + noise_dict, + dimensions, + mask, + ) + + # Generate spatial noise + vol = sim._generate_noise_spatial(np.array([10, 10, 10, trs])) + assert len(vol.shape) == 3, 'Volume was not reshaped to ignore TRs' + + # Switch some of the noise types on + noise_dict = dict(physiological_sigma=1, drift_sigma=1, task_sigma=1, + auto_reg_sigma=0) + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=noise_dict, + iterations=[0, 0], + ) + + +def test_generate_noise_spatial(): + + # Set up the inputs + dimensions = np.array([10, 5, 10]) + mask = np.ones(dimensions) + vol = sim._generate_noise_spatial(dimensions, mask) + + # Run the analysis from _calc_FHWM but for th elast step of aggregating + # across dimensions + v_count = 0 + v_sum = 0 + v_sq = 0 + + d_sum = [0.0, 0.0, 0.0] + d_sq = [0.0, 0.0, 0.0] + d_count = [0, 0, 0] + + # Pull out all the voxel coordinates + coordinates = list(product(range(dimensions[0]), + range(dimensions[1]), + range(dimensions[2]))) + + # Find the sum of squared error for the non-masked voxels in the brain + for i in list(range(len(coordinates))): + + # Pull out this coordinate + x, y, z = coordinates[i] + + # Is this within the mask? + if mask[x, y, z] > 0: + + # Find the the volume sum and squared values + v_count += 1 + v_sum += vol[x, y, z] + v_sq += vol[x, y, z] ** 2 + + # Get the volume variance + v_var = (v_sq - ((v_sum ** 2) / v_count)) / (v_count - 1) + + for i in list(range(len(coordinates))): + + # Pull out this coordinate + x, y, z = coordinates[i] + + # Is this within the mask? + if mask[x, y, z] > 0: + # For each xyz dimension calculate the squared + # difference of this voxel and the next + + in_range = (x < dimensions[0] - 1) + in_mask = in_range and (mask[x + 1, y, z] > 0) + included = in_mask and (~np.isnan(vol[x + 1, y, z])) + if included: + d_sum[0] += vol[x, y, z] - vol[x + 1, y, z] + d_sq[0] += (vol[x, y, z] - vol[x + 1, y, z]) ** 2 + d_count[0] += 1 + + in_range = (y < dimensions[1] - 1) + in_mask = in_range and (mask[x, y + 1, z] > 0) + included = in_mask and (~np.isnan(vol[x, y + 1, z])) + if included: + d_sum[1] += vol[x, y, z] - vol[x, y + 1, z] + d_sq[1] += (vol[x, y, z] - vol[x, y + 1, z]) ** 2 + d_count[1] += 1 + + in_range = (z < dimensions[2] - 1) + in_mask = in_range and (mask[x, y, z + 1] > 0) + included = in_mask and (~np.isnan(vol[x, y, z + 1])) + if included: + d_sum[2] += vol[x, y, z] - vol[x, y, z + 1] + d_sq[2] += (vol[x, y, z] - vol[x, y, z + 1]) ** 2 + d_count[2] += 1 + + # Find the variance + d_var = np.divide((d_sq - np.divide(np.power(d_sum, 2), + d_count)), (np.add(d_count, -1))) + + o_var = np.divide(-1, (4 * np.log(1 - (0.5 * d_var / v_var)))) + fwhm3 = np.sqrt(o_var) * 2 * np.sqrt(2 * np.log(2)) + + # Calculate the proportion of std relative to the mean + std_proportion = np.nanstd(fwhm3) / np.nanmean(fwhm3) + print(fwhm3) + assert std_proportion < 0.25, 'Variance is inconsistent across dim' + + +def test_mask_brain(): + + # Inputs for generate_signal + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[4, 4, 4]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Mask the volume to be the same shape as a brain + mask, _ = sim.mask_brain(dimensions, mask_self=None,) + brain = volume * mask + + assert np.sum(brain != 0) == np.sum(volume != 0), "Masking did not work" + assert brain[0, 0, 0] == 0, "Masking did not work" + assert brain[4, 4, 4] != 0, "Masking did not work" + + feature_coordinates = np.array( + [[1, 1, 1]]) + + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Mask the volume to be the same shape as a brain + mask, _ = sim.mask_brain(dimensions, mask_self=None, ) + brain = volume * mask + + assert np.sum(brain != 0) < np.sum(volume != 0), "Masking did not work" + + # Test that you can load the default + dimensions = np.array([100, 100, 100]) + mask, template = sim.mask_brain(dimensions, mask_self=False) + + assert mask[20, 80, 50] == 0, 'Masking didn''t work' + assert mask[25, 80, 50] == 1, 'Masking didn''t work' + assert int(template[25, 80, 50] * 100) == 57, 'Template not correct' + + # Check that you can mask self + mask_self, template_self = sim.mask_brain(template, mask_self=True) + + assert (template_self - template).sum() < 1e2, 'Mask self error' + assert (mask_self - mask).sum() == 0, 'Mask self error' + + +def test_calc_noise(): + + # Inputs for functions + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 200 + temporal_res = 100 + tr_number = int(np.floor(duration / tr_duration)) + dimensions_tr = np.array([10, 10, 10, tr_number]) + + # Preset the noise dict + nd_orig = sim._noise_dict_update({}) + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + temporal_resolution=temporal_res, + ) + + # Mask the volume to be the same shape as a brain + mask, template = sim.mask_brain(dimensions_tr, mask_self=None) + stimfunction_tr = stimfunction[::int(tr_duration * temporal_res)] + + nd_orig['matched'] = 0 + noise = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + ) + + # Check the spatial noise match + nd_orig['matched'] = 1 + noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + iterations=[50, 0] + ) + + # Calculate the noise parameters from this newly generated volume + nd_new = sim.calc_noise(noise, mask, template) + nd_matched = sim.calc_noise(noise_matched, mask, template) + + # Check the values are reasonable" + assert nd_new['snr'] > 0, 'snr out of range' + assert nd_new['sfnr'] > 0, 'sfnr out of range' + assert nd_new['auto_reg_rho'][0] > 0, 'ar out of range' + + # Check that the dilation increases SNR + no_dilation_snr = sim._calc_snr(noise_matched, + mask, + dilation=0, + reference_tr=tr_duration, + ) + + assert nd_new['snr'] > no_dilation_snr, "Dilation did not increase SNR" + + # Check that template size is in bounds + with pytest.raises(ValueError): + sim.calc_noise(noise, mask, template * 2) + + # Check that Mask is set is checked + with pytest.raises(ValueError): + sim.calc_noise(noise, None, template) + + # Check that it can deal with missing noise parameters + temp_nd = sim.calc_noise(noise, mask, template, noise_dict={}) + assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' + + temp_nd = sim.calc_noise(noise, mask, template, noise_dict=None) + assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' + + # Check that the fitting worked + snr_diff = abs(nd_orig['snr'] - nd_new['snr']) + snr_diff_match = abs(nd_orig['snr'] - nd_matched['snr']) + assert snr_diff > snr_diff_match, 'snr fit incorrectly' + + # Test that you can generate rician and exponential noise + sim._generate_noise_system(dimensions_tr, + 1, + 1, + spatial_noise_type='exponential', + temporal_noise_type='rician', + ) + + # Check the temporal noise match + nd_orig['matched'] = 1 + noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + iterations=[0, 50] + ) + + nd_matched = sim.calc_noise(noise_matched, mask, template) + + sfnr_diff = abs(nd_orig['sfnr'] - nd_new['sfnr']) + sfnr_diff_match = abs(nd_orig['sfnr'] - nd_matched['sfnr']) + assert sfnr_diff > sfnr_diff_match, 'sfnr fit incorrectly' + + ar1_diff = abs(nd_orig['auto_reg_rho'][0] - nd_new['auto_reg_rho'][0]) + ar1_diff_match = abs(nd_orig['auto_reg_rho'][0] - nd_matched[ + 'auto_reg_rho'][0]) + assert ar1_diff > ar1_diff_match, 'AR1 fit incorrectly' + + # Check that you can calculate ARMA for a single voxel + vox = noise[5, 5, 5, :] + arma = sim._calc_ARMA_noise(vox, + None, + sample_num=2, + ) + assert len(arma) == 2, "Two outputs not given by ARMA" diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py new file mode 100644 index 000000000..f37c51a7e --- /dev/null +++ b/tests/utils/test_utils.py @@ -0,0 +1,396 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + + +def test_tri_sym_convert(): + from brainiak.utils.utils import from_tri_2_sym, from_sym_2_tri + import numpy as np + + sym = np.random.rand(3, 3) + tri = from_sym_2_tri(sym) + assert tri.shape[0] == 6,\ + "from_sym_2_tri returned wrong result!" + sym1 = from_tri_2_sym(tri, 3) + assert sym1.shape[0] == sym1.shape[1],\ + "from_tri_2_sym returned wrong shape!" + tri1 = from_sym_2_tri(sym1) + assert np.array_equiv(tri, tri1),\ + "from_sym_2_tri returned wrong result!" + + +def test_sumexp(): + from brainiak.utils.utils import sumexp_stable + import numpy as np + + data = np.array([[1, 1], [0, 1]]) + sums, maxs, exps = sumexp_stable(data) + assert sums.size == data.shape[1], ( + "Invalid sum(exp(v)) computation (wrong # samples in sums)") + assert exps.shape[0] == data.shape[0], ( + "Invalid exp(v) computation (wrong # features)") + assert exps.shape[1] == data.shape[1], ( + "Invalid exp(v) computation (wrong # samples)") + assert maxs.size == data.shape[1], ( + "Invalid max computation (wrong # samples in maxs)") + + +def test_concatenate_not_none(): + from brainiak.utils.utils import concatenate_not_none + import numpy as np + arrays = [None] * 5 + + arrays[1] = np.array([0, 1, 2]) + arrays[3] = np.array([3, 4]) + + r = concatenate_not_none(arrays, axis=0) + + assert np.all(np.arange(5) == r), ( + "Invalid concatenation of a list of arrays") + + +def test_cov2corr(): + from brainiak.utils.utils import cov2corr + import numpy as np + cov = np.array([[4, 3, 0], [3, 9, 0], [0, 0, 1]]) + corr = cov2corr(cov) + assert np.allclose(corr, + np.array([[1, 0.5, 0], [0.5, 1, 0], [0, 0, 1]])), ( + "Converting from covariance matrix to correlation incorrect") + + +def test_ReadDesign(): + from brainiak.utils.utils import ReadDesign + import numpy as np + import os.path + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + design = ReadDesign(fname=file_path, include_orth=False, + include_pols=False) + assert design, 'Failed to read design matrix' + assert design.reg_nuisance is None, \ + 'Nuiance regressor is not None when include_orth and include_pols are'\ + ' both set to False' + read = ReadDesign() + assert read, 'Failed to initialize an instance of the class' + design = ReadDesign(fname=file_path, include_orth=True, include_pols=True) + assert np.size(design.cols_nuisance) == 10, \ + 'Mistake in counting the number of nuiance regressors' + assert np.size(design.cols_task) == 17, \ + 'Mistake in counting the number of task conditions' + assert (np.shape(design.reg_nuisance)[0] + == np.shape(design.design_task)[0] + ), 'The number of time points in nuiance regressor does not match'\ + ' that of task response' + + +def test_gen_design(): + from brainiak.utils.utils import gen_design + import numpy as np + import os.path + files = {'FSL1': 'example_stimtime_1_FSL.txt', + 'FSL2': 'example_stimtime_2_FSL.txt', + 'AFNI1': 'example_stimtime_1_AFNI.txt', + 'AFNI2': 'example_stimtime_2_AFNI.txt'} + for key in files.keys(): + files[key] = os.path.join(os.path.dirname(__file__), files[key]) + design1 = gen_design(stimtime_files=files['FSL1'], scan_duration=[48, 20], + TR=2, style='FSL') + assert design1.shape == (34, 1), 'Returned design matrix has wrong shape' + assert design1[24] == 0, ( + "gen_design should generated design matrix for each run separately " + "and concatenate them.") + design2 = gen_design(stimtime_files=[files['FSL1'], files['FSL2']], + scan_duration=[48, 20], TR=2, style='FSL') + assert design2.shape == (34, 2), 'Returned design matrix has wrong shape' + design3 = gen_design(stimtime_files=files['FSL1'], scan_duration=68, TR=2, + style='FSL') + assert design3[24] != 0, ( + 'design matrix should be non-zero 8 seconds after an event onset.') + design4 = gen_design(stimtime_files=[files['FSL2']], + scan_duration=[48, 20], TR=2, style='FSL') + assert np.all(np.isclose(design1 * 0.5, design4)), ( + 'gen_design does not treat missing values correctly') + design5 = gen_design(stimtime_files=[files['FSL2']], + scan_duration=[48, 20], TR=1) + assert (np.abs(design4 - design5[::2])).mean() < 0.1, ( + 'design matrices sampled at different frequency do not match' + ' at corresponding time points') + design6 = gen_design(stimtime_files=[files['AFNI1']], + scan_duration=[48, 20], TR=2, style='AFNI') + assert np.all(np.isclose(design1, design6)), ( + 'design matrices generated from AFNI style and FSL style do not match') + design7 = gen_design(stimtime_files=[files['AFNI2']], + scan_duration=[48], TR=2, style='AFNI') + assert np.all(design7 == 0.0), ( + 'A negative stimulus onset of AFNI style should result in an all-zero' + + ' design matrix') + + +def test_center_mass_exp(): + from brainiak.utils.utils import center_mass_exp + import numpy as np + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp([1, 2]) + assert ('interval must be a tuple' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((1, 2, 3)) + assert ('interval must be length two' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((-2, -1)) + assert ('interval_left must be non-negative' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((-2, 3)) + assert ('interval_left must be non-negative' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((3, 3)) + assert ('interval_right must be bigger than interval_left' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((1, 2), -1) + assert ('scale must be positive' + in str(excinfo.value)) + + result = center_mass_exp((0, np.inf), 2.0) + assert np.isclose(result, 2.0), 'center of mass '\ + 'incorrect for the whole distribution' + result = center_mass_exp((1.0, 1.0+2e-10)) + assert np.isclose(result, 1.0+1e-10), 'for a small '\ + 'enough interval, the center of mass should be '\ + 'close to its mid-point' + + +def test_p_from_null(): + import numpy as np + from brainiak.utils.utils import p_from_null + + # Create random null and observed value in tail + null = np.random.randn(10000) + observed = np.ceil(np.percentile(null, 97.5) * 1000) / 1000 + + # Check that we catch improper side + with pytest.raises(ValueError): + _ = p_from_null(observed, null, side='wrong') + + # Check two-tailed p-value for observed + p_ts = p_from_null(observed, null) + assert np.isclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for observed + p_right = p_from_null(observed, null, side='right') + assert np.isclose(p_right, 0.025, atol=1e-02) + assert np.isclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for observed + p_left = p_from_null(observed, null, side='left') + assert np.isclose(p_left, 0.975, atol=1e-02) + assert np.isclose(1 - p_left, p_right, atol=1e-02) + assert np.isclose(1 - p_left, p_ts / 2, atol=1e-02) + + # Check 2-dimensional input (i.e., samples by voxels) + null = np.random.randn(10000, 3) + observed = np.ceil(np.percentile(null, 97.5, axis=0) * 1000) / 1000 + + # Check two-tailed p-value for observed + p_ts = p_from_null(observed, null, axis=0) + assert np.allclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for observed + p_right = p_from_null(observed, null, side='right', axis=0) + assert np.allclose(p_right, 0.025, atol=1e-02) + assert np.allclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for observed + p_left = p_from_null(observed, null, side='left', axis=0) + assert np.allclose(p_left, 0.975, atol=1e-02) + assert np.allclose(1 - p_left, p_right, atol=1e-02) + assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) + + # Check for exact test + p_ts = p_from_null(observed, null, exact=True, axis=0) + assert np.allclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for exact + p_right = p_from_null(observed, null, side='right', + exact=True, axis=0) + assert np.allclose(p_right, 0.025, atol=1e-02) + assert np.allclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for exact + p_left = p_from_null(observed, null, side='left', + exact=True, axis=0) + assert np.allclose(p_left, 0.975, atol=1e-02) + assert np.allclose(1 - p_left, p_right, atol=1e-02) + assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) + + +def test_phase_randomize(): + import numpy as np + from scipy.fftpack import fft + from scipy.stats import pearsonr + from brainiak.utils.utils import phase_randomize + + data = np.repeat(np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], + 30, axis=1), + 20, axis=2) + assert np.array_equal(data[..., 0], data[..., 1]) + + # Phase-randomize data across subjects (same across voxels) + shifted_data = phase_randomize(data, voxelwise=False, random_state=1) + assert shifted_data.shape == data.shape + assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) + assert not np.array_equal(shifted_data[..., 0], data[..., 0]) + + # Check that uneven n_TRs doesn't explode + _ = phase_randomize(data[:-1, ...]) + + # Check that random_state returns same shifts + shifted_data_ = phase_randomize(data, voxelwise=False, random_state=1) + assert np.array_equal(shifted_data, shifted_data_) + + shifted_data_ = phase_randomize(data, voxelwise=False, random_state=2) + assert not np.array_equal(shifted_data, shifted_data_) + + # Phase-randomize subjects and voxels + shifted_data = phase_randomize(data, voxelwise=True, random_state=1) + assert shifted_data.shape == data.shape + assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) + assert not np.array_equal(shifted_data[..., 0], data[..., 0]) + assert not np.array_equal(shifted_data[:, 0, 0], shifted_data[:, 1, 0]) + + # Try with 2-dimensional input + shifted_data = phase_randomize(data[..., 0], + voxelwise=True, + random_state=1) + assert shifted_data.ndim == 2 + assert not np.array_equal(shifted_data[:, 0], shifted_data[:, 1]) + + # Create correlated noisy data + corr_data = np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], + 2, axis=2) + np.random.randn(60, 1, 2) + + # Get correlation and frequency domain for data + corr_r = pearsonr(corr_data[:, 0, 0], + corr_data[:, 0, 1])[0] + corr_freq = fft(corr_data, axis=0) + + # Phase-randomize time series and get correlation/frequency + shifted_data = phase_randomize(corr_data) + shifted_r = pearsonr(shifted_data[:, 0, 0], + shifted_data[:, 0, 1])[0] + shifted_freq = fft(shifted_data, axis=0) + + # Check that phase-randomization reduces correlation + assert np.abs(shifted_r) < np.abs(corr_r) + + # Check that amplitude spectrum is preserved + assert np.allclose(np.abs(shifted_freq), np.abs(corr_freq)) + + +def test_check_timeseries_input(): + import numpy as np + from itertools import combinations + from brainiak.utils.utils import _check_timeseries_input + + # Set a fixed vector for comparison + vector = np.random.randn(60) + + # List of subjects with one voxel/ROI + list_1d = [vector for _ in np.arange(10)] + (data_list_1d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_1d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Array of subjects with one voxel/ROI + array_2d = np.hstack([vector[:, np.newaxis] + for _ in np.arange(10)]) + (data_array_2d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_2d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # List of 2-dimensional arrays + list_2d = [vector[:, np.newaxis] for _ in np.arange(10)] + (data_list_2d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_2d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Check if lists have mismatching size + list_bad = [list_2d[0][:-1, :]] + list_2d[1:] + with pytest.raises(ValueError): + (data_list_bad, _, _, _) = _check_timeseries_input(list_bad) + + # List of 3-dimensional arrays + list_3d = [vector[:, np.newaxis, np.newaxis] + for _ in np.arange(10)] + (data_list_3d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_3d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # 3-dimensional array + array_3d = np.dstack([vector[:, np.newaxis] + for _ in np.arange(10)]) + (data_array_3d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_3d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Check that 4-dimensional input array throws error + array_4d = array_3d[..., np.newaxis] + with pytest.raises(ValueError): + (data_array_4d, _, _, _) = _check_timeseries_input(array_4d) + + # Check they're the same + for pair in combinations([data_list_1d, data_array_2d, + data_list_2d, data_list_3d, + data_array_3d], 2): + assert np.array_equal(pair[0], pair[1]) + + # List of multivoxel arrays + matrix = np.random.randn(60, 30) + list_mv = [matrix + for _ in np.arange(10)] + (data_list_mv, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_mv) + assert n_TRs == 60 + assert n_voxels == 30 + assert n_subjects == 10 + + # 3-dimensional array with multiple voxels + array_mv = np.dstack([matrix for _ in np.arange(10)]) + (data_array_mv, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_mv) + assert n_TRs == 60 + assert n_voxels == 30 + assert n_subjects == 10 + + assert np.array_equal(data_list_mv, data_array_mv) From 73b24f133ed1e2b6ebd190487ceb997c3e2c935c Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 17 Apr 2019 17:58:44 +0200 Subject: [PATCH 05/24] Revert "Revert "Add fastSRM algorithm and dependencies"" This reverts commit 56a0f1c71b9262298ad519a7544ae9cb7994ad82. --- brainiak/funcalign/fastsrm.py | 824 +++++++++++++++ requirements-dev.txt | 3 + tests/eventseg/test_event.py | 155 --- tests/factoranalysis/test_htfa.py | 168 --- tests/factoranalysis/test_tfa.py | 110 -- tests/fcma/data/expected_processed_data.npy | Bin 2672 -> 0 bytes tests/fcma/data/expected_raw_data.npy | Bin 5264 -> 0 bytes .../expected_searchlight_processed_data.npy | Bin 1704016 -> 0 bytes tests/fcma/test_classification.py | 222 ---- tests/fcma/test_mvpa_voxel_selection.py | 51 - tests/fcma/test_preprocessing.py | 109 -- tests/fcma/test_util.py | 59 -- tests/fcma/test_voxel_selection.py | 116 --- tests/hyperparamopt/test_hpo.py | 86 -- tests/image/test_image.py | 172 ---- tests/io/data/epoch_labels.npy | Bin 160 -> 0 bytes tests/io/data/mask.nii.gz | Bin 634 -> 0 bytes tests/io/data/subject1_bet.nii.gz | Bin 7149 -> 0 bytes tests/io/data/subject2_bet.nii.gz | Bin 7112 -> 0 bytes tests/io/test_io.py | 106 -- tests/isc/test_isc.py | 956 ------------------ tests/reprsimil/example_design.1D | 272 ----- tests/reprsimil/test_brsa.py | 651 ------------ tests/reprsimil/test_gbrsa.py | 630 ------------ tests/searchlight/test_searchlight.py | 293 ------ tests/utils/example_design.1D | 272 ----- tests/utils/example_stimtime_1_AFNI.txt | 2 - tests/utils/example_stimtime_1_FSL.txt | 3 - tests/utils/example_stimtime_2_AFNI.txt | 1 - tests/utils/example_stimtime_2_FSL.txt | 3 - tests/utils/test_fmrisim.py | 858 ---------------- tests/utils/test_utils.py | 396 -------- 32 files changed, 827 insertions(+), 5691 deletions(-) create mode 100644 brainiak/funcalign/fastsrm.py delete mode 100644 tests/eventseg/test_event.py delete mode 100644 tests/factoranalysis/test_htfa.py delete mode 100644 tests/factoranalysis/test_tfa.py delete mode 100644 tests/fcma/data/expected_processed_data.npy delete mode 100644 tests/fcma/data/expected_raw_data.npy delete mode 100644 tests/fcma/data/expected_searchlight_processed_data.npy delete mode 100644 tests/fcma/test_classification.py delete mode 100644 tests/fcma/test_mvpa_voxel_selection.py delete mode 100644 tests/fcma/test_preprocessing.py delete mode 100644 tests/fcma/test_util.py delete mode 100644 tests/fcma/test_voxel_selection.py delete mode 100644 tests/hyperparamopt/test_hpo.py delete mode 100644 tests/image/test_image.py delete mode 100644 tests/io/data/epoch_labels.npy delete mode 100755 tests/io/data/mask.nii.gz delete mode 100644 tests/io/data/subject1_bet.nii.gz delete mode 100644 tests/io/data/subject2_bet.nii.gz delete mode 100644 tests/io/test_io.py delete mode 100644 tests/isc/test_isc.py delete mode 100755 tests/reprsimil/example_design.1D delete mode 100755 tests/reprsimil/test_brsa.py delete mode 100644 tests/reprsimil/test_gbrsa.py delete mode 100644 tests/searchlight/test_searchlight.py delete mode 100755 tests/utils/example_design.1D delete mode 100644 tests/utils/example_stimtime_1_AFNI.txt delete mode 100644 tests/utils/example_stimtime_1_FSL.txt delete mode 100644 tests/utils/example_stimtime_2_AFNI.txt delete mode 100644 tests/utils/example_stimtime_2_FSL.txt delete mode 100644 tests/utils/test_fmrisim.py delete mode 100644 tests/utils/test_utils.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py new file mode 100644 index 000000000..c093a1be3 --- /dev/null +++ b/brainiak/funcalign/fastsrm.py @@ -0,0 +1,824 @@ +"""Fast Shared Response Model (FastSRM) + +The implementations are based on the following publications: + +.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", + P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge + Advances in Neural Information Processing Systems (NIPS), 2015. + http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model + +.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging + Datasets", + Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. + Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, + Kenneth A. Norman, + IEEE International Conference on Big Data, 2016. + https://doi.org/10.1109/BigData.2016.7840719 +""" + +# Author: Hugo Richard (INRIA - Parietal) +# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) and Bertrand Thirion (Inria - Parietal) +# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience Institute) and Javier Turek (Intel Labs) + +import logging + +import numpy as np +import scipy +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.utils import assert_all_finite +from sklearn.exceptions import NotFittedError +from mpi4py import MPI +import sys +from joblib import Parallel, delayed +import os +import glob +import hashlib + +__all__ = [ + "FastSRM", +] + +logger = logging.getLogger(__name__) + + +def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir=None): + """Reduce data using given atlas + + Parameters + ---------- + + img : str + path to data. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + atlas : array, shape=[n_supervoxels, n_voxels] or None or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + inv_atlas : array, shape=[n_voxels, n_supervoxels] + Pseudo inverse of the atlas (only for probabilistic atlases) + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and number of sessions are large + + Returns + ------- + + reduced_data : array, shape=[n_timeframes, n_supervoxels] + reduced data + """ + if atlas is None and inv_atlas is None: + AssertionError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") + + if inv_atlas is None and atlas is not None: + atlas_values = np.unique(atlas) + if 0 in atlas_values: + atlas_values = atlas_values[1:] + data = np.load(img) + reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T + else: + data = np.load(img) + reduced_data = data.dot(inv_atlas) + + if low_ram: + name = hashlib.md5(img.encode()).hexdigest() + path = os.path.join(temp_dir, "reduced_data_" + name) + np.save(path, reduced_data) + return path + else: + return reduced_data + + +def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): + """Reduce data using given atlas. + Work done in parallel across subjects. + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + n_jobs : integer, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and/or sessions is large + + Returns + ------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + + if len(atlas.shape) == 2: + A = None + A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) + else: + A = atlas + A_inv = None + + n_subjects, n_sessions = imgs.shape + + reduced_data_list = Parallel(n_jobs=n_jobs)( + delayed(reduce_data_single)( + img, + atlas=A, + inv_atlas=A_inv, + low_ram=low_ram, + temp_dir=temp_dir + ) for img in imgs.flatten()) + + if low_ram: + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) + else: + n_timeframes, n_voxels = reduced_data_list[0].shape + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_voxels)) + + return reduced_data_list + + +def _reduced_space_compute_shared_response(reduced_data_list, + reduced_basis_list, + n_components=50): + """Compute shared response with basis fixed in reduced space + + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + reduced_basis_list : None or list of array, element i has shape=[n_components, n_supervoxels] + each subject's reduced basis + if None the basis will be generated on the fly + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + """ + n_subjects, n_sessions = reduced_data_list.shape[:2] + if type(reduced_data_list[0, 0]) == np.ndarray: + low_ram = False + elif type(reduced_data_list[0, 0]) == str: + low_ram = True + else: + AssertionError("Reduced data are stored using type %s which is neither an ndarray or str" + % type(reduced_data_list[0, 0])) + low_ram = False + + s = [None] * n_sessions + + for n in range(n_subjects): + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + + if reduced_basis_list is None: + n_timeframes, n_supervoxels = data_nm.shape + reduced_basis_list = [] + for subject in range(n_subjects): + q = np.eye(n_components, n_supervoxels) + reduced_basis_list.append(q) + + basis_n = reduced_basis_list[n] + if s[m] is None: + s[m] = data_nm.dot(basis_n.T) + else: + s[m] = s + data_nm.dot(basis_n.T) + + for m in range(n_sessions): + s[m] = float(s[m]) / n_subjects + + return s + + +def _compute_and_save_corr_mat(img, shared_response, temp_dir): + """computes correlation matrix and stores it + + Parameters + ---------- + img : str + path to data. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + shared_response : array, shape=[n_timeframes, n_components] + shared response + """ + data = np.load(img) + name = hashlib.md5(img.encode()).hexdigest() + path = os.path.join(temp_dir, "corr_mat_" + name) + np.save(path, shared_response.T.dot(data)) + + +def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): + """computes correlation matrix for all sessions + + Parameters + ---------- + + subject_number: int + Number that identifies the subject. Basis will be stored in [temp_dir]/basis_[subject_number].npy + + sessions : array of str + Element i of the array is a path to the data collected during session i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + Returns + ------- + + basis: array, shape=[n_component, n_voxels] or str + basis of subject [subject_number] or path to this basis + """ + corr_mat = None + for session in sessions: + name = hashlib.md5(session.encode()).hexdigest() + path = os.path.join(temp_dir, "corr_mat_" + name + ".npy") + if corr_mat is None: + corr_mat = np.load(path) + else: + corr_mat += np.load(path) + basis_i = _compute_subject_basis(corr_mat) + + if temp_dir is None: + return basis_i + else: + path = os.path.join(temp_dir, "basis_" % subject_number) + np.save(path, basis_i) + return path + + +def _compute_subject_basis(corr_mat): + """From correlation matrix between shared response and subject data, + Finds subject's basis + + Parameters + ---------- + + corr_mat: array, shape=[n_component, n_voxels] or shape=[n_components, n_supervoxels] + correlation matrix between shared response and subject data or subject reduced data + element k, v is given by S.T.dot(X_i) where S is the shared response and + X_i the data of subject i. + + Returns + ------- + + basis: array, shape=[n_components, n_voxels] or shape=[n_components, n_supervoxels] + basis of subject or reduced_basis of subject + """ + if corr_mat.shape[0] == corr_mat.shape[1]: + U, _, V = scipy.linalg.svd( + corr_mat + 1.e-18 * np.eye(corr_mat.shape[0]), + full_matrices=False + ) + else: + U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) + return U.dot(V) + + +def fast_srm(reduced_data_list, n_iter=10, n_components=None): + """Computes shared response and basis in reduced space + + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + n_iter : int + Number of iterations performed + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + + if type(reduced_data_list[0, 0]) == np.ndarray: + low_ram = False + elif type(reduced_data_list[0, 0]) == str: + low_ram = True + else: + AssertionError("Reduced data are stored using type %s which is neither np.ndarray or str" + % type(reduced_data_list[0, 0])) + low_ram = False + + + n_subjects, n_sessions = reduced_data_list.shape[:2] + shared_response = _reduced_space_compute_shared_response( + reduced_data_list, + None, + n_components + ) + + reduced_basis = [None] * n_subjects + for _ in range(n_iter): + for n in range(n_subjects): + cov = None + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + if cov is None: + cov = shared_response[m].T.dot(data_nm) + else: + cov += shared_response[m].T.dot(data_nm) + reduced_basis[n] = _compute_subject_basis(cov) + + shared_response = _reduced_space_compute_shared_response( + reduced_data_list, + reduced_basis, + n_components + ) + + return shared_response + + +def _compute_basis_subject_online(sessions, shared_response_list): + """Computes subject's basis with shared response fixed + + Parameters + ---------- + + sessions : array of str + Element i of the array is a path to the data collected during session i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + Returns + ------- + + basis: array, shape=[n_components, n_voxels] + basis + """ + + basis_i = None + i = 0 + for session in sessions: + data = np.load(session) + if basis_i is None: + basis_i = shared_response_list[i].T.dot(data) + else: + basis_i += shared_response_list[i].T.dot(data) + i += 1 + del data + return _compute_subject_basis(basis_i) + + +def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subjects_indexes): + """Computes shared response during one session with basis fixed + + Parameters + ---------- + + subjects : array of str + Element i of the array is a path to the data of subject i. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis of all subjects, element i is the basis of subject i + + temp_dir : None or str + path to basis folder where file basis_%i.npy contains the basis of subject i + + subjects_indexes : list of int or None + list of indexes corresponding to the subjects to use to compute shared response + + Returns + ------- + + shared_response : array, shape=[n_timeframes, n_components] + shared response + """ + n = 0 + shared_response = None + for k, i in enumerate(subjects_indexes): + subject = subjects[k] + data = np.load(subject) + if temp_dir is None: + basis_i = basis_list[i] + else: + basis_i = np.load(os.path.join(temp_dir, "basis_%i.npy" % i)) + + if shared_response is None: + shared_response = data.dot(basis_i.T) + else: + shared_response += data.dot(basis_i.T) + + n += 1 + return shared_response / float(n) + + +def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects_indexes): + """Computes shared response with basis fixed + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis of all subjects, element i is the basis of subject i + + temp_dir : None or str + path to basis folder where file basis_%i.npy contains the basis of subject i + + n_jobs : integer, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + subjects_indexes : list or None + list of indexes corresponding to the subjects to use to compute shared response + + Returns + ------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + shared_response_list = Parallel(n_jobs=n_jobs)( + delayed(_compute_shared_response_online_single)( + subjects, + basis_list, + temp_dir, + subjects_indexes + ) for subjects in imgs.T) + + return shared_response_list + + +class FastSRM(BaseEstimator, TransformerMixin): + """SRM decomposition using a very low amount of memory and computational power + + Given multi-subject data, factorize it as a shared response S among all + subjects and an orthogonal transform (basis) W per subject: + + .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N + + Parameters + ---------- + + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + + n_components : int + Number of timecourses of the shared coordinates + + n_iter : int + Number of iterations to perform + + temp_dir : str or None + path to dir where temporary results are stored + if None temporary results will be stored in memory. This + can results in memory errors when the number of subjects + and / or sessions is large + + low_ram : bool + if True and temp_dir is not None, reduced_data will be saved on disk + this increases the number of IO but reduces memory complexity when the number + of subject and / or sessions is large + + random_state : int or RandomState + Pseudo number generator state used for random sampling. + + n_jobs : int, optional, default=1 + The number of CPUs to use to do the computation. + -1 means all CPUs, -2 all CPUs but one, and so on. + + verbose : bool or "warn" + if True, logs are enabled. + if False, logs are disabled. + if "warn" only warnings are printed. + + Attributes + ---------- + + `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str + basis of all subjects, element i is the basis of subject i + or path to basis of all subjects, element i is the path to the basis of subject i + """ + def __init__(self, + atlas, + n_components=20, + n_iter=100, + temp_dir=None, + low_ram=False, + random_state=None, + n_jobs=1, + verbose="warn",): + + self.random_state = random_state + self.n_jobs = n_jobs + self.verbose = verbose + self.n_components = n_components + self.n_iter = n_iter + self.atlas = atlas + + self.basis_list = None + + if temp_dir is None: + if self.verbose == "warn" or self.verbose is True: + logger.warning("temp_dir has value None. All basis (spatial maps) and " + "reconstructed data will therefore be kept in memory." + "This can lead to memory errors when the number of subjects " + "and/or sessions is large.") + + if temp_dir is not None: + if not os.path.exists(os.path.join(temp_dir, "fastsrm")): + os.mkdir(os.path.join(temp_dir, "fastsrm")) + self.temp_dir = os.path.join(temp_dir, "fastsrm") + + # Remove files in temp folder + paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) + for path in paths: + os.remove(path) + + self.low_ram = low_ram + + def fit(self, imgs): + """Computes basis across subjects from input imgs + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + ------- + self : object + Returns the instance itself. Contains attributes listed + at the object level. + """ + + if self.temp_dir is not None: + # Remove former basis in temp folder + paths = glob.glob(os.path.join(self.temp_dir, "basis") + "*") + for path in paths: + os.remove(path) + + if type(imgs) != np.ndarray: + AssertionError("imgs should be of type np.ndarray but is of type %s" + % type(imgs)) + + if len(imgs.shape) != 2: + AssertionError("imgs should be an array of shape [n_subjects, n_sessions] " + "but its shape is of size %i" + % len(imgs.shape)) + + if self.verbose is True: + n_subjects, n_sessions = imgs.shape + logger.info("Fitting using %i subjects and %i sessions per subject" + % (n_subjects, n_sessions)) + + if self.verbose is True: + logger.info("[FastSRM.fit] Reducing data") + + reduced_data = reduce_data( + imgs, + atlas=self.atlas, + n_jobs=self.n_jobs, + low_ram=self.low_ram, + temp_dir=self.temp_dir + ) + + if self.verbose: + logger.info("[FastSRM.fit] Finds shared response using reduced data") + + shared_response_list = fast_srm( + reduced_data, + n_iter=self.n_iter, + n_components=self.n_components, + ) + + if self.verbose: + print("[FastSRM.fit] Finds basis using full data and shared response") + + if self.n_jobs == 1: + basis = [] + for i, sessions in enumerate(imgs): + basis_i = _compute_basis_subject_online(sessions, shared_response_list) + if self.temp_dir is None: + basis.append(basis_i) + else: + path = os.path.join(self.temp_dir, "basis_%i" % i) + np.save(path, basis_i) + basis.append(path) + del basis_i + else: + Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_corr_mat)( + subject, + shared_response_list[m], + self.temp_dir + ) + for m, subjects in enumerate(imgs.T) + for subject in subjects + ) + + basis = Parallel(n_jobs=self.n_jobs)(delayed(_compute_and_save_subject_basis)(i, sessions, self.temp_dir) + for i, sessions in enumerate(imgs)) + + self.basis_list = basis + return self + + def fit_transform(self, imgs, **fit_params): + """Computes basis across subjects and shared response from input imgs + return shared response. + + Parameters + ---------- + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + -------- + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + self.fit(imgs) + return self.transform(imgs) + + def transform(self, imgs, subjects_indexes=None): + """From data in imgs and basis from training data, + computes shared response. + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i collected during session j. + Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + + subjects_indexes : list or None: + if None imgs[i] will be transformed using basis[i] + otherwise imgs[i] will be transformed using basis[subjects_index[i]] + + Returns + ------- + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ + if subjects_indexes is None: + subjects_indexes = np.arange(len(imgs)) + else: + subjects_indexes = np.array(subjects_indexes) + + shared_response = _compute_shared_response_online( + imgs, + self.basis_list, + self.temp_dir, + self.n_jobs, + subjects_indexes + ) + + return shared_response + + def inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None): + """From shared response and basis from training data reconstruct subject's data + + Parameters + ---------- + + shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + + subjects_indexes: list or None: + if None reconstructs data of all subjects' used during train + otherwise reconstructs data using subject's specified by subjects_indexes + + sessions_indexes: list or None: + if None reconstructs data using all sessions + otherwise uses only specified sessions + + Returns + ------- + reconstructed_data: array shape=[len(subjects_indexes), len(sessions_indexes), n_timeframes, n_voxels] + Reconstructed data for chosen subjects and sessions + """ + n_subjects = len(self.basis_list) + + if subjects_indexes is None: + subjects_indexes = np.arange(n_subjects) + else: + subjects_indexes = np.array(subjects_indexes) + + if sessions_indexes is None: + sessions_indexes = np.arange(len(shared_response_list)) + else: + sessions_indexes = np.array(sessions_indexes) + + data = [] + for i in subjects_indexes: + data_ = [] + if self.temp_dir is None: + basis_i = self.basis_list[i] + else: + basis_i = np.load(os.path.join(self.temp_dir, "basis_%i.npy" % i)) + + for j in sessions_indexes: + data_.append(shared_response_list[j].dot(basis_i)) + + data.append(np.array(data_)) + return np.array(data) diff --git a/requirements-dev.txt b/requirements-dev.txt index bf2baa3eb..9132617e1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,6 +8,9 @@ setuptools_scm sphinx sphinx_rtd_theme towncrier +joblib +os +glob # testing # Also add to .conda/meta.yaml diff --git a/tests/eventseg/test_event.py b/tests/eventseg/test_event.py deleted file mode 100644 index a82de27bd..000000000 --- a/tests/eventseg/test_event.py +++ /dev/null @@ -1,155 +0,0 @@ -from brainiak.eventseg.event import EventSegment -from scipy.special import comb -import numpy as np -import pytest -from sklearn.exceptions import NotFittedError - - -def test_create_event_segmentation(): - es = EventSegment(5) - assert es, "Invalid EventSegment instance" - - -def test_fit_shapes(): - K = 5 - V = 3 - T = 10 - es = EventSegment(K, n_iter=2) - sample_data = np.random.rand(V, T) - es.fit(sample_data.T) - - assert es.segments_[0].shape == (T, K), "Segmentation from fit " \ - "has incorrect shape" - assert np.isclose(np.sum(es.segments_[0], axis=1), np.ones(T)).all(), \ - "Segmentation from learn_events not correctly normalized" - - T2 = 15 - sample_data2 = np.random.rand(V, T2) - test_segments, test_ll = es.find_events(sample_data2.T) - - assert test_segments.shape == (T2, K), "Segmentation from find_events " \ - "has incorrect shape" - assert np.isclose(np.sum(test_segments, axis=1), np.ones(T2)).all(), \ - "Segmentation from find_events not correctly normalized" - - es_invalid = EventSegment(K) - with pytest.raises(ValueError, message="T < K should cause error"): - es_invalid.model_prior(K-1) - with pytest.raises(ValueError, message="#Events < K should cause error"): - es_invalid.set_event_patterns(np.zeros((V, K-1))) - - -def test_simple_boundary(): - es = EventSegment(2) - random_state = np.random.RandomState(0) - - sample_data = np.array([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + \ - random_state.rand(2, 7) * 10 - es.fit(sample_data.T) - - events = np.argmax(es.segments_[0], axis=1) - assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ - "Failed to correctly segment two events" - - events_predict = es.predict(sample_data.T) - assert np.array_equal(events_predict, [0, 0, 0, 1, 1, 1, 1]), \ - "Error in predict interface" - - -def test_event_transfer(): - es = EventSegment(2) - sample_data = np.asarray([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) - - with pytest.raises(NotFittedError, message="Should need to set variance"): - seg = es.find_events(sample_data.T)[0] - - with pytest.raises(NotFittedError, message="Should need to set patterns"): - seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] - - es.set_event_patterns(np.asarray([[1, 0], [0, 1]])) - seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] - - events = np.argmax(seg, axis=1) - assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ - "Failed to correctly transfer two events to new data" - - -def test_weighted_var(): - es = EventSegment(2) - - D = np.zeros((8, 4)) - for t in range(4): - D[t, :] = (1/np.sqrt(4/3)) * np.array([-1, -1, 1, 1]) - for t in range(4, 8): - D[t, :] = (1 / np.sqrt(4 / 3)) * np.array([1, 1, -1, -1]) - mean_pat = D[[0, 4], :].T - - weights = np.zeros((8, 2)) - weights[:, 0] = [1, 1, 1, 1, 0, 0, 0, 0] - weights[:, 1] = [0, 0, 0, 0, 1, 1, 1, 1] - assert np.array_equal( - es.calc_weighted_event_var(D, weights, mean_pat), [0, 0]),\ - "Failed to compute variance with 0/1 weights" - - weights[:, 0] = [1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5] - weights[:, 1] = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] - true_var = (4 * 0.5 * 12)/(6 - 5/6) * np.ones(2) / 4 - assert np.allclose( - es.calc_weighted_event_var(D, weights, mean_pat), true_var),\ - "Failed to compute variance with fractional weights" - - -def test_sym(): - es = EventSegment(4) - - evpat = np.repeat(np.arange(10).reshape(-1, 1), 4, axis=1) - es.set_event_patterns(evpat) - - D = np.repeat(np.arange(10).reshape(1, -1), 20, axis=0) - ev = es.find_events(D, var=1)[0] - - # Check that events 1-4 and 2-3 are symmetric - assert np.all(np.isclose(ev[:, :2], np.fliplr(np.flipud(ev[:, 2:])))),\ - "Fit with constant data is not symmetric" - - -def test_chains(): - es = EventSegment(5, event_chains=np.array(['A', 'A', 'B', 'B', 'B'])) - - es.set_event_patterns(np.array([[1, 1, 0, 0, 0], - [0, 0, 1, 1, 1]])) - sample_data = np.array([[0, 0, 0], [1, 1, 1]]) - seg = es.find_events(sample_data.T, 0.1)[0] - - ev = np.nonzero(seg > 0.99)[1] - assert np.array_equal(ev, [2, 3, 4]),\ - "Failed to fit with multiple chains" - - -def test_prior(): - K = 10 - T = 100 - - es = EventSegment(K) - mp = es.model_prior(T)[0] - - p_bound = np.zeros((T, K-1)) - norm = comb(T-1, K-1) - for t in range(T-1): - for k in range(K-1): - # See supplementary material of Neuron paper - # https://doi.org/10.1016/j.neuron.2017.06.041 - p_bound[t+1, k] = comb(t, k) * comb(T-t-2, K-k-2) / norm - p_bound = np.cumsum(p_bound, axis=0) - - mp_gt = np.zeros((T, K)) - for k in range(K): - if k == 0: - mp_gt[:, k] = 1 - p_bound[:, 0] - elif k == K - 1: - mp_gt[:, k] = p_bound[:, k-1] - else: - mp_gt[:, k] = p_bound[:, k-1] - p_bound[:, k] - - assert np.all(np.isclose(mp, mp_gt)),\ - "Prior does not match analytic solution" diff --git a/tests/factoranalysis/test_htfa.py b/tests/factoranalysis/test_htfa.py deleted file mode 100644 index 58591b130..000000000 --- a/tests/factoranalysis/test_htfa.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_R(): - from brainiak.factoranalysis.htfa import HTFA - with pytest.raises(TypeError) as excinfo: - HTFA() - assert "missing 2 required positional arguments" in str(excinfo.value) - - -def test_X(): - from brainiak.factoranalysis.htfa import HTFA - import numpy as np - - n_voxel = 100 - n_tr = 20 - K = 5 - max_global_iter = 3 - max_local_iter = 3 - max_voxel = n_voxel - max_tr = n_tr - - R = [] - n_subj = 2 - for s in np.arange(n_subj): - R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) - - htfa = HTFA( - K, - n_subj=n_subj, - max_global_iter=max_global_iter, - max_local_iter=max_local_iter, - max_voxel=max_voxel, - max_tr=max_tr) - - X = np.random.rand(n_voxel, n_tr) - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "Input data should be a list" in str(excinfo.value) - - X = [] - # Check that does NOT run with wrong array dimension - with pytest.raises(ValueError) as excinfo: - htfa.fit(X, R=R) - assert "Need at leat one subject to train the model" in str(excinfo.value) - - X = [] - X.append([1, 2, 3]) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "data should be an array" in str(excinfo.value) - - X = [] - X.append(np.random.rand(n_voxel)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "subject data should be 2D array" in str(excinfo.value) - - X = [] - for s in np.arange(n_subj): - X.append(np.random.rand(n_voxel, n_tr)) - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert "Coordinates should be a list" in str(excinfo.value) - - R = [] - R.append([1, 2, 3]) - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("Each scanner coordinate matrix should be an array" - in str(excinfo.value)) - - R = [] - R.append(np.random.rand(n_voxel)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("Each scanner coordinate matrix should be 2D array" - in str(excinfo.value)) - - R = [] - for s in np.arange(n_subj): - R.append(np.random.rand(n_voxel - 1, 3)) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - htfa.fit(X, R=R) - assert ("n_voxel should be the same in X[idx] and R[idx]" - in str(excinfo.value)) - - -def test_can_run(): - import numpy as np - from brainiak.factoranalysis.htfa import HTFA - from mpi4py import MPI - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - - n_voxel = 100 - n_tr = 20 - K = 5 - max_global_iter = 3 - max_local_iter = 3 - max_voxel = n_voxel - max_tr = n_tr - R = [] - n_subj = 2 - for s in np.arange(n_subj): - R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) - my_R = [] - for idx in np.arange(n_subj): - if idx % size == rank: - my_R.append(R[idx]) - - htfa = HTFA( - K, - n_subj=n_subj, - max_global_iter=max_global_iter, - max_local_iter=max_local_iter, - max_voxel=max_voxel, - max_tr=max_tr, - verbose=True) - assert htfa, "Invalid HTFA instance!" - - X = [] - for s in np.arange(n_subj): - X.append(np.random.rand(n_voxel, n_tr)) - my_data = [] - for idx in np.arange(n_subj): - if idx % size == rank: - my_data.append(X[idx]) - - if rank == 0: - htfa.fit(my_data, R=my_R) - assert True, "Root successfully running HTFA" - assert htfa.global_prior_.shape[0] == htfa.prior_bcast_size,\ - "Invalid result of HTFA! (wrong # element in global_prior)" - assert htfa.global_posterior_.shape[0] == htfa.prior_bcast_size,\ - "Invalid result of HTFA! (wrong # element in global_posterior)" - - else: - htfa.fit(my_data, R=my_R) - assert True, "worker successfully running HTFA" - print(htfa.local_weights_.shape) - assert htfa.local_weights_.shape[0] == n_tr * K,\ - "Invalid result of HTFA! (wrong # element in local_weights)" - assert htfa.local_posterior_.shape[0] == htfa.prior_size,\ - "Invalid result of HTFA! (wrong # element in local_posterior)" diff --git a/tests/factoranalysis/test_tfa.py b/tests/factoranalysis/test_tfa.py deleted file mode 100644 index 041d03cbd..000000000 --- a/tests/factoranalysis/test_tfa.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_tfa(): - from brainiak.factoranalysis.tfa import TFA - import numpy as np - - n_voxel = 100 - n_tr = 20 - K = 5 - max_iter = 5 - max_num_voxel = n_voxel - max_num_tr = n_tr - tfa = TFA( - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - assert tfa, "Invalid TFA instance!" - - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - X = [1, 2, 3] - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "Input data should be an array" in str(excinfo.value) - - X = np.random.rand(n_voxel) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "Input data should be 2D array" in str(excinfo.value) - - X = np.random.rand(n_voxel, n_tr) - R = [1, 2, 3] - # Check that does NOT run with wrong data type - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "coordinate matrix should be an array" in str(excinfo.value) - - R = np.random.rand(n_voxel) - # Check that does NOT run with wrong array dimension - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "coordinate matrix should be 2D array" in str(excinfo.value) - - R = np.random.randint(2, high=102, size=(n_voxel - 1, 3)) - # Check that does NOT run if n_voxel in X and R does not match - with pytest.raises(TypeError) as excinfo: - tfa.fit(X, R=R) - assert "The number of voxels should be the same in X and R" in str( - excinfo.value) - - R = np.random.randint(2, high=102, size=(n_voxel, 3)) - tfa.fit(X, R=R) - assert True, "Success running TFA with one subject!" - posterior_size = K * (tfa.n_dim + 1) - assert tfa.local_posterior_.shape[ - 0] == posterior_size,\ - "Invalid result of TFA! (wrong # element in local_posterior)" - - weight_method = 'ols' - tfa = TFA( - weight_method=weight_method, - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - assert tfa, "Invalid TFA instance!" - - X = np.random.rand(n_voxel, n_tr) - tfa.fit(X, R=R) - assert True, "Success running TFA with one subject!" - - template_prior, _, _ = tfa.get_template(R) - tfa.set_K(K) - tfa.set_seed(200) - tfa.fit(X, R=R, template_prior=template_prior) - assert True, "Success running TFA with one subject and template prior!" - assert tfa.local_posterior_.shape[ - 0] == posterior_size,\ - "Invalid result of TFA! (wrong # element in local_posterior)" - - weight_method = 'odd' - tfa = TFA( - weight_method=weight_method, - K=K, - max_iter=max_iter, - verbose=True, - max_num_voxel=max_num_voxel, - max_num_tr=max_num_tr) - with pytest.raises(ValueError) as excinfo: - tfa.fit(X, R=R) - assert "'rr' and 'ols' are accepted as weight_method!" in str( - excinfo.value) diff --git a/tests/fcma/data/expected_processed_data.npy b/tests/fcma/data/expected_processed_data.npy deleted file mode 100644 index 460553dff13f0839b8571f9a0943cfb4eb237fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2672 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 Niah$^!R=(7{0Gj{kZb?| diff --git a/tests/fcma/data/expected_searchlight_processed_data.npy b/tests/fcma/data/expected_searchlight_processed_data.npy deleted file mode 100644 index e23174f2c7f6c3458e2fcb6f9bee6b5ecf7970c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1704016 zcmeI*F>BjE6aZj%?XS@07CaS`p%k)ZYsu1~Q!$N0htd?gTM6Vp^auH`RZj06_!&i& zX%c}xfq8zXJL&vApGMv94v{Bli0hZA z4i5_))gk#+-)P?*?zdIPceTg3j`;BGoUQ&HzI{TqKH2}o>-F8w`9pzPzwFt)4mm*`;g>*DSwhl zPxvOC^(uAfqlvmkzsS3;8~wz86UdG+sygq=eoQ&WI*RtlBX+dM^~L`qkn1-zk6Cn$ zi}T{+2xRwsm%0vPU8R`&%Lx!5K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfWR08>ihWZUonPq9L=HDr|g4}JM^AUb16VFKAZ=fke1Hvs|!2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF c5FkK+009C72oNAZfB*pk1PBlyKwzlAA8SKUk^lez diff --git a/tests/fcma/test_classification.py b/tests/fcma/test_classification.py deleted file mode 100644 index 3225fa80d..000000000 --- a/tests/fcma/test_classification.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.classifier import Classifier -from scipy.stats.mstats import zscore -from sklearn import svm -from sklearn.linear_model import LogisticRegression -import numpy as np -import math -from numpy.random import RandomState -from scipy.spatial.distance import hamming - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def create_epoch(idx, num_voxels): - row = 12 - col = num_voxels - mat = prng.rand(row, col).astype(np.float32) - # impose a pattern to even epochs - if idx % 2 == 0: - mat = np.sort(mat, axis=0) - mat = zscore(mat, axis=0, ddof=0) - # if zscore fails (standard deviation is zero), - # set all values to be zero - mat = np.nan_to_num(mat) - mat = mat / math.sqrt(mat.shape[0]) - return mat - - -def test_classification(): - fake_raw_data = [create_epoch(i, 5) for i in range(20)] - labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - # 5 subjects, 4 epochs per subject - epochs_per_subj = 4 - # svm - svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - training_data = fake_raw_data[0:12] - clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data)), labels[0:12]) - expected_confidence = np.array([-1.18234421, 0.97403604, -1.04005679, - 0.92403019, -0.95567738, 1.11746593, - -0.83275891, 0.9486868]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM does not provide correct results' - confidence = clf.decision_function(list(zip(fake_raw_data[12:], - fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence) - ) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM without recomputation ' \ - 'does not provide correct results' - y = [0, 1, 0, 1, 0, 1, 0, 1] - score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data[12:])), y) - assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ - 'the prediction score is incorrect' - # svm with partial similarity matrix computation - clf = Classifier(svm_clf, num_processed_voxels=2, - epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(fake_raw_data, fake_raw_data)), - labels, - num_training_samples=12) - y_pred = clf.predict() - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM (partial sim) does not ' \ - 'provide correct results' - confidence = clf.decision_function() - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM (partial sim) without recomputation ' \ - 'does not provide correct results' - # logistic regression - lr_clf = LogisticRegression() - clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data)), labels[0:12]) - expected_confidence = np.array([-4.49666484, 3.73025553, -4.04181695, - 3.73027436, -3.77043872, 4.42613412, - -3.35616616, 3.77716609]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) - expected_output = [0, 0, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via logistic regression ' \ - 'does not provide correct results' - confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence) - ) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression without precomputation ' \ - 'does not provide correct results' - - -def test_classification_with_two_components(): - fake_raw_data = [create_epoch(i, 5) for i in range(20)] - fake_raw_data2 = [create_epoch(i, 6) for i in range(20)] - labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] - # 5 subjects, 4 epochs per subject - epochs_per_subj = 4 - # svm - svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - training_data = fake_raw_data[0: 12] - training_data2 = fake_raw_data2[0: 12] - clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(training_data, training_data2)), labels[0:12]) - expected_confidence = np.array([-1.23311606, 1.02440964, -0.93898336, - 1.07028798, -1.04420007, 0.97647772, - -1.0498268, 1.04970111]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM does not provide correct results' - confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM without recomputation ' \ - 'does not provide correct results' - y = [0, 1, 0, 1, 0, 1, 0, 1] - score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data2[12:])), y) - assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ - 'the prediction score is incorrect' - # svm with partial similarity matrix computation - clf = Classifier(svm_clf, num_processed_voxels=2, - epochs_per_subj=epochs_per_subj) - clf.fit(list(zip(fake_raw_data, fake_raw_data2)), - labels, - num_training_samples=12) - y_pred = clf.predict() - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via SVM (partial sim) does not ' \ - 'provide correct results' - confidence = clf.decision_function() - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of SVM (partial sim) without recomputation ' \ - 'does not provide correct results' - # logistic regression - lr_clf = LogisticRegression() - clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) - # specifying num_training_samples is for coverage - clf.fit(list(zip(training_data, training_data2)), - labels[0:12], - num_training_samples=12) - expected_confidence = np.array([-4.90819848, 4.22548132, -3.76255726, - 4.46505975, -4.19933099, 4.08313584, - -4.23070437, 4.31779758]) - recomputed_confidence = clf.decision_function(list(zip( - fake_raw_data[12:], fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(recomputed_confidence) - ) * expected_confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression with recomputation ' \ - 'does not provide correct results' - y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) - expected_output = [0, 1, 0, 1, 0, 1, 0, 1] - hamming_distance = hamming(y_pred, expected_output) * len(y_pred) - assert hamming_distance <= 1, \ - 'classification via logistic regression ' \ - 'does not provide correct results' - confidence = clf.decision_function(list(zip(fake_raw_data[12:], - fake_raw_data2[12:]))) - hamming_distance = hamming(np.sign(expected_confidence), - np.sign(confidence)) * confidence.size - assert hamming_distance <= 1, \ - 'decision function of logistic regression without precomputation ' \ - 'does not provide correct results' - - -if __name__ == '__main__': - test_classification() - test_classification_with_two_components() diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py deleted file mode 100644 index d551ff5ee..000000000 --- a/tests/fcma/test_mvpa_voxel_selection.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector -from brainiak.searchlight.searchlight import Searchlight -from sklearn import svm -import numpy as np -from mpi4py import MPI -from numpy.random import RandomState - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def test_mvpa_voxel_selection(): - data = prng.rand(5, 5, 5, 8).astype(np.float32) - # all MPI processes read the mask; the mask file is small - mask = np.ones([5, 5, 5], dtype=np.bool) - mask[0, 0, :] = False - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - sl = Searchlight(sl_rad=1) - mvs = MVPAVoxelSelector(data, mask, labels, 2, sl) - # for cross validation, use SVM with precomputed kernel - - clf = svm.SVC(kernel='rbf', C=10) - result_volume, results = mvs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [] - for tuple in results: - if tuple[1] > 0: - output.append(int(8*tuple[1])) - expected_output = [6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, - 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - - -if __name__ == '__main__': - test_mvpa_voxel_selection() diff --git a/tests/fcma/test_preprocessing.py b/tests/fcma/test_preprocessing.py deleted file mode 100644 index 0ee38db49..000000000 --- a/tests/fcma/test_preprocessing.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path - -import numpy as np - -from brainiak.fcma.preprocessing import (prepare_fcma_data, prepare_mvpa_data, - prepare_searchlight_mvpa_data) -from brainiak import io - -data_dir = Path(__file__).parents[1] / 'io' / 'data' -expected_dir = Path(__file__).parent / 'data' -suffix = 'bet.nii.gz' -mask_file = data_dir / 'mask.nii.gz' -epoch_file = data_dir / 'epoch_labels.npy' -expected_labels = np.array([0, 1, 0, 1]) - - -def test_prepare_fcma_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - mask = io.load_boolean_mask(mask_file) - conditions = io.load_labels(epoch_file) - raw_data, _, labels = prepare_fcma_data(images, conditions, mask) - expected_raw_data = np.load(expected_dir / 'expected_raw_data.npy') - assert len(raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - for idx in range(len(raw_data)): - assert np.allclose(raw_data[idx], expected_raw_data[idx]), \ - 'raw data do not match in test_prepare_fcma_data' - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_fcma_data' - from brainiak.fcma.preprocessing import RandomType - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, - random=RandomType.REPRODUCIBLE) - assert len(random_raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, - random=RandomType.UNREPRODUCIBLE) - assert len(random_raw_data) == len(expected_raw_data), \ - 'numbers of epochs do not match in test_prepare_fcma_data' - - -def test_prepare_mvpa_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - mask = io.load_boolean_mask(mask_file) - conditions = io.load_labels(epoch_file) - processed_data, labels = prepare_mvpa_data(images, conditions, mask) - expected_processed_data = np.load(expected_dir - / 'expected_processed_data.npy') - assert len(processed_data) == len(expected_processed_data), \ - 'numbers of epochs do not match in test_prepare_mvpa_data' - for idx in range(len(processed_data)): - assert np.allclose(processed_data[idx], - expected_processed_data[idx]), ( - 'raw data do not match in test_prepare_mvpa_data') - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_mvpa_data' - - -def test_prepare_searchlight_mvpa_data(): - images = io.load_images_from_dir(data_dir, suffix=suffix) - conditions = io.load_labels(epoch_file) - processed_data, labels = prepare_searchlight_mvpa_data(images, - conditions) - expected_searchlight_processed_data = np.load( - expected_dir / 'expected_searchlight_processed_data.npy') - for idx in range(len(processed_data)): - assert np.allclose(processed_data[idx], - expected_searchlight_processed_data[idx]), ( - 'raw data do not match in test_prepare_searchlight_mvpa_data') - assert np.array_equal(labels, expected_labels), \ - 'the labels do not match in test_prepare_searchlight_mvpa_data' - from brainiak.fcma.preprocessing import RandomType - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_processed_data, _ = prepare_searchlight_mvpa_data( - images, - conditions, - random=RandomType.REPRODUCIBLE) - assert (len(random_processed_data) - == len(expected_searchlight_processed_data)), ( - 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') - images = io.load_images_from_dir(data_dir, suffix=suffix) - random_processed_data, _ = prepare_searchlight_mvpa_data( - images, - conditions, - random=RandomType.UNREPRODUCIBLE) - assert (len(random_processed_data) - == len(expected_searchlight_processed_data)), ( - 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') - - -if __name__ == '__main__': - test_prepare_fcma_data() - test_prepare_mvpa_data() - test_prepare_searchlight_mvpa_data() diff --git a/tests/fcma/test_util.py b/tests/fcma/test_util.py deleted file mode 100644 index 6508d49b1..000000000 --- a/tests/fcma/test_util.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from numpy.random import RandomState -from brainiak.fcma.util import compute_correlation - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def test_correlation_computation(): - row1 = 5 - col = 10 - row2 = 6 - mat1 = prng.rand(row1, col).astype(np.float32) - mat2 = prng.rand(row2, col).astype(np.float32) - corr = compute_correlation(mat1, mat1) - expected_corr = np.corrcoef(mat1) - assert np.allclose(corr, expected_corr, atol=1e-5), ( - "high performance correlation computation does not provide correct " - "correlation results within the same set") - corr = compute_correlation(mat1, mat2) - mat = np.concatenate((mat1, mat2), axis=0) - expected_corr = np.corrcoef(mat)[0:row1, row1:] - assert np.allclose(corr, expected_corr, atol=1e-5), ( - "high performance correlation computation does not provide correct " - "correlation results between two sets") - - -def test_correlation_nans(): - row1 = 5 - col = 10 - row2 = 6 - mat1 = prng.rand(row1, col).astype(np.float32) - mat2 = prng.rand(row2, col).astype(np.float32) - mat1[0, 0] = np.nan - corr = compute_correlation(mat1, mat2, return_nans=False) - assert np.all(corr == 0, axis=1)[0] - assert np.sum(corr == 0) == row2 - corr = compute_correlation(mat1, mat2, return_nans=True) - assert np.all(np.isnan(corr), axis=1)[0] - assert np.sum(np.isnan(corr)) == row2 - - -if __name__ == '__main__': - test_correlation_computation() - test_correlation_nans() diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py deleted file mode 100644 index 98fadc4ea..000000000 --- a/tests/fcma/test_voxel_selection.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from brainiak.fcma.voxelselector import VoxelSelector -from scipy.stats.mstats import zscore -from sklearn import svm -from sklearn.linear_model import LogisticRegression -import numpy as np -import math -from mpi4py import MPI -from numpy.random import RandomState - -# specify the random state to fix the random numbers -prng = RandomState(1234567890) - - -def create_epoch(): - row = 12 - col = 5 - mat = prng.rand(row, col).astype(np.float32) - mat = zscore(mat, axis=0, ddof=0) - # if zscore fails (standard deviation is zero), - # set all values to be zero - mat = np.nan_to_num(mat) - mat = mat / math.sqrt(mat.shape[0]) - return mat - - -def test_voxel_selection(): - fake_raw_data = [create_epoch() for i in range(8)] - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - vs = VoxelSelector(labels, 4, 2, fake_raw_data, voxel_unit=1) - # test scipy normalization - fake_corr = prng.rand(1, 4, 5).astype(np.float32) - fake_corr = vs._correlation_normalization(fake_corr) - if MPI.COMM_WORLD.Get_rank() == 0: - expected_fake_corr = [[[1.06988919, 0.51641309, -0.46790636, - -1.31926763, 0.2270218], - [-1.22142744, -1.39881694, -1.2979387, - 1.05702305, -0.6525566], - [0.89795232, 1.27406132, 0.36460185, - 0.87538344, 1.5227468], - [-0.74641371, -0.39165771, 1.40124381, - -0.61313909, -1.0972116]]] - assert np.allclose(fake_corr, expected_fake_corr), \ - 'within-subject normalization does not provide correct results' - # for cross validation, use SVM with precomputed kernel - # no shrinking, set C=1 - clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [7, 4, 6, 4, 4] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - # for cross validation, use logistic regression - clf = LogisticRegression() - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [6, 3, 6, 4, 4] - assert np.allclose(output, expected_output, atol=1), ( - "voxel selection via logistic regression does not provide correct " - "results") - - -def test_voxel_selection_with_two_masks(): - fake_raw_data1 = [create_epoch() for i in range(8)] - fake_raw_data2 = [create_epoch() for i in range(8)] - labels = [0, 1, 0, 1, 0, 1, 0, 1] - # 2 subjects, 4 epochs per subject - vs = VoxelSelector(labels, 4, 2, fake_raw_data1, - raw_data2=fake_raw_data2, voxel_unit=1) - # for cross validation, use SVM with precomputed kernel - # no shrinking, set C=1 - clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [3, 3, 3, 6, 6] - assert np.allclose(output, expected_output, atol=1), \ - 'voxel selection via SVM does not provide correct results' - # for cross validation, use logistic regression - clf = LogisticRegression() - results = vs.run(clf) - if MPI.COMM_WORLD.Get_rank() == 0: - output = [None] * len(results) - for tuple in results: - output[tuple[0]] = int(8*tuple[1]) - expected_output = [3, 4, 4, 6, 6] - assert np.allclose(output, expected_output, atol=1), ( - "voxel selection via logistic regression does not provide correct " - "results") - - -if __name__ == '__main__': - test_voxel_selection() - test_voxel_selection_with_two_masks() diff --git a/tests/hyperparamopt/test_hpo.py b/tests/hyperparamopt/test_hpo.py deleted file mode 100644 index 3904458ee..000000000 --- a/tests/hyperparamopt/test_hpo.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest -import numpy as np -import scipy.stats as st -from brainiak.hyperparamopt.hpo import gmm_1d_distribution, fmin - - -def test_simple_gmm(): - x = np.array([1., 1., 2., 3., 1.]) - d = gmm_1d_distribution(x, min_limit=0., max_limit=4.) - assert d(1.1) > d(3.5), "GMM distribution not behaving correctly" - assert d(2.0) > d(3.0), "GMM distribution not behaving correctly" - assert d(-1.0) == 0, "GMM distribution out of bounds error" - assert d(9.0) == 0, "GMM distribution out of bounds error" - - samples = d.get_samples(n=25) - np.testing.assert_array_less(samples, 4.) - np.testing.assert_array_less(0., samples) - - -def test_simple_gmm_weights(): - x = np.array([1., 1., 2., 3., 1., 3.]) - d = gmm_1d_distribution(x) - - x2 = np.array([1., 2., 3.]) - w = np.array([3., 1., 2.]) - d2 = gmm_1d_distribution(x2, weights=w) - y2 = d2(np.array([1.1, 2.0])) - - assert d2(1.1) == y2[0],\ - "GMM distribution array & scalar results don't match" - assert np.abs(d(1.1) - d2(1.1)) < 1e-5,\ - "GMM distribution weights not handled correctly" - assert np.abs(d(2.0) - d2(2.0)) < 1e-5,\ - "GMM distribution weights not handled correctly" - - -def test_simple_hpo(): - - def f(args): - x = args['x'] - return x*x - - s = {'x': {'dist': st.uniform(loc=-10., scale=20), 'lo': -10., 'hi': 10.}} - trials = [] - - # Test fmin and ability to continue adding to trials - best = fmin(loss_fn=f, space=s, max_evals=40, trials=trials) - best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) - - assert len(trials) == 50, "HPO continuation trials not working" - - # Test verbose flag - best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) - - yarray = np.array([tr['loss'] for tr in trials]) - np.testing.assert_array_less(yarray, 100.) - - xarray = np.array([tr['x'] for tr in trials]) - np.testing.assert_array_less(np.abs(xarray), 10.) - - assert best['loss'] < 100., "HPO out of range" - assert np.abs(best['x']) < 10., "HPO out of range" - - # Test unknown distributions - s2 = {'x': {'dist': 'normal', 'mu': 0., 'sigma': 1.}} - trials2 = [] - with pytest.raises(ValueError) as excinfo: - fmin(loss_fn=f, space=s2, max_evals=40, trials=trials2) - assert "Unknown distribution type for variable" in str(excinfo.value) - - s3 = {'x': {'dist': st.norm(loc=0., scale=1.)}} - trials3 = [] - fmin(loss_fn=f, space=s3, max_evals=40, trials=trials3) diff --git a/tests/image/test_image.py b/tests/image/test_image.py deleted file mode 100644 index 4796bc3f1..000000000 --- a/tests/image/test_image.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Iterable, Sequence - -import numpy as np -import pytest - -from nibabel.nifti1 import Nifti1Pair -from nibabel.spatialimages import SpatialImage - -from brainiak.image import (mask_image, mask_images, MaskedMultiSubjectData, - multimask_images, SingleConditionSpec) - - -@pytest.fixture -def masked_multi_subject_data(masked_images): - return np.stack(masked_images, axis=-1) - - -class TestMaskedMultiSubjectData: - def test_from_masked_images(self, masked_images, - masked_multi_subject_data): - result = MaskedMultiSubjectData.from_masked_images(masked_images, - len(masked_images)) - assert np.array_equal(np.moveaxis(result, 1, 0), - masked_multi_subject_data) - - -@pytest.fixture -def condition_spec() -> SingleConditionSpec: - return np.array([[[1, 1, 1, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]]], - dtype=np.int8).view(SingleConditionSpec) - - -class TestUniqueLabelConditionSpec: - def test_extract_labels(self, condition_spec: SingleConditionSpec - ) -> None: - assert np.array_equal(condition_spec.extract_labels(), - np.array([0, 1])) - - -@pytest.fixture -def spatial_image() -> SpatialImage: - return Nifti1Pair(np.array([[[[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 1, 0], - [0, 1, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]]]).reshape(4, 4, 4, 1), - np.eye(4)) - - -@pytest.fixture -def mask() -> np.ndarray: - return np.array([[[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 1, 0], - [0, 1, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 1, 1, 0], - [0, 1, 1, 0], - [0, 0, 0, 0]], - [[0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0]]], dtype=np.bool) - - -@pytest.fixture -def masked_data() -> np.ndarray: - return np.array([[1, 0, 0, 1, 0, 1, 1, 0]]).reshape(8, 1) - - -@pytest.fixture -def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]: - images = [spatial_image] - image_data = spatial_image.get_data().copy() - image_data[1, 1, 1, 0] = 2 - images.append(Nifti1Pair(image_data, np.eye(4))) - return images - - -@pytest.fixture -def masks(mask: np.ndarray) -> Sequence[np.ndarray]: - masks = [mask] - mask2 = mask.copy() - mask2[0, 0, 0] = 1 - masks.append(mask2) - mask3 = mask.copy() - mask3[2, 2, 2] = 0 - masks.append(mask3) - return masks - - -@pytest.fixture -def multimasked_images(masked_data) -> Iterable[Iterable[np.ndarray]]: - masked_data_2 = np.concatenate(([[2]], masked_data[1:, :])) - return [[masked_data, np.concatenate(([[0]], masked_data)), - masked_data[:-1, :]], - [masked_data_2, np.concatenate(([[0]], masked_data_2)), - masked_data_2[:-1, :]]] - - -@pytest.fixture -def masked_images(multimasked_images) -> Iterable[np.ndarray]: - return [multimasked_image[0] for multimasked_image in multimasked_images] - - -def test_mask_image(spatial_image: SpatialImage, mask: np.ndarray, - masked_data: np.ndarray) -> None: - result = mask_image(spatial_image, mask) - assert np.array_equal(result, masked_data) - - -def test_mask_image_with_type(spatial_image: SpatialImage, mask: np.ndarray, - masked_data: np.ndarray) -> None: - masked_data_type = np.float32 - result = mask_image(spatial_image, mask, masked_data_type) - assert result.dtype == masked_data_type - assert np.allclose(result, masked_data) - - -def test_multimask_images( - images: Iterable[SpatialImage], - masks: Sequence[np.ndarray], - multimasked_images: Iterable[Iterable[np.ndarray]] - ) -> None: - result = multimask_images(images, masks) - for result_images, expected_images in zip(result, - multimasked_images): - for result_image, expected_image in zip(result_images, - expected_images): - assert np.array_equal(result_image, expected_image) - - -def test_mask_images( - images: Iterable[SpatialImage], - mask: np.ndarray, - masked_images: Iterable[np.ndarray] - ) -> None: - result = mask_images(images, mask) - for result_image, expected_image in zip(result, masked_images): - assert np.array_equal(result_image, expected_image) diff --git a/tests/io/data/epoch_labels.npy b/tests/io/data/epoch_labels.npy deleted file mode 100644 index 50b0a4304f8d755e5ba6147e7c2ebe6021326848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7ItmbEXrQU1P^+Mzz{Lmz4EO<939;$`jT{x$ diff --git a/tests/io/data/mask.nii.gz b/tests/io/data/mask.nii.gz deleted file mode 100755 index 687ab69749ac9e33b4aa23f6e3bf269c37b6d2c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmb2|=3oE==C^kYvzQzuSRY7UV(CwqYA3*PLz^Y&=2G8@+%padSBUgK5L~<>QHRC) z|C8GLMjPH7dbe+W?){o?WzD748Qp&kbN`mC348qa__r_B|F&AJds~99kSj9e&V_GzaCYpfvaTyQ~&ez z32dn14y-5dKEL;-al)M16f~)V`jdO_e|h)Y@fXje&GVN&XGhW5pmk~Y{+B;DvO5%& Vp-9kx)6iAN5cDRYH=&V@0RSu0fKUJc diff --git a/tests/io/data/subject1_bet.nii.gz b/tests/io/data/subject1_bet.nii.gz deleted file mode 100644 index f34b35c1d201cfae247bc3e4857f17eaee53a5f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7149 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 P=cU0(r*}}F+er8i>K-AX diff --git a/tests/io/test_io.py b/tests/io/test_io.py deleted file mode 100644 index 9f72550f6..000000000 --- a/tests/io/test_io.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2017 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path -from typing import Iterable, Sequence - -import nibabel as nib -import numpy as np -import pytest - -from brainiak import io - - -@pytest.fixture -def in_dir() -> Path: - return Path(__file__).parent / "data" - - -@pytest.fixture -def expected_image_data_shape() -> Sequence[int]: - return (64, 64, 26, 10) - - -@pytest.fixture -def mask_path(in_dir: Path) -> Path: - return in_dir / "mask.nii.gz" - - -@pytest.fixture -def labels_path(in_dir: Path) -> Path: - return in_dir / "epoch_labels.npy" - - -@pytest.fixture -def expected_condition_spec_shape() -> Sequence[int]: - return (2, 2, 10) - - -@pytest.fixture -def expected_n_subjects() -> int: - return 2 - - -@pytest.fixture -def image_paths(in_dir: Path) -> Iterable[Path]: - return (in_dir / "subject1_bet.nii.gz", in_dir / "subject2_bet.nii.gz") - - -def test_load_images_from_dir_data_shape( - in_dir: Path, - expected_image_data_shape: Sequence[int], - expected_n_subjects: int - ) -> None: - for i, image in enumerate(io.load_images_from_dir(in_dir, "bet.nii.gz")): - assert image.get_data().shape == (64, 64, 26, 10) - assert i + 1 == expected_n_subjects - - -def test_load_images_data_shape( - image_paths: Iterable[Path], - expected_image_data_shape: Sequence[int], - expected_n_subjects: int - ) -> None: - for i, image in enumerate(io.load_images(image_paths)): - assert image.get_data().shape == (64, 64, 26, 10) - assert i + 1 == expected_n_subjects - - -def test_load_boolean_mask(mask_path: Path) -> None: - mask = io.load_boolean_mask(mask_path) - assert mask.dtype == np.bool - - -def test_load_boolean_mask_predicate(mask_path: Path) -> None: - mask = io.load_boolean_mask(mask_path, lambda x: np.logical_not(x)) - expected_mask = np.logical_not(io.load_boolean_mask(mask_path)) - assert np.array_equal(mask, expected_mask) - - -def test_load_labels(labels_path: Path, - expected_condition_spec_shape: Sequence[int], - expected_n_subjects: int) -> None: - condition_specs = io.load_labels(labels_path) - i = 0 - for condition_spec in condition_specs: - assert condition_spec.shape == expected_condition_spec_shape - i += 1 - assert i == expected_n_subjects - - -def test_save_as_nifti_file(tmpdir) -> None: - out_file = str(tmpdir / "nifti.nii") - shape = (4, 4, 4) - io.save_as_nifti_file(np.ones(shape), np.eye(4), out_file) - assert nib.load(out_file).get_data().shape == shape diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py deleted file mode 100644 index fc4ea17c1..000000000 --- a/tests/isc/test_isc.py +++ /dev/null @@ -1,956 +0,0 @@ -import numpy as np -import logging -import pytest -from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, - squareform_isfc, timeshift_isc, - phaseshift_isc) -from scipy.spatial.distance import squareform - -logger = logging.getLogger(__name__) - - -# Create simple simulated data with high intersubject correlation -def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, - noise=1, data_type='array', - random_state=None): - prng = np.random.RandomState(random_state) - if n_voxels: - signal = prng.randn(n_TRs, n_voxels) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs, n_voxels) * noise - for subject in np.arange(n_subjects)] - elif not n_voxels: - signal = prng.randn(n_TRs) - prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) - data = [signal + prng.randn(n_TRs) * noise - for subject in np.arange(n_subjects)] - if data_type == 'array': - if n_voxels: - data = np.dstack(data) - elif not n_voxels: - data = np.column_stack(data) - return data - - -# Create 3 voxel simulated data with correlated time series -def correlated_timeseries(n_subjects, n_TRs, noise=0, - random_state=None): - prng = np.random.RandomState(random_state) - signal = prng.randn(n_TRs) - correlated = True - while correlated: - uncorrelated = np.random.randn(n_TRs, - n_subjects)[:, np.newaxis, :] - unc_max = np.amax(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - unc_mean = np.mean(squareform(np.corrcoef( - uncorrelated[:, 0, :].T), checks=False)) - if unc_max < .3 and np.abs(unc_mean) < .001: - correlated = False - data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], - 20, axis=2) - data = np.concatenate((data, uncorrelated), axis=1) - data = data + np.random.randn(n_TRs, 3, n_subjects) * noise - return data - - -# Compute ISCs using different input types -# List of subjects with one voxel/ROI -def test_isc_input(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC inputs") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with one voxel/ROI - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=None, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - # List of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='list', - random_state=random_state) - iscs_list = isc(data, pairwise=False, summary_statistic=None) - - # Array of subjects with multiple voxels/ROIs - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs_array = isc(data, pairwise=False, summary_statistic=None) - - # Check they're the same - assert np.array_equal(iscs_list, iscs_array) - - logger.info("Finished testing ISC inputs") - - -# Check pairwise and leave-one-out, and summary statistics for ISC -def test_isc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs_loo = isc(data, pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_subjects, n_voxels) - - # Just two subjects - iscs_loo = isc(data[..., :2], pairwise=False, summary_statistic=None) - assert iscs_loo.shape == (n_voxels,) - - iscs_pw = isc(data, pairwise=True, summary_statistic=None) - assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) - - # Check summary statistics - isc_mean = isc(data, pairwise=False, summary_statistic='mean') - assert isc_mean.shape == (n_voxels,) - - isc_median = isc(data, pairwise=False, summary_statistic='median') - assert isc_median.shape == (n_voxels,) - - with pytest.raises(ValueError): - isc(data, pairwise=False, summary_statistic='min') - - logger.info("Finished testing ISC options") - - -# Make sure ISC recovers correlations of 1 and less than 1 -def test_isc_output(): - - logger.info("Testing ISC outputs") - - data = correlated_timeseries(20, 60, noise=0, - random_state=42) - iscs = isc(data, pairwise=False) - assert np.allclose(iscs[:, :2], 1., rtol=1e-05) - assert np.all(iscs[:, -1] < 1.) - - iscs = isc(data, pairwise=True) - assert np.allclose(iscs[:, :2], 1., rtol=1e-05) - assert np.all(iscs[:, -1] < 1.) - - logger.info("Finished testing ISC outputs") - - -# Check for proper handling of NaNs in ISC -def test_isc_nans(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Inject NaNs into data - data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - iscs_loo = isc(data, pairwise=False, tolerate_nans=False) - assert np.sum(np.isnan(iscs_loo)) == n_subjects - - # Tolerate all NaNs, only subject with NaNs yields NaN - iscs_loo = isc(data, pairwise=False, tolerate_nans=True) - assert np.sum(np.isnan(iscs_loo)) == 1 - - # Pairwise approach shouldn't care - iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) - iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) - assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(iscs_pw_T)) == - np.sum(np.isnan(iscs_pw_F)) == - n_subjects - 1) - - # Set proportion of nans to reject (70% and 90% non-NaN) - data[0, 0, :] = np.nan - data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan - data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan - - iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) - iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) - iscs_loo_95 = isc(data, pairwise=False, tolerate_nans=.95) - iscs_loo_90 = isc(data, pairwise=False, tolerate_nans=.90) - iscs_loo_80 = isc(data, pairwise=False, tolerate_nans=.8) - iscs_loo_70 = isc(data, pairwise=False, tolerate_nans=.7) - iscs_loo_60 = isc(data, pairwise=False, tolerate_nans=.6) - - assert (np.sum(np.isnan(iscs_loo_F)) == - np.sum(np.isnan(iscs_loo_95)) == 60) - assert (np.sum(np.isnan(iscs_loo_80)) == - np.sum(np.isnan(iscs_loo_90)) == 42) - assert (np.sum(np.isnan(iscs_loo_T)) == - np.sum(np.isnan(iscs_loo_60)) == - np.sum(np.isnan(iscs_loo_70)) == 28) - assert np.array_equal(np.sum(np.isnan(iscs_loo_F), axis=0), - np.sum(np.isnan(iscs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(iscs_loo_80), axis=0), - np.sum(np.isnan(iscs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(iscs_loo_T), axis=0), - np.sum(np.isnan(iscs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(iscs_loo_T), axis=0), - np.sum(np.isnan(iscs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(iscs_loo_60), axis=0), - np.sum(np.isnan(iscs_loo_70), axis=0)))) - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Make sure voxel with NaNs across all subjects is always removed - data[0, 0, :] = np.nan - iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) - iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) - assert np.allclose(iscs_loo_T, iscs_loo_F, equal_nan=True) - assert (np.sum(np.isnan(iscs_loo_T)) == - np.sum(np.isnan(iscs_loo_F)) == - n_subjects) - - iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) - iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) - assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(iscs_pw_T)) == - np.sum(np.isnan(iscs_pw_F)) == - n_subjects * (n_subjects - 1) / 2) - - -# Test one-sample bootstrap test -def test_bootstrap_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - n_bootstraps = 10 - - logger.info("Testing bootstrap hypothesis test") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - iscs = isc(data, pairwise=False, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Test one-sample bootstrap test with pairwise approach - n_bootstraps = 10 - - iscs = isc(data, pairwise=True, summary_statistic=None) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95) - assert distribution.shape == (n_bootstraps, n_voxels) - - # Check random seeds - iscs = isc(data, pairwise=False, summary_statistic=None) - distributions = [] - for random_state in [42, 42, None]: - observed, ci, p, distribution = bootstrap_isc( - iscs, pairwise=False, - summary_statistic='median', - n_bootstraps=n_bootstraps, - ci_percentile=95, - random_state=random_state) - distributions.append(distribution) - assert np.array_equal(distributions[0], distributions[1]) - assert not np.array_equal(distributions[1], distributions[2]) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=False) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=False, - summary_statistic='median')) - - # Check that ISC computation and bootstrap observed are same - iscs = isc(data, pairwise=True) - observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, - summary_statistic='median') - assert np.array_equal(observed, isc(data, pairwise=True, - summary_statistic='median')) - - logger.info("Finished testing bootstrap hypothesis test") - - -# Test permutation test with group assignments -def test_permutation_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - group_assignment = [1] * 10 + [2] * 10 - - logger.info("Testing permutation test") - - # Create dataset with two groups in pairwise approach - data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=5, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=200) - - # Create data with two groups in leave-one-out approach - data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, - noise=10, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=200) - - # One-sample leave-one-out permutation test - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=False, - summary_statistic='median', - n_permutations=200) - - # One-sample pairwise permutation test - iscs = isc(data, pairwise=True, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, - pairwise=True, - summary_statistic='median', - n_permutations=200) - - # Small one-sample pairwise exact test - data = simulated_timeseries(12, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - iscs = isc(data, pairwise=False, summary_statistic=None) - - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median', - n_permutations=10000) - - # Small two-sample pairwise exact test (and unequal groups) - data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3), - simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4))) - iscs = isc(data, pairwise=True, summary_statistic=None) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=True, - summary_statistic='mean', - n_permutations=10000) - - # Small two-sample leave-one-out exact test (and unequal groups) - data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, - noise=1, data_type='array', - random_state=3) - data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, - noise=50, data_type='array', - random_state=4) - iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), - isc(data_2, pairwise=False, summary_statistic=None))) - group_assignment = [1, 1, 1, 2, 2, 2, 2] - - observed, p, distribution = permutation_isc( - iscs, - group_assignment=group_assignment, - pairwise=False, - summary_statistic='mean', - n_permutations=10000) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = permutation_isc(iscs, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = permutation_isc(iscs, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing permutaton test") - - -def test_timeshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing circular time-shift") - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, pairwise - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Circular time-shift on one sample, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = timeshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = timeshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing circular time-shift") - - -# Phase randomization test -def test_phaseshift_isc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing phase randomization") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='median', - n_shifts=200) - - # Phase randomization one-sample test, leave-one-out - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='mean', - n_shifts=200) - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True) - assert np.all(iscs[:, :2] > .5) - assert np.all(iscs[:, -1] < .5) - assert p[0] < .05 and p[1] < .05 - assert p[2] > .01 - - # Check that ISC computation and permutation observed are same - iscs = isc(data, pairwise=False) - observed, p, distribution = phaseshift_isc(data, pairwise=False, - summary_statistic='median') - assert np.allclose(observed, isc(data, pairwise=False, - summary_statistic='median'), - rtol=1e-03) - - # Check that ISC computation and permuation observed are same - iscs = isc(data, pairwise=True) - observed, p, distribution = phaseshift_isc(data, pairwise=True, - summary_statistic='mean') - assert np.allclose(observed, isc(data, pairwise=True, - summary_statistic='mean'), - rtol=1e-03) - - logger.info("Finished testing phase randomization") - - -# Test ISFC -def test_isfc_options(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - - logger.info("Testing ISFC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - isfcs, iscs = isfc(data, pairwise=False, summary_statistic=None) - assert isfcs.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) - assert iscs.shape == (n_subjects, n_voxels) - - # Without vectorized upper triangle - isfcs = isfc(data, pairwise=False, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_voxels) - - # Just two subjects - isfcs, iscs = isfc(data[..., :2], pairwise=False, summary_statistic=None) - assert isfcs.shape == (n_voxels * (n_voxels - 1) / 2,) - assert iscs.shape == (n_voxels,) - - isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_voxels, n_voxels) - - # ISFC with pairwise approach - isfcs, iscs = isfc(data, pairwise=True, summary_statistic=None) - assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels * (n_voxels - 1) / 2) - assert iscs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels) - - isfcs = isfc(data, pairwise=True, summary_statistic=None, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, - n_voxels, n_voxels) - - # ISFC with summary statistics - isfcs, iscs = isfc(data, pairwise=True, summary_statistic='mean') - isfcs, iscs = isfc(data, pairwise=True, summary_statistic='median') - - # Check output p-values - data = correlated_timeseries(20, 60, noise=.5, - random_state=42) - isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) - assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) - assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) - - isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) - assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) - assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) - - # Check that ISC and ISFC diagonal are identical - iscs = isc(data, pairwise=False) - isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) - isfcs, iscs_v = isfc(data, pairwise=False) - assert np.allclose(iscs, iscs_v, rtol=1e-03) - - # Check that ISC and ISFC diagonal are identical (pairwise) - iscs = isc(data, pairwise=True) - isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) - for s in np.arange(len(iscs)): - assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) - isfcs, iscs_v = isfc(data, pairwise=True) - assert np.allclose(iscs, iscs_v, rtol=1e-03) - - # Generate 'targets' data and use for ISFC - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array') - n_targets = 15 - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Ensure 'square' output enforced - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=True) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Check list input for targets - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='list') - isfcs = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - # Check that mismatching subjects / TRs breaks targets - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - - with pytest.raises(ValueError): - isfcs = isfc(data, targets=targets_data[..., :-1], - pairwise=False, vectorize_isfcs=False) - assert isfcs.shape == (n_subjects, n_voxels, n_targets) - - with pytest.raises(ValueError): - isfcs = isfc(data, targets=targets_data[:-1, ...], - pairwise=False, vectorize_isfcs=False) - - # Check targets for only 2 subjects - isfcs = isfc(data[..., :2], targets=targets_data[..., :2], - pairwise=False, summary_statistic=None) - assert isfcs.shape == (2, n_voxels, n_targets) - - isfcs = isfc(data[..., :2], targets=targets_data[..., :2], - pairwise=True, summary_statistic=None) - assert isfcs.shape == (2, n_voxels, n_targets) - - # Check that supplying targets enforces leave-one-out - isfcs_pw = isfc(data, targets=targets_data, pairwise=True, - vectorize_isfcs=False, tolerate_nans=False) - assert isfcs_pw.shape == (n_subjects, n_voxels, n_targets) - - logger.info("Finished testing ISFC options") - - -# Check for proper handling of NaNs in ISFC -def test_isfc_nans(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Inject NaNs into data - data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels * 2 - 1) - - # With vectorized ISFCs - isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels - 1) - - # Tolerate all NaNs, only subject with NaNs yields NaN - isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == n_voxels * 2 - 1 - - isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == n_voxels - 1 - - # Pairwise approach shouldn't care - isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_F)) == - (n_voxels * 2 - 1) * (n_subjects - 1)) - - isfcs_pw_T, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, - tolerate_nans=True) - isfcs_pw_F, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_F)) == - (n_voxels - 1) * (n_subjects - 1)) - - # Set proportion of nans to reject (70% and 90% non-NaN) - data[0, 0, :] = np.nan - data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan - data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan - - isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - isfcs_loo_95 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.95) - isfcs_loo_90 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.90) - isfcs_loo_80 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.8) - isfcs_loo_70 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.7) - isfcs_loo_60 = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=.6) - assert (np.sum(np.isnan(isfcs_loo_F)) == - np.sum(np.isnan(isfcs_loo_95)) == 3420) - assert (np.sum(np.isnan(isfcs_loo_80)) == - np.sum(np.isnan(isfcs_loo_90)) == 2430) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_60)) == - np.sum(np.isnan(isfcs_loo_70)) == 1632) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), - np.sum(np.isnan(isfcs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), - np.sum(np.isnan(isfcs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_60), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)))) - - isfcs_loo_T, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=True) - isfcs_loo_F, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=False) - isfcs_loo_95, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.95) - isfcs_loo_90, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.90) - isfcs_loo_80, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.8) - isfcs_loo_70, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.7) - isfcs_loo_60, _ = isfc(data, pairwise=False, vectorize_isfcs=True, - tolerate_nans=.6) - assert (np.sum(np.isnan(isfcs_loo_F)) == - np.sum(np.isnan(isfcs_loo_95)) == 1680) - assert (np.sum(np.isnan(isfcs_loo_80)) == - np.sum(np.isnan(isfcs_loo_90)) == 1194) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_60)) == - np.sum(np.isnan(isfcs_loo_70)) == 802) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), - np.sum(np.isnan(isfcs_loo_95), axis=0)) - assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), - np.sum(np.isnan(isfcs_loo_90), axis=0)) - assert np.all((np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_60), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_T), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)), - np.array_equal( - np.sum(np.isnan(isfcs_loo_60), axis=0), - np.sum(np.isnan(isfcs_loo_70), axis=0)))) - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Make sure voxel with NaNs across all subjects is always removed - data[0, 0, :] = np.nan - isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_loo_T, isfcs_loo_F, equal_nan=True) - assert (np.sum(np.isnan(isfcs_loo_T)) == - np.sum(np.isnan(isfcs_loo_F)) == - 1180) - - isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=True) - isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, - tolerate_nans=False) - assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) - - assert (np.sum(np.isnan(isfcs_pw_T)) == - np.sum(np.isnan(isfcs_pw_T)) == - 11210) - - # Check for NaN-handling in targets - n_targets = 15 - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - targets_data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_targets, - data_type='array') - - # Inject NaNs into targets_data - targets_data[0, 0, 0] = np.nan - - # Don't tolerate NaNs, should lose zeroeth voxel - isfcs_loo = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False, tolerate_nans=False) - assert np.sum(np.isnan(isfcs_loo)) == (n_subjects - 1) * (n_targets * 2) - - # Single NaN in targets will get averaged out with tolerate - isfcs_loo = isfc(data, targets=targets_data, pairwise=False, - vectorize_isfcs=False, tolerate_nans=True) - assert np.sum(np.isnan(isfcs_loo)) == 0 - - -def test_squareform_isfc(): - - # Set parameters for toy time series data - n_subjects = 20 - n_TRs = 60 - n_voxels = 30 - random_state = 42 - - logger.info("Testing ISC options") - - data = simulated_timeseries(n_subjects, n_TRs, - n_voxels=n_voxels, data_type='array', - random_state=random_state) - - # Generate square redundant ISFCs - isfcs_r = isfc(data, vectorize_isfcs=False) - assert isfcs_r.shape == (n_subjects, n_voxels, n_voxels) - - # Squareform these into condensed ISFCs and ISCs - isfcs_c, iscs_c = squareform_isfc(isfcs_r) - assert isfcs_c.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) - assert iscs_c.shape == (n_subjects, n_voxels) - - # Go back the other way and check it's the same - isfcs_new = squareform_isfc(isfcs_c, iscs_c) - assert np.array_equal(isfcs_r, isfcs_new) - - # Check against ISC function - assert np.allclose(isc(data), iscs_c, rtol=1e-03) - - # Check for two subjects - isfcs_r = isfc(data[..., :2], vectorize_isfcs=False) - assert isfcs_r.shape == (n_voxels, n_voxels) - isfcs_c, iscs_c = squareform_isfc(isfcs_r) - assert isfcs_c.shape == (n_voxels * (n_voxels - 1) / 2,) - assert iscs_c.shape == (n_voxels,) - assert np.array_equal(isfcs_r, squareform_isfc(isfcs_c, iscs_c)) - - -if __name__ == '__main__': - test_isc_input() - test_isc_options() - test_isc_output() - test_isc_nans() - test_bootstrap_isc() - test_permutation_isc() - test_timeshift_isc() - test_phaseshift_isc() - test_isfc_options() - test_isfc_nans() - test_squareform_isfc() - logger.info("Finished all ISC tests") diff --git a/tests/reprsimil/example_design.1D b/tests/reprsimil/example_design.1D deleted file mode 100755 index 7385edea6..000000000 --- a/tests/reprsimil/example_design.1D +++ /dev/null @@ -1,272 +0,0 @@ -# - 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 - 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 - 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 - 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 - 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 - 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 - 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 - 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 - 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 - 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 - 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 - 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 - 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 - 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 - 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 - 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 - 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 - 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 - 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 - 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 - 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 - 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 - 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 - 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 - 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 - 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 - 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 - 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 - 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 - 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 - 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 - 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 - 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 - 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 - 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 - 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 - 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 - 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 - 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 - 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 - 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 - 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 - 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 - 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 - 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 - 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 - 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 - 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 - 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 - 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 - 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 - 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 - 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 - 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 - 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 - 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 - 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 - 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 - 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 - 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 - 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 - 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 - 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 - 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 - 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 - 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 - 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 - 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 - 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 - 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 - 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 - 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 - 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 - 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 - 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 - 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 - 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 - 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 - 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 - 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 - 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 - 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 - 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 - 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 - 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 - 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 - 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 - 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 - 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 - 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 - 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 - 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 - 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 - 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 - 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 - 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 - 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 - 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 - 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 - 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 - 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 - 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 - 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 - 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 - 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 - 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 - 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 - 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 - 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 - 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 - 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 - 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 - 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 - 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 - 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 - 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 - 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 - 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 - 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 - 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 - 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 - 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 - 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 - 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 - 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 - 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 - 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 - 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 - 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 - 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 - 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 - 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 - 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 - 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 - 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 - 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 - 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 - 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 - 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 - 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 - 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 - 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 - 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 - 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 - 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 - 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 - 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 - 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 - 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 - 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 - 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 - 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 - 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 - 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 - 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 - 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 - 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 - 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 - 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 - 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 - 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 - 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 - 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 - 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 - 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 - 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 - 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 - 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 - 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 - 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 - 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 - 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 - 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 - 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 - 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 - 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 - 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 - 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 - 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 - 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 - 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 - 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 - 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 - 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 - 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 - 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 -# - diff --git a/tests/reprsimil/test_brsa.py b/tests/reprsimil/test_brsa.py deleted file mode 100755 index fbf898b86..000000000 --- a/tests/reprsimil/test_brsa.py +++ /dev/null @@ -1,651 +0,0 @@ -# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, -# Princeton University -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - - -def test_can_instantiate(): - import brainiak.reprsimil.brsa - s = brainiak.reprsimil.brsa.BRSA() - assert s, "Invalid BRSA instance!" - - s = brainiak.reprsimil.brsa.BRSA( - n_iter=50, rank=5, auto_nuisance=False, n_nureg=2, nureg_method='ICA', - baseline_single=False, init_iter=5, GP_space=True, GP_inten=True, - tol=2e-3, eta=0.001, space_smooth_range=10.0, inten_smooth_range=100.0, - tau_range=2.0, - tau2_prior=brainiak.reprsimil.brsa.prior_GP_var_inv_gamma, - optimizer='CG', random_state=100, anneal_speed=20) - assert s, "Invalid BRSA instance!" - - -def test_fit(): - from brainiak.reprsimil.brsa import BRSA - import brainiak.utils.utils as utils - import scipy.stats - import numpy as np - import os.path - np.random.seed(10) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 2 times, mimicking 2 runs of itenditcal timing - n_run = 2 - design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) - design.n_TR = design.n_TR * n_run - - # start simulating some data - n_V = 50 - n_C = np.size(design.design_task, axis=1) - n_T = design.n_TR - - noise_bot = 0.5 - noise_top = 5.0 - noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot - - # generating noise - noise = np.zeros([n_T, n_V]) - noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ - np.random.randn(n_V) * noise_level - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_level = 5.0 # test with high SNR - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends - # not only on beta but also on x. - inten = np.random.rand(n_V) * 20.0 - - # parameters of Gaussian process to generate pseuso SNR - tau = 1.0 - smooth_width = 5.0 - inten_kernel = 1.0 - - coords = np.arange(0, n_V)[:, None] - - dist2 = np.square(coords - coords.T) - - inten_tile = np.tile(inten, [n_V, 1]) - inten_diff2 = (inten_tile - inten_tile.T)**2 - - K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / - inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 - - L = np.linalg.cholesky(K) - snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level - sqrt_v = noise_level * snr - betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v - signal = np.dot(design.design_task, betas_simulated) - - # Adding noise to signal as data - Y = signal + noise + inten - - scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) - - # Test fitting with GP prior. - brsa = BRSA(GP_space=True, GP_inten=True, n_iter=5, - init_iter=10, auto_nuisance=False, tol=2e-3) - - # We also test that it can detect baseline regressor included in the - # design matrix for task conditions - wrong_design = np.insert(design.design_task, 0, 1, axis=1) - with pytest.raises(ValueError) as excinfo: - brsa.fit(X=Y, design=wrong_design, scan_onsets=scan_onsets, - coords=coords, inten=inten) - assert ('Your design matrix appears to have included baseline time series.' - in str(excinfo.value)) - # Now we fit with the correct design matrix. - brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets, - coords=coords, inten=inten) - - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], - u_i[np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - - noise_new = np.zeros([n_T, n_V]) - noise_new[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise_new[i_t, :] = noise_new[i_t - 1, :] * \ - rho1 + np.random.randn(n_V) * noise_level - - Y_new = signal + noise_new + inten - ts, ts0 = brsa.transform(Y_new, scan_onsets=scan_onsets) - p = scipy.stats.pearsonr(ts[:, 0], design.design_task[:, 0])[1] - assert p < 0.01, ( - "Recovered time series does not correlate with true time series!") - assert np.shape(ts) == (n_T, n_C) and np.shape(ts0) == (n_T, 1), ( - "Wrong shape in returned time series by transform function!") - - [score, score_null] = brsa.score( - X=Y_new, design=design.design_task, scan_onsets=scan_onsets) - assert score > score_null, ( - "Full model does not win over null model on data containing signal") - - [score, score_null] = brsa.score(X=noise_new + inten, - design=design.design_task, - scan_onsets=scan_onsets) - assert score < score_null, ( - "Null model does not win over full model on data without signal") - - # Test fitting with lower rank, nuisance regressors and without GP prior - rank = n_C - 1 - n_nureg = 1 - brsa = BRSA(rank=rank, n_nureg=n_nureg, tol=2e-3, - n_iter=8, init_iter=4, auto_nuisance=True) - brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets) - # u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ - np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - - assert (not hasattr(brsa, 'bGP_') - and not hasattr(brsa, 'lGPspace_') - and not hasattr(brsa, 'lGPinten_') - ), ("the BRSA object should not have parameters of GP if GP is " - "not requested.") - # GP parameters are not set if not requested - assert brsa.beta0_.shape[0] == n_nureg + 1, 'Shape of beta0 incorrect' - p = scipy.stats.pearsonr(brsa.beta0_[0, :], inten)[1] - assert p < 0.01, ( - 'recovered beta0 does not correlate with the baseline of voxels.') - assert np.shape(brsa.L_) == ( - n_C, rank), 'Cholesky factor should have shape of (n_C, rank)' - - # Test fitting with GP over just spatial coordinates. - brsa = BRSA(GP_space=True, baseline_single=False, - tol=2e-3, n_iter=4, init_iter=4) - brsa.fit(X=Y, design=design.design_task, - scan_onsets=scan_onsets, coords=coords) - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = brsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ - np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" - p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - assert not hasattr(brsa, 'lGPinten_'), ( - "the BRSA object should not have parameters of lGPinten_ if only " - "smoothness in space is requested.") - # GP parameters are not set if not requested - - -def test_gradient(): - from brainiak.reprsimil.brsa import BRSA - import brainiak.utils.utils as utils - import numpy as np - import os.path - import numdifftools as nd - - np.random.seed(100) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - n_run = 4 - # concatenate it by 4 times, mimicking 4 runs of itenditcal timing - design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) - design.n_TR = design.n_TR * n_run - - # start simulating some data - n_V = 30 - n_C = np.size(design.design_task, axis=1) - n_T = design.n_TR - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot - - # generating noise - noise = np.zeros([n_T, n_V]) - noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) - for i_t in range(1, n_T): - noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ - np.random.randn(n_V) * noise_level - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0, 0] = 0.2 - ideal_cov[5:9, 5:9] = 0.6 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_level = 5.0 # test with high SNR - inten = np.random.randn(n_V) * 20.0 - - # parameters of Gaussian process to generate pseuso SNR - tau = 0.8 - smooth_width = 5.0 - inten_kernel = 1.0 - - coords = np.arange(0, n_V)[:, None] - - dist2 = np.square(coords - coords.T) - - inten_tile = np.tile(inten, [n_V, 1]) - inten_diff2 = (inten_tile - inten_tile.T)**2 - - K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / - inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 - - L = np.linalg.cholesky(K) - snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - sqrt_v = noise_level * snr - betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v - signal = np.dot(design.design_task, betas_simulated) - - # Adding noise to signal as data - Y = signal + noise - - scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) - - # Test fitting with GP prior. - brsa = BRSA(GP_space=True, GP_inten=True, rank=n_C) - - # Additionally, we test the generation of re-used terms. - X0 = np.ones(n_T)[:, None] - D, F, run_TRs, n_run_returned = brsa._prepare_DF( - n_T, scan_onsets=scan_onsets) - assert np.shape(D) == (n_T, n_T), 'D has wrong shape' - assert np.shape(F) == (n_T, n_T), 'F has wrong shape' - assert np.sum(D) == (n_T - n_run) * 2, 'D is initialized incorrectly.' - assert np.sum(F) == n_T - n_run * 2, 'F is initialized incorrectly.' - assert n_run_returned == n_run, ( - 'There is mistake in counting number of runs') - assert np.sum(run_TRs) == n_T, ( - 'The segmentation of the total experiment duration is wrong') - (XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX - ) = brsa._prepare_data_XY(design.design_task, Y, D, F) - (X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, X_base, - n_X0, idx_DC - ) = brsa._prepare_data_XYX0(design.design_task, Y, X0, - np.random.randn(n_T)[:, None], D, F, run_TRs, - no_DC=False) - assert (np.shape(XTY) == (n_C, n_V) - and np.shape(XTDY) == (n_C, n_V) - and np.shape(XTFY) == (n_C, n_V) - ), 'Dimension of XTY etc. returned from _prepare_data is wrong' - assert (np.ndim(YTY_diag) == 1 - and np.ndim(YTDY_diag) == 1 - and np.ndim(YTFY_diag) == 1 - ), ("Dimension of YTY_diag etc. returned from _prepare_data is " - "wrong") - assert (np.ndim(XTX) == 2 - and np.ndim(XTDX) == 2 - and np.ndim(XTFX) == 2 - ), 'Dimension of XTX etc. returned from _prepare_data is wrong' - assert (np.ndim(X0TX0) == 2 - and np.ndim(X0TDX0) == 2 - and np.ndim(X0TFX0) == 2 - ), 'Dimension of X0TX0 etc. returned from _prepare_data is wrong' - assert (np.ndim(XTX0) == 2 - and np.ndim(XTDX0) == 2 - and np.ndim(XTFX0) == 2 - ), 'Dimension of XTX0 etc. returned from _prepare_data is wrong' - assert (np.ndim(X0TY) == 2 - and np.ndim(X0TDY) == 2 - and np.ndim(X0TFY) == 2 - ), 'Dimension of X0TY etc. returned from _prepare_data is wrong' - assert (np.shape(X0) == (n_T, n_X0) - and np.shape(X_base) == (n_T, np.size(idx_DC)) - and np.max(idx_DC) < n_X0 - and np.size(idx_DC) + 1 == n_X0 - ), ("Dimension of X0 or X_base, or n_X0 or indices of DC " - "components are wrong.") - l_idx = np.tril_indices(n_C) - n_l = np.size(l_idx[0]) - - # Make sure all the fields are in the indices. - idx_param_sing, idx_param_fitU, idx_param_fitV = brsa._build_index_param( - n_l, n_V, 2) - assert 'Cholesky' in idx_param_sing and 'a1' in idx_param_sing, \ - 'The dictionary for parameter indexing misses some keys' - assert 'Cholesky' in idx_param_fitU and 'a1' in idx_param_fitU, \ - 'The dictionary for parameter indexing misses some keys' - assert 'log_SNR2' in idx_param_fitV and 'c_space' in idx_param_fitV \ - and 'c_inten' in idx_param_fitV and 'c_both' in idx_param_fitV, \ - 'The dictionary for parameter indexing misses some keys' - - # Initial parameters are correct parameters with some perturbation - param0_fitU = np.random.randn(n_l + n_V) * 0.1 - param0_fitV = np.random.randn(n_V + 1) * 0.1 - param0_sing = np.random.randn(n_l + 1) * 0.1 - param0_sing[idx_param_sing['a1']] += np.mean(np.tan(rho1 * np.pi / 2)) - param0_fitV[idx_param_fitV['log_SNR2']] += np.log(snr[:n_V - 1]) * 2 - param0_fitV[idx_param_fitV['c_space']] += np.log(smooth_width) * 2 - param0_fitV[idx_param_fitV['c_inten']] += np.log(inten_kernel) * 2 - - # test if the gradients are correct - # log likelihood and derivative of the _singpara function - - ll0, deriv0 = brsa._loglike_AR1_singpara(param0_sing, XTX, XTDX, XTFX, - YTY_diag, YTDY_diag, YTFY_diag, - XTY, XTDY, XTFY, X0TX0, X0TDX0, - X0TFX0, XTX0, XTDX0, XTFX0, X0TY, - X0TDY, X0TFY, l_idx, n_C, n_T, - n_V, n_run, n_X0, idx_param_sing) - # We test the gradient to the Cholesky factor - vec = np.zeros(np.size(param0_sing)) - vec[idx_param_sing['Cholesky'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, - XTDX0, XTFX0, X0TY, X0TDY, X0TFY, - l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_sing)[0], - param0_sing, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of singpara wrt Cholesky is incorrect') - - # We test the gradient to a1 - vec = np.zeros(np.size(param0_sing)) - vec[idx_param_sing['a1']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, - XTDX0, XTFX0, X0TY, X0TDY, X0TFY, - l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_sing)[0], - param0_sing, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), - rtol=1e-5), 'gradient of singpara wrt a1 is incorrect' - - # log likelihood and derivative of the fitU function. - ll0, deriv0 = brsa._loglike_AR1_diagV_fitU(param0_fitU, XTX, XTDX, XTFX, - YTY_diag, YTDY_diag, YTFY_diag, - XTY, XTDY, XTFY, X0TX0, X0TDX0, - X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, np.log(snr) - * 2, l_idx, n_C, n_T, n_V, - n_run, n_X0, idx_param_fitU, - n_C) - - # We test the gradient wrt the reparametrization of AR(1) coefficient of - # noise. - vec = np.zeros(np.size(param0_fitU)) - vec[idx_param_fitU['a1'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitU wrt to AR(1) coefficient incorrect') - - # We test if the numerical and analytical gradient wrt to the first - # element of Cholesky factor is correct - vec = np.zeros(np.size(param0_fitU)) - vec[idx_param_fitU['Cholesky'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitU wrt Cholesky factor incorrect') - - # Test on a random direction - vec = np.random.randn(np.size(param0_fitU)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, - YTDY_diag, YTFY_diag, XTY, XTDY, - XTFY, X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, X0TY, X0TDY, - X0TFY, np.log(snr) * 2, l_idx, - n_C, n_T, n_V, n_run, n_X0, - idx_param_fitU, n_C)[0], - param0_fitU, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), - rtol=1e-5), 'gradient of fitU incorrect' - - # We test the gradient of _fitV wrt to log(SNR^2) assuming no GP prior. - X0TAX0, XTAX0, X0TAY, X0TAX0_i, \ - XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL = \ - brsa._precompute_ar1_quad_forms(XTY, XTDY, XTFY, - YTY_diag, YTDY_diag, YTFY_diag, - XTX, XTDX, XTFX, - X0TX0, X0TDX0, X0TFX0, - XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, - L_full, rho1, n_V, n_X0) - assert np.shape(XTAcorrX) == (n_V, n_C, n_C), ( - 'Dimension of XTAcorrX is wrong by _precompute_ar1_quad_forms()') - assert XTAcorrY.shape == XTY.shape, ( - 'Shape of XTAcorrY is wrong by _precompute_ar1_quad_forms()') - assert YTAcorrY.shape == YTY_diag.shape, ( - 'Shape of YTAcorrY is wrong by _precompute_ar1_quad_forms()') - assert np.shape(X0TAX0) == (n_V, n_X0, n_X0), ( - 'Dimension of X0TAX0 is wrong by _precompute_ar1_quad_forms()') - assert np.shape(XTAX0) == (n_V, n_C, n_X0), ( - 'Dimension of XTAX0 is wrong by _precompute_ar1_quad_forms()') - assert X0TAY.shape == X0TY.shape, ( - 'Shape of X0TAX0 is wrong by _precompute_ar1_quad_forms()') - assert np.all(np.isfinite(X0TAX0_i)), ( - 'Inverse of X0TAX0 includes NaN or Inf') - ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( - param0_fitV[idx_param_fitV['log_SNR2']], X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, - n_V, n_run, n_X0, idx_param_fitV, n_C, False, False) - vec = np.zeros(np.size(param0_fitV[idx_param_fitV['log_SNR2']])) - vec[idx_param_fitV['log_SNR2'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, - False, False)[0], - param0_fitV[idx_param_fitV['log_SNR2']], - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt log(SNR2) incorrect for model without GP') - - # We test the gradient of _fitV wrt to log(SNR^2) assuming GP prior. - ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( - param0_fitV, X0TAX0, XTAX0, X0TAY, X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, n_V, n_run, n_X0, - idx_param_fitV, n_C, True, True, dist2, inten_diff2, 100, 100) - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['log_SNR2'][0]] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV srt log(SNR2) incorrect for model with GP') - - # We test the graident wrt spatial length scale parameter of GP prior - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['c_space']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt spatial length scale of GP incorrect') - - # We test the graident wrt intensity length scale parameter of GP prior - vec = np.zeros(np.size(param0_fitV)) - vec[idx_param_fitV['c_inten']] = 1 - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV wrt intensity length scale of GP incorrect') - - # We test the graident on a random direction - vec = np.random.randn(np.size(param0_fitV)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, - X0TAX0_i, XTAcorrX, XTAcorrY, - YTAcorrY, LTXTAcorrY, XTAcorrXL, - LTXTAcorrXL, L_full[l_idx], - np.tan(rho1 * np.pi / 2), - l_idx, n_C, n_T, n_V, n_run, - n_X0, idx_param_fitV, n_C, True, - True, dist2, inten_diff2, 100, - 100)[0], - param0_fitV, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( - 'gradient of fitV incorrect') - - -def test_nureg_determine(): - from brainiak.reprsimil.brsa import Ncomp_SVHT_MG_DLD_approx - import numpy as np - x = np.dot(np.random.randn(100, 5), np.random.randn(5, 40)) + \ - np.random.randn(100, 40) * 0.01 - ncomp = Ncomp_SVHT_MG_DLD_approx(x) - assert ncomp >= 3 and ncomp <= 8, ( - 'recovered number of components should be in a reasonable range') - - -def test_half_log_det(): - import numpy as np - from brainiak.reprsimil.brsa import BRSA - a = np.asarray([[1, 0.2], [0.2, 1]]) - brsa = BRSA() - half_log_det = np.log(np.linalg.det(a)) / 2 - assert np.isclose(half_log_det, brsa._half_log_det( - a)), 'half log determinant function is wrong' - - -def test_n_nureg(): - import brainiak.reprsimil.brsa - import numpy as np - # noise = np.random.randn(100,30) - noise = np.dot(np.random.randn(100, 8), np.random.randn( - 8, 30)) + np.random.randn(100, 30) * 0.01 - design = np.random.randn(100, 2) - s = brainiak.reprsimil.brsa.BRSA(n_iter=2) - s.fit(X=noise, design=design) - assert s.n_nureg_ > 2 and s.n_nureg_ < 16, ( - 'n_nureg_ estimation is wrong in BRSA') diff --git a/tests/reprsimil/test_gbrsa.py b/tests/reprsimil/test_gbrsa.py deleted file mode 100644 index 7038d35b4..000000000 --- a/tests/reprsimil/test_gbrsa.py +++ /dev/null @@ -1,630 +0,0 @@ -# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, -# Princeton University -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def test_can_instantiate(): - import brainiak.reprsimil.brsa - s = brainiak.reprsimil.brsa.GBRSA() - assert s, "Invalid GBRSA instance!" - - s = brainiak.reprsimil.brsa.GBRSA(n_iter=40, rank=4, auto_nuisance=False, - nureg_method='PCA', - baseline_single=False, logS_range=1.0, - SNR_bins=11, rho_bins=40, tol=2e-3, - optimizer='CG', random_state=0, - anneal_speed=20, SNR_prior='unif') - assert s, "Invalid GBRSA instance!" - - -def test_fit(): - from brainiak.reprsimil.brsa import GBRSA - import brainiak.utils.utils as utils - import scipy.stats - import numpy as np - import os.path - np.random.seed(10) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 1, 2, and 3 times, mimicking different length - # of experiments for different participants - n_run = [2, 1, 1] - design_mat = [None] * 3 - n_T = [None] * 3 - n_V = [40, 60, 60] - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - - # start simulating some data - n_C = np.size(design_mat[0], axis=1) - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = [None] * 3 - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = [None] * 3 - - # generating noise - noise = [None] * 3 - - # baseline - inten = [None] * 3 - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - n_T[i] = n_run[i] * design.n_TR - noise_level[i] = np.random.rand( - n_V[i]) * (noise_top - noise_bot) + noise_bot - # noise level is random. - rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot - - noise[i] = np.zeros([n_T[i], n_V[i]]) - noise[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ - np.random.randn(n_V[i]) * noise_level[i] - noise[i] = noise[i] + \ - np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) - inten[i] = np.random.rand(n_V[i]) * 20.0 - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_top = 5.0 # test with high SNR - snr_bot = 1.0 - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - - snr = [None] * 3 - signal = [None] * 3 - betas_simulated = [None] * 3 - scan_onsets = [None] * 3 - Y = [None] * 3 - for i in range(3): - snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot - sqrt_v = noise_level[i] * snr[i] - betas_simulated[i] = np.dot( - L_full, np.random.randn(n_C, n_V[i])) * sqrt_v - signal[i] = np.dot(design_mat[i], betas_simulated[i]) - - # Adding noise to signal as data - Y[i] = signal[i] + noise[i] + inten[i] - - scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) - - # Test fitting. - n_nureg = 2 - gbrsa = GBRSA(n_iter=15, auto_nuisance=True, logS_range=0.5, SNR_bins=11, - rho_bins=16, n_nureg=n_nureg, optimizer='L-BFGS-B') - - gbrsa.fit(X=Y, design=design_mat, scan_onsets=scan_onsets) - - # Check that result is significantly correlated with the ideal covariance - # matrix - u_b = gbrsa.U_ - u_i = ideal_cov - p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], - u_i[np.tril_indices_from(u_i)])[1] - assert p < 0.01, ( - "Fitted covariance matrix does not correlate with ideal covariance " - "matrix!") - # check that the recovered SNRs makes sense - p = scipy.stats.pearsonr(gbrsa.nSNR_[0], snr[0])[1] - assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" - p = scipy.stats.pearsonr(gbrsa.sigma_[1], noise_level[1])[1] - assert p < 0.01, ( - "Fitted noise level does not correlate with simulated noise level!") - p = scipy.stats.pearsonr(gbrsa.rho_[2], rho1[2])[1] - assert p < 0.01, ( - "Fitted AR(1) coefficient does not correlate with simulated values!") - assert np.shape(gbrsa.X0_[1]) == (n_T[1], n_nureg + 1), "Wrong size of X0" - - Y_new = [None] * 3 - noise_new = [None] * 3 - for i in range(3): - noise_new[i] = np.zeros([n_T[i], n_V[i]]) - noise_new[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise_new[i][i_t, :] = noise_new[i][i_t - 1, :] * \ - rho1[i] + np.random.randn(n_V[i]) * noise_level[i] - - Y_new[i] = signal[i] + noise_new[i] + inten[i] - ts, ts0 = gbrsa.transform(Y_new, scan_onsets=scan_onsets) - [score, score_null] = gbrsa.score( - X=Y_new, design=design_mat, scan_onsets=scan_onsets) - [score_noise, score_null_noise] = gbrsa.score( - X=noise_new, design=design_mat, scan_onsets=scan_onsets) - for i in range(3): - assert np.shape(ts[i]) == (n_T[i], n_C) and np.shape( - ts0[i]) == (n_T[i], n_nureg + 1) - p = scipy.stats.pearsonr(ts[i][:, 0], design_mat[i][:, 0])[1] - assert p < 0.01, ( - "Recovered time series does not correlate with true time series!") - - assert score[i] > score_null[i], ( - "Full model does not win over null model on data containing " - "signal") - - assert score_noise[i] < score_null_noise[i], ( - "Null model does not win over full model on data without signal") - - [score, score_null] = gbrsa.score( - X=[None] * 3, design=design_mat, scan_onsets=scan_onsets) - assert score == [None] * 3 and score_null == [None] * \ - 3, "score did not return list of None when data is None" - ts, ts0 = gbrsa.transform(X=[None] * 3, scan_onsets=scan_onsets) - assert ts == [None] * 3 and ts0 == [None] * \ - 3, "transform did not return list of None when data is None" - - -def test_gradient(): - from brainiak.reprsimil.brsa import GBRSA - import brainiak.utils.utils as utils - import numpy as np - import os.path - import numdifftools as nd - - np.random.seed(100) - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - # Load an example design matrix - design = utils.ReadDesign(fname=file_path) - - # concatenate it by 1, 2, and 3 times, mimicking different length - # of experiments for different participants - n_run = [1, 2, 1] - design_mat = [None] * 3 - n_T = [None] * 3 - n_V = [30, 30, 20] - for i in range(3): - design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) - n_T[i] = n_run[i] * design.n_TR - - # start simulating some data - n_C = np.size(design_mat[0], axis=1) - - noise_bot = 0.5 - noise_top = 1.5 - noise_level = [None] * 3 - for i in range(3): - noise_level[i] = np.random.rand( - n_V[i]) * (noise_top - noise_bot) + noise_bot - # noise level is random. - - # AR(1) coefficient - rho1_top = 0.8 - rho1_bot = -0.2 - rho1 = [None] * 3 - - # generating noise - noise = [None] * 3 - - # baseline - inten = [None] * 3 - for i in range(3): - rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot - noise[i] = np.zeros([n_T[i], n_V[i]]) - noise[i][0, :] = np.random.randn( - n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) - for i_t in range(1, n_T[i]): - noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ - np.random.randn(n_V[i]) * noise_level[i] - noise[i] = noise[i] + \ - np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) - inten[i] = np.random.rand(n_V[i]) * 20.0 - - # ideal covariance matrix - ideal_cov = np.zeros([n_C, n_C]) - ideal_cov = np.eye(n_C) * 0.6 - ideal_cov[0:4, 0:4] = 0.2 - for cond in range(0, 4): - ideal_cov[cond, cond] = 2 - ideal_cov[5:9, 5:9] = 0.9 - for cond in range(5, 9): - ideal_cov[cond, cond] = 1 - L_full = np.linalg.cholesky(ideal_cov) - - # generating signal - snr_top = 5.0 # test with high SNR - snr_bot = 1.0 - # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot - # Notice that accurately speaking this is not snr. the magnitude of signal - # depends not only on beta but also on x. - - snr = [None] * 3 - signal = [None] * 3 - betas_simulated = [None] * 3 - scan_onsets = [None] * 3 - Y = [None] * 3 - for i in range(3): - snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot - sqrt_v = noise_level[i] * snr[i] - betas_simulated[i] = np.dot( - L_full, np.random.randn(n_C, n_V[i])) * sqrt_v - signal[i] = np.dot(design_mat[i], betas_simulated[i]) - - # Adding noise to signal as data - Y[i] = signal[i] + noise[i] + inten[i] - - scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) - - # Get some initial fitting. - SNR_bins = 11 - rho_bins = 20 - gbrsa = GBRSA(n_iter=3, rank=n_C, SNR_bins=SNR_bins, - rho_bins=rho_bins, logS_range=0.5) - - n_grid = SNR_bins * rho_bins - half_log_det_X0TAX0 = [np.random.randn(n_grid) for i in range(3)] - log_weights = np.random.randn(n_grid) - log_fixed_terms = [np.random.randn(n_grid) for i in range(3)] - l_idx = np.tril_indices(n_C) - L_vec = np.random.randn(int(n_C * (n_C + 1) / 2)) - n_X0 = [2, 2, 2] - s = np.linspace(1, SNR_bins, n_grid) - a = np.linspace(0.5, 1, n_grid) - s2XTAcorrX = [None] * 3 - YTAcorrY_diag = [None] * 3 - sXTAcorrY = [None] * 3 - # The calculations below are quite arbitrary and do not conform - # to the model. They simply conform to the symmetry property and shape of - # the matrix indicated by the model - for i in range(3): - YTAcorrY_diag[i] = np.sum(Y[i] * Y[i], axis=0) * a[:, None] - s2XTAcorrX[i] = np.dot(design_mat[i].T, design_mat[ - i]) * s[:, None, None]**2 * a[:, None, None] - sXTAcorrY[i] = np.dot(design_mat[i].T, Y[i]) * \ - s[:, None, None] * a[:, None, None] - - # test if the gradients are correct - print(log_fixed_terms) - ll0, deriv0 = gbrsa._sum_loglike_marginalized(L_vec, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, - log_weights, log_fixed_terms, - l_idx, n_C, n_T, n_V, n_X0, - n_grid, rank=None) - # We test the gradient to the Cholesky factor - vec = np.random.randn(np.size(L_vec)) - vec = vec / np.linalg.norm(vec) - dd = nd.directionaldiff( - lambda x: gbrsa._sum_loglike_marginalized(x, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, - half_log_det_X0TAX0, - log_weights, log_fixed_terms, - l_idx, n_C, n_T, n_V, n_X0, - n_grid, rank=None)[0], - L_vec, - vec) - assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), 'gradient incorrect' - - -def test_SNR_grids(): - import brainiak.reprsimil.brsa - import numpy as np - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='unif', SNR_bins=10) - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.isclose(np.sum(SNR_weights), 1) - and np.isclose(np.std(SNR_weights[1:-1]), 0) - and np.all(SNR_weights > 0) - and np.isclose(np.min(SNR_grids), 0) - and np.all(SNR_grids >= 0) - and np.isclose(np.max(SNR_grids), 1) - ), 'SNR_weights or SNR_grids are incorrect for uniform prior' - assert np.isclose(np.ptp(np.diff(SNR_grids[1:-1])), 0), \ - 'SNR grids are not equally spaced for uniform prior' - assert (np.size(SNR_grids) == np.size(SNR_weights) - and np.size(SNR_grids) == 10 - ), ("size of SNR_grids or SNR_weights is not correct for uniform " - "prior") - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='lognorm', SNR_bins=35) - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids >= 0) - and np.isclose(np.sum(SNR_weights), 1) - and np.all(SNR_weights > 0) - and np.all(np.diff(SNR_grids) > 0) - ), 'SNR_grids or SNR_weights not correct for log normal prior' - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='exp') - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids >= 0) - and np.isclose(np.sum(SNR_weights), 1) - and np.all(SNR_weights > 0) - and np.all(np.diff(SNR_grids) > 0) - ), 'SNR_grids or SNR_weights not correct for exponential prior' - - s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='equal') - SNR_grids, SNR_weights = s._set_SNR_grids() - assert (np.all(SNR_grids == 1) - and np.all(SNR_weights == 1) - and np.size(SNR_grids) == 1 - ), 'SNR_grids or SNR_weights not correct for equal prior' - - -def test_n_nureg(): - import brainiak.reprsimil.brsa - import numpy as np - noise = np.dot(np.random.randn(100, 8), np.random.randn( - 8, 30)) + np.random.randn(100, 30) * 0.001 - design = np.random.randn(100, 2) - s = brainiak.reprsimil.brsa.GBRSA(n_iter=2) - s.fit(X=noise, design=design) - assert s.n_nureg_[0] == 8, 'n_nureg_ estimation is wrong in GBRSA' - - -def test_grid_flatten_num_int(): - # Check for numeric integration of SNR, and correctly flattening 2-D grids - # to 1-D grid. - import brainiak.reprsimil.brsa - import brainiak.utils.utils as utils - import numpy as np - import scipy.special - n_V = 30 - n_T = 50 - n_C = 3 - design = np.random.randn(n_T, n_C) - U_simu = np.asarray([[1.0, 0.1, 0.0], [0.1, 1.0, 0.2], [0.0, 0.2, 1.0]]) - L_simu = np.linalg.cholesky(U_simu) - SNR = np.random.exponential(size=n_V) - beta = np.dot(L_simu, np.random.randn(n_C, n_V)) * SNR - noise = np.random.randn(n_T, n_V) - Y = np.dot(design, beta) + noise - X = design - X_base = None - scan_onsets = [0] - - s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, - SNR_prior='exp') - s.fit(X=[Y], design=[design]) - rank = n_C - l_idx, rank = s._chol_idx(n_C, rank) - L = np.zeros((n_C, rank)) - n_l = np.size(l_idx[0]) - current_vec_U_chlsk_l = s.random_state_.randn(n_l) * 10 - L[l_idx] = current_vec_U_chlsk_l - - # Now we change the grids for SNR and rho for testing. - s.SNR_bins = 2 - s.rho_bins = 2 - SNR_grids, SNR_weights = s._set_SNR_grids() - # rho_grids, rho_weights = s._set_rho_grids() - rho_grids = np.ones(2) * 0.1 - rho_weights = np.ones(2) / 2 - # We purposefully set all rhos to be equal to test flattening of - # grids. - n_grid = s.SNR_bins * s.rho_bins - - D, F, run_TRs, n_run = s._prepare_DF( - n_T, scan_onsets=scan_onsets) - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX \ - = s._prepare_data_XY(X, Y, D, F) - X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, \ - X_base, n_X0, idx_DC = s._prepare_data_XYX0( - X, Y, X_base, None, D, F, run_TRs, no_DC=False) - - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] - and half_log_det_X0TAX0[2] == half_log_det_X0TAX0[3] - and half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] - ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' - assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) - and np.array_equal(X0TAX0[2, :, :], X0TAX0[3, :, :]) - and np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0' - assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) - and np.array_equal(X0TAX0_i[2, :, :], X0TAX0_i[3, :, :]) - and np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0_i' - assert np.allclose( - np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), - np.eye(n_X0) - ), 'X0TAX0_i is not inverse of X0TAX0' - assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[1, :]) - and np.array_equal(YTAcorrY_diag[2, :], YTAcorrY_diag[3, :]) - and np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) - ), '_matrix_flattened_grid has mistake YTAcorrY_diag' - assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) - and np.array_equal(sXTAcorrY[2, :, :], sXTAcorrY[3, :, :]) - and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) - ), '_matrix_flattened_grid has mistake sXTAcorrY' - assert (np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) - and np.array_equal(X0TAY[2, :, :], X0TAY[3, :, :]) - and np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) - ), '_matrix_flattened_grid has mistake X0TAY' - assert (np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) - and np.array_equal(XTAX0[2, :, :], XTAX0[3, :, :]) - and np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) - ), '_matrix_flattened_grid has mistake XTAX0' - - # Now we test the other way - rho_grids, rho_weights = s._set_rho_grids() - # rho_grids, rho_weights = s._set_rho_grids() - SNR_grids = np.ones(2) * 0.1 - SNR_weights = np.ones(2) / 2 - # We purposefully set all SNR to be equal to test flattening of - # grids. - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] - and half_log_det_X0TAX0[1] == half_log_det_X0TAX0[3] - and not half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] - ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' - assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) - and np.array_equal(X0TAX0[1, :, :], X0TAX0[3, :, :]) - and not np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0' - assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) - and np.array_equal(X0TAX0_i[1, :, :], X0TAX0_i[3, :, :]) - and not np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAX0_i' - assert np.allclose( - np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), - np.eye(n_X0) - ), 'X0TAX0_i is not inverse of X0TAX0' - assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) - and np.array_equal(YTAcorrY_diag[1, :], YTAcorrY_diag[3, :]) - and not np.array_equal(YTAcorrY_diag[0, :], - YTAcorrY_diag[1, :]) - ), '_matrix_flattened_grid has mistake YTAcorrY_diag' - assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) - and np.array_equal(sXTAcorrY[1, :, :], sXTAcorrY[3, :, :]) - and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) - ), '_matrix_flattened_grid has mistake sXTAcorrY' - assert (np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) - and np.array_equal(X0TAY[1, :, :], X0TAY[3, :, :]) - and not np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) - ), '_matrix_flattened_grid has mistake X0TAY' - assert (np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) - and np.array_equal(XTAX0[1, :, :], XTAX0[3, :, :]) - and not np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) - ), '_matrix_flattened_grid has mistake XTAX0' - - # Now test the integration over SNR - s.SNR_bins = 50 - s.rho_bins = 1 - SNR_grids, SNR_weights = s._set_SNR_grids() - rho_grids, rho_weights = s._set_rho_grids() - n_grid = s.SNR_bins * s.rho_bins - - def setup_for_test(): - # This function will be re-used to set up the variables necessary for - # testing. - - X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ - = s._precompute_ar1_quad_forms_marginalized( - XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, - XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, - X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) - - half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ - sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( - X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, - X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) - - log_weights = np.reshape( - np.log(SNR_weights[:, None]) + np.log(rho_weights), n_grid) - all_rho_grids = np.reshape(np.repeat( - rho_grids[None, :], s.SNR_bins, axis=0), n_grid) - log_fixed_terms = - (n_T - n_X0) / 2 * np.log(2 * np.pi) + n_run \ - / 2 * np.log(1 - all_rho_grids**2) + scipy.special.gammaln( - (n_T - n_X0 - 2) / 2) + (n_T - n_X0 - 2) / 2 * np.log(2) - return s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, \ - log_weights, log_fixed_terms - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, log_weights, - log_fixed_terms, l_idx, n_C, n_T, - n_V, n_X0, n_grid, rank=rank) - LL_total = - LL_total - # Now we re-calculate using scipy.integrate - s.SNR_bins = 100 - SNR_grids = np.linspace(0, 12, s.SNR_bins) - SNR_weights = np.exp(- SNR_grids) - SNR_weights = SNR_weights / np.sum(SNR_weights) - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, half_log_det_X0TAX0, - log_weights, log_fixed_terms, - n_C, n_T, n_V, n_X0, - n_grid, rank) - result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) - scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) - LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) - - tol = 1e-3 - assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ - 'Error of log likelihood calculation exceeds the tolerance' - - # Now test the log normal prior - s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, - SNR_prior='lognorm') - s.SNR_bins = 50 - s.rho_bins = 1 - SNR_grids, SNR_weights = s._set_SNR_grids() - rho_grids, rho_weights = s._set_rho_grids() - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, - YTAcorrY_diag, sXTAcorrY, - half_log_det_X0TAX0, log_weights, - log_fixed_terms, l_idx, n_C, n_T, - n_V, n_X0, n_grid, rank=rank) - LL_total = - LL_total - # Now we re-calculate using scipy.integrate - s.SNR_bins = 400 - SNR_grids = np.linspace(1e-8, 20, s.SNR_bins) - log_SNR_weights = scipy.stats.lognorm.logpdf(SNR_grids, s=s.logS_range) - result_sum, max_value, result_exp = utils.sumexp_stable( - log_SNR_weights[:, None]) - SNR_weights = np.squeeze(result_exp / result_sum) - n_grid = s.SNR_bins * s.rho_bins - - (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, - log_fixed_terms) = setup_for_test() - LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, - sXTAcorrY, half_log_det_X0TAX0, - log_weights, log_fixed_terms, - n_C, n_T, n_V, n_X0, - n_grid, rank) - result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) - scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) - LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) - - tol = 1e-3 - assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ - 'Error of log likelihood calculation exceeds the tolerance' diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py deleted file mode 100644 index 7d472e6f5..000000000 --- a/tests/searchlight/test_searchlight.py +++ /dev/null @@ -1,293 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections import namedtuple - -import numpy as np -from mpi4py import MPI - -from brainiak.searchlight.searchlight import Searchlight -from brainiak.searchlight.searchlight import Diamond, Ball - -"""Distributed Searchlight Test -""" - - -def cube_sfn(l, msk, myrad, bcast_var): - if np.all(msk) and np.any(msk): - return 1.0 - return None - - -def test_searchlight_with_cube(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = True - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(cube_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def test_searchlight_with_cube_poolsize_1(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = True - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(cube_sfn, pool_size=1) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def diamond_sfn(l, msk, myrad, bcast_var): - assert not np.any(msk[~Diamond(3).mask_]) - if np.all(msk[Diamond(3).mask_]): - return 1.0 - return None - - -def test_searchlight_with_diamond(): - sl = Searchlight(sl_rad=3, shape=Diamond) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = Diamond(3).mask_ - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(diamond_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -def ball_sfn(l, msk, myrad, bcast_var): - x, y, z = np.mgrid[-myrad:myrad+1, -myrad:myrad+1, -myrad:myrad+1] - correct_mask = np.square(x) + np.square(y) + np.square(z) <= myrad ** 2 - assert not np.any(msk[~Ball(3).mask_]) - if np.all(correct_mask == msk): - return 1.0 - return None - - -def test_searchlight_with_ball(): - sl = Searchlight(sl_rad=3, shape=Ball) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50, 50, 50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17, 10:17, 10:17] = Ball(3).mask_ - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(ball_sfn) - - if rank == 0: - assert global_outputs[13, 13, 13] == 1.0 - global_outputs[13, 13, 13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i, j, k] is None - - -MaskRadBcast = namedtuple("MaskRadBcast", "mask rad") - - -def test_instantiate(): - sl = Searchlight(sl_rad=5, max_blk_edge=10) - assert sl - - -def voxel_test_sfn(l, msk, myrad, bcast): - rad = bcast.rad - # Check each point - for subj in l: - for _tr in range(subj.shape[3]): - tr = subj[:, :, :, _tr] - midpt = tr[rad, rad, rad] - for d0 in range(tr.shape[0]): - for d1 in range(tr.shape[1]): - for d2 in range(tr.shape[2]): - assert np.array_equal(tr[d0, d1, d2] - midpt, - np.array([d0-rad, d1-rad, - d2-rad, 0])) - - # Determine midpoint - midpt = l[0][rad, rad, rad, 0] - midpt = (midpt[0], midpt[1], midpt[2]) - - for d0 in range(msk.shape[0]): - for d1 in range(msk.shape[1]): - for d2 in range(msk.shape[2]): - pt = (midpt[0] - rad + d0, midpt[1] - rad + d1, - midpt[2] - rad + d2) - assert bcast.mask[pt] == msk[d0, d1, d2] - - # Return midpoint - return midpt - - -def block_test_sfn(l, msk, myrad, bcast_var, extra_params): - outmat = l[0][:, :, :, 0] - outmat[~msk] = None - return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] - - -def test_correctness(): # noqa: C901 - def voxel_test(data, mask, max_blk_edge, rad): - - comm = MPI.COMM_WORLD - rank = comm.rank - - (dim0, dim1, dim2) = mask.shape - - # Initialize dataset with known pattern - for subj in data: - if subj is not None: - for tr in range(subj.shape[3]): - for d1 in range(dim0): - for d2 in range(dim1): - for d3 in range(dim2): - subj[d1, d2, d3, tr] = np.array( - [d1, d2, d3, tr]) - - sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) - sl.distribute(data, mask) - sl.broadcast(MaskRadBcast(mask, rad)) - global_outputs = sl.run_searchlight(voxel_test_sfn) - - if rank == 0: - for d0 in range(rad, global_outputs.shape[0]-rad): - for d1 in range(rad, global_outputs.shape[1]-rad): - for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: - assert np.array_equal( - np.array(global_outputs[d0, d1, d2]), - np.array([d0, d1, d2])) - - def block_test(data, mask, max_blk_edge, rad): - - comm = MPI.COMM_WORLD - rank = comm.rank - - (dim0, dim1, dim2) = mask.shape - - # Initialize dataset with known pattern - for subj in data: - if subj is not None: - for tr in range(subj.shape[3]): - for d1 in range(dim0): - for d2 in range(dim1): - for d3 in range(dim2): - subj[d1, d2, d3, tr] = np.array( - [d1, d2, d3, tr]) - - sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) - sl.distribute(data, mask) - sl.broadcast(mask) - global_outputs = sl.run_block_function(block_test_sfn) - - if rank == 0: - for d0 in range(rad, global_outputs.shape[0]-rad): - for d1 in range(rad, global_outputs.shape[1]-rad): - for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: - assert np.array_equal( - np.array(global_outputs[d0, d1, d2]), - np.array([d0, d1, d2, 0])) - - # Create dataset - def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - mask = np.random.choice([True, False], (dim0, dim1, dim2)) - data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) - if i % size == rank - else None - for i in range(0, nsubj)] - voxel_test(data, mask, max_blk_edge, rad) - block_test(data, mask, max_blk_edge, rad) - - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=1) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=1, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=0, dim1=10, dim2=8, ntr=5, nsubj=5, max_blk_edge=4, rad=1) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) - do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) diff --git a/tests/utils/example_design.1D b/tests/utils/example_design.1D deleted file mode 100755 index 7385edea6..000000000 --- a/tests/utils/example_design.1D +++ /dev/null @@ -1,272 +0,0 @@ -# - 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 - 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 - 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 - 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 - 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 - 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 - 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 - 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 - 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 - 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 - 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 - 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 - 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 - 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 - 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 - 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 - 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 - 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 - 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 - 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 - 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 - 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 - 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 - 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 - 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 - 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 - 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 - 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 - 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 - 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 - 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 - 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 - 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 - 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 - 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 - 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 - 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 - 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 - 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 - 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 - 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 - 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 - 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 - 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 - 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 - 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 - 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 - 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 - 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 - 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 - 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 - 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 - 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 - 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 - 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 - 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 - 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 - 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 - 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 - 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 - 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 - 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 - 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 - 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 - 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 - 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 - 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 - 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 - 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 - 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 - 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 - 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 - 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 - 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 - 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 - 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 - 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 - 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 - 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 - 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 - 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 - 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 - 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 - 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 - 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 - 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 - 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 - 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 - 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 - 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 - 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 - 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 - 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 - 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 - 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 - 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 - 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 - 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 - 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 - 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 - 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 - 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 - 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 - 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 - 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 - 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 - 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 - 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 - 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 - 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 - 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 - 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 - 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 - 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 - 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 - 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 - 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 - 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 - 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 - 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 - 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 - 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 - 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 - 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 - 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 - 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 - 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 - 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 - 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 - 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 - 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 - 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 - 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 - 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 - 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 - 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 - 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 - 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 - 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 - 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 - 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 - 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 - 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 - 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 - 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 - 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 - 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 - 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 - 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 - 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 - 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 - 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 - 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 - 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 - 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 - 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 - 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 - 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 - 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 - 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 - 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 - 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 - 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 - 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 - 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 - 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 - 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 - 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 - 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 - 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 - 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 - 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 - 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 - 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 - 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 - 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 - 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 - 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 - 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 - 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 - 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 - 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 - 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 - 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 - 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 - 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 -# - diff --git a/tests/utils/example_stimtime_1_AFNI.txt b/tests/utils/example_stimtime_1_AFNI.txt deleted file mode 100644 index 48200041d..000000000 --- a/tests/utils/example_stimtime_1_AFNI.txt +++ /dev/null @@ -1,2 +0,0 @@ -5.2*2.0:2.0 40.0*4.0:1.5 -2.0*2.0:1.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_1_FSL.txt b/tests/utils/example_stimtime_1_FSL.txt deleted file mode 100644 index ce14b1d55..000000000 --- a/tests/utils/example_stimtime_1_FSL.txt +++ /dev/null @@ -1,3 +0,0 @@ -5.2 2.0 2.0 -40.0 1.5 4.0 -50.0 1.0 2.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_2_AFNI.txt b/tests/utils/example_stimtime_2_AFNI.txt deleted file mode 100644 index 39d03d9ee..000000000 --- a/tests/utils/example_stimtime_2_AFNI.txt +++ /dev/null @@ -1 +0,0 @@ --10.0 diff --git a/tests/utils/example_stimtime_2_FSL.txt b/tests/utils/example_stimtime_2_FSL.txt deleted file mode 100644 index e60beb2a6..000000000 --- a/tests/utils/example_stimtime_2_FSL.txt +++ /dev/null @@ -1,3 +0,0 @@ -5.2 2.0 -40.0 1.5 2.0 -50.0 \ No newline at end of file diff --git a/tests/utils/test_fmrisim.py b/tests/utils/test_fmrisim.py deleted file mode 100644 index 7c442661a..000000000 --- a/tests/utils/test_fmrisim.py +++ /dev/null @@ -1,858 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""fMRI Simulator test script - -Test script for generating a run of a participant's data. - - Authors: Cameron Ellis (Princeton) 2016 -""" -import numpy as np -import math -from brainiak.utils import fmrisim as sim -import pytest -from itertools import product - - -def test_generate_signal(): - - # Inputs for generate_signal - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [3] - feature_type = ['cube'] - feature_coordinates = np.array([[5, 5, 5]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - assert np.all(volume.shape == dimensions), "Check signal shape" - assert np.max(volume) == signal_magnitude, "Check signal magnitude" - assert np.sum(volume > 0) == math.pow(feature_size[0], 3), ( - "Check feature size") - assert volume[5, 5, 5] == signal_magnitude, "Check signal location" - assert volume[5, 5, 1] == 0, "Check noise location" - - feature_coordinates = np.array( - [[5, 5, 5], [3, 3, 3], [7, 7, 7]]) - - # Check feature size is correct - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=['loop', 'cavity', 'sphere'], - feature_size=[3], - signal_magnitude=signal_magnitude) - assert volume[5, 5, 5] == 0, "Loop is empty" - assert volume[3, 3, 3] == 0, "Cavity is empty" - assert volume[7, 7, 7] != 0, "Sphere is not empty" - - # Check feature size manipulation - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=['loop', 'cavity', 'sphere'], - feature_size=[1], - signal_magnitude=signal_magnitude) - assert volume[5, 6, 6] == 0, "Loop is too big" - assert volume[3, 5, 5] == 0, "Cavity is too big" - assert volume[7, 9, 9] == 0, "Sphere is too big" - - # Check that out of bounds feature coordinates are corrected - feature_coordinates = np.array([0, 2, dimensions[2]]) - x, y, z = sim._insert_idxs(feature_coordinates, feature_size[0], - dimensions) - assert x[1] - x[0] == 2, "x min not corrected" - assert y[1] - y[0] == 3, "y was corrected when it shouldn't be" - assert z[1] - z[0] == 1, "z max not corrected" - - # Check that signal patterns are created - feature_coordinates = np.array([[5, 5, 5]]) - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - signal_constant=0, - ) - assert volume[4:7, 4:7, 4:7].std() > 0, "Signal is constant" - - -def test_generate_stimfunction(): - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 100 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - assert stimfunction.shape[0] == duration * 100, "stimfunc incorrect length" - eventNumber = np.sum(event_durations * len(onsets)) * 100 - assert np.sum(stimfunction) == eventNumber, "Event number" - - # Create the signal function - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - stim_dur = stimfunction.shape[0] / (tr_duration * 100) - assert signal_function.shape[0] == stim_dur, "The length did not change" - - # Test - onsets = [0] - tr_duration = 1 - event_durations = [1] - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - max_response = np.where(signal_function != 0)[0].max() - assert 25 < max_response <= 30, "HRF has the incorrect length" - assert np.sum(signal_function < 0) > 0, "No values below zero" - - # Export a stimfunction - sim.export_3_column(stimfunction, - 'temp.txt', - ) - - # Load in the stimfunction - stimfunc_new = sim.generate_stimfunction(onsets=None, - event_durations=None, - total_time=duration, - timing_file='temp.txt', - ) - - assert np.all(stimfunc_new == stimfunction), "Export/import failed" - - # Break the timing precision of the generation - stimfunc_new = sim.generate_stimfunction(onsets=None, - event_durations=None, - total_time=duration, - timing_file='temp.txt', - temporal_resolution=0.5, - ) - - assert stimfunc_new.sum() == 0, "Temporal resolution not working right" - - # Set the duration to be too short so you should get an error - onsets = [10, 30, 50, 70, 90] - event_durations = [5] - with pytest.raises(ValueError): - sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=89, - ) - - # Clip the event offset - stimfunc_new = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=95, - ) - assert stimfunc_new[-1] == 1, 'Event offset was not clipped' - - # Test exporting a group of participants to an epoch file - cond_a = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=110, - ) - - cond_b = sim.generate_stimfunction(onsets=[x + 5 for x in onsets], - event_durations=event_durations, - total_time=110, - ) - - stimfunction_group = [np.hstack((cond_a, cond_b))] * 2 - sim.export_epoch_file(stimfunction_group, - 'temp.txt', - tr_duration, - ) - - # Check that convolve throws a warning when the shape is wrong - sim.convolve_hrf(stimfunction=np.hstack((cond_a, cond_b)).T, - tr_duration=tr_duration, - temporal_resolution=1, - ) - - -def test_apply_signal(): - - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[5, 5, 5]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 100 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - # Check that you can compute signal change appropriately - # Preset a bunch of things - stimfunction_tr = stimfunction[::int(tr_duration * 100)] - mask, template = sim.mask_brain(dimensions, mask_self=False) - noise_dict = sim._noise_dict_update({}) - noise = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=noise_dict, - iterations=[0, 0] - ) - coords = feature_coordinates[0] - noise_function_a = noise[coords[0], coords[1], coords[2], :] - noise_function_a = noise_function_a.reshape(duration // tr_duration, 1) - - noise_function_b = noise[coords[0] + 1, coords[1], coords[2], :] - noise_function_b = noise_function_b.reshape(duration // tr_duration, 1) - - # Create the calibrated signal with PSC - method = 'PSC' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - sig_b = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [1.0], - method, - ) - - assert sig_b.max() / sig_a.max() == 2, 'PSC modulation failed' - - # Create the calibrated signal with SFNR - method = 'SFNR' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a / (noise_function_a.mean() / noise_dict['sfnr']) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b / (noise_function_b.mean() / noise_dict['sfnr']) - - assert scaled_b.max() / scaled_a.max() == 2, 'SFNR modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-SD - method = 'CNR_Amp/Noise-SD' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a / noise_function_a.std() - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b / noise_function_b.std() - - assert scaled_b.max() / scaled_a.max() == 2, 'CNR_Amp modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-Var_dB - method = 'CNR_Amp2/Noise-Var_dB' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = np.log(sig_a.max() / noise_function_a.std()) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = np.log(sig_b.max() / noise_function_b.std()) - - assert np.round(scaled_b / scaled_a) == 2, 'CNR_Amp dB modulation failed' - - # Create the calibrated signal with CNR_Signal-SD/Noise-SD - method = 'CNR_Signal-SD/Noise-SD' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - scaled_a = sig_a.std() / noise_function_a.std() - sig_b = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [1.0], - method, - ) - scaled_b = sig_b.std() / noise_function_a.std() - - assert (scaled_b / scaled_a) == 2, 'CNR signal modulation failed' - - # Create the calibrated signal with CNR_Amp/Noise-Var_dB - method = 'CNR_Signal-Var/Noise-Var_dB' - sig_a = sim.compute_signal_change(signal_function, - noise_function_a, - noise_dict, - [0.5], - method, - ) - - scaled_a = np.log(sig_a.std() / noise_function_a.std()) - sig_b = sim.compute_signal_change(signal_function, - noise_function_b, - noise_dict, - [1.0], - method, - ) - scaled_b = np.log(sig_b.std() / noise_function_b.std()) - - assert np.round(scaled_b / scaled_a) == 2, 'CNR signal dB modulation ' \ - 'failed' - - # Convolve the HRF with the stimulus sequence - signal = sim.apply_signal(signal_function=signal_function, - volume_signal=volume, - ) - - assert signal.shape == (dimensions[0], dimensions[1], dimensions[2], - duration / tr_duration), "The output is the " \ - "wrong size" - - signal = sim.apply_signal(signal_function=stimfunction, - volume_signal=volume, - ) - - assert np.any(signal == signal_magnitude), "The stimfunction is not binary" - - # Check that there is an error if the number of signal voxels doesn't - # match the number of non zero brain voxels - with pytest.raises(IndexError): - sig_vox = (volume > 0).sum() - vox_pattern = np.tile(stimfunction, (1, sig_vox - 1)) - sim.apply_signal(signal_function=vox_pattern, - volume_signal=volume, - ) - - -def test_generate_noise(): - - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[5, 5, 5]]) - signal_magnitude = [1] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Inputs for generate_stimfunction - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 200 - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - ) - - signal_function = sim.convolve_hrf(stimfunction=stimfunction, - tr_duration=tr_duration, - ) - - # Convolve the HRF with the stimulus sequence - signal = sim.apply_signal(signal_function=signal_function, - volume_signal=volume, - ) - - # Generate the mask of the signal - mask, template = sim.mask_brain(signal, - mask_self=None) - - assert min(mask[mask > 0]) > 0.1, "Mask thresholding did not work" - assert len(np.unique(template) > 2), "Template creation did not work" - - stimfunction_tr = stimfunction[::int(tr_duration * 100)] - - # Create the noise volumes (using the default parameters) - noise = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - iterations=[1, 0], - ) - - assert signal.shape == noise.shape, "The dimensions of signal and noise " \ - "the same" - - noise_high = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={'sfnr': 50, 'snr': 25}, - iterations=[1, 0], - ) - - noise_low = sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={'sfnr': 100, 'snr': 25}, - iterations=[1, 0], - ) - - system_high = np.std(noise_high[mask > 0], 1).mean() - system_low = np.std(noise_low[mask > 0], 1).mean() - - assert system_low < system_high, "SFNR noise could not be manipulated" - - # Check that you check for the appropriate template values - with pytest.raises(ValueError): - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template * 2, - mask=mask, - noise_dict={}, - ) - - # Check that iterations does what it should - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={}, - iterations=[0, 0], - ) - - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict={}, - iterations=None, - ) - - # Test drift noise - trs = 1000 - period = 100 - drift = sim._generate_noise_temporal_drift(trs, - tr_duration, - 'sine', - period, - ) - - # Check that the max frequency is the appropriate frequency - power = abs(np.fft.fft(drift))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs - period_freq = np.where(freq == 1 / (period // tr_duration)) - max_freq = np.argmax(power) - - assert period_freq == max_freq, 'Max frequency is not where it should be' - - # Do the same but now with cosine basis functions, answer should be close - drift = sim._generate_noise_temporal_drift(trs, - tr_duration, - 'discrete_cos', - period, - ) - - # Check that the appropriate frequency is peaky (may not be the max) - power = abs(np.fft.fft(drift))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs - period_freq = np.where(freq == 1 / (period // tr_duration))[0][0] - - assert power[period_freq] > power[period_freq + 1], 'Power is low' - assert power[period_freq] > power[period_freq - 1], 'Power is low' - - # Check it gives a warning if the duration is too short - drift = sim._generate_noise_temporal_drift(50, - tr_duration, - 'discrete_cos', - period, - ) - - # Test physiological noise (using unrealistic parameters so that it's easy) - timepoints = list(np.linspace(0, (trs - 1) * tr_duration, trs)) - resp_freq = 0.2 - heart_freq = 1.17 - phys = sim._generate_noise_temporal_phys(timepoints, - resp_freq, - heart_freq, - ) - - # Check that the max frequency is the appropriate frequency - power = abs(np.fft.fft(phys))[1:trs // 2] - freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / (trs * tr_duration) - peaks = (power > (power.mean() + power.std())) # Where are the peaks - peak_freqs = freq[peaks] - - assert np.any(resp_freq == peak_freqs), 'Resp frequency not found' - assert len(peak_freqs) == 2, 'Two peaks not found' - - # Test task noise - sim._generate_noise_temporal_task(stimfunction_tr, - motion_noise='gaussian', - ) - sim._generate_noise_temporal_task(stimfunction_tr, - motion_noise='rician', - ) - - # Test ARMA noise - with pytest.raises(ValueError): - noise_dict = {'fwhm': 4, 'auto_reg_rho': [1], 'ma_rho': [1, 1]} - sim._generate_noise_temporal_autoregression(stimfunction_tr, - noise_dict, - dimensions, - mask, - ) - - # Generate spatial noise - vol = sim._generate_noise_spatial(np.array([10, 10, 10, trs])) - assert len(vol.shape) == 3, 'Volume was not reshaped to ignore TRs' - - # Switch some of the noise types on - noise_dict = dict(physiological_sigma=1, drift_sigma=1, task_sigma=1, - auto_reg_sigma=0) - sim.generate_noise(dimensions=dimensions, - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=noise_dict, - iterations=[0, 0], - ) - - -def test_generate_noise_spatial(): - - # Set up the inputs - dimensions = np.array([10, 5, 10]) - mask = np.ones(dimensions) - vol = sim._generate_noise_spatial(dimensions, mask) - - # Run the analysis from _calc_FHWM but for th elast step of aggregating - # across dimensions - v_count = 0 - v_sum = 0 - v_sq = 0 - - d_sum = [0.0, 0.0, 0.0] - d_sq = [0.0, 0.0, 0.0] - d_count = [0, 0, 0] - - # Pull out all the voxel coordinates - coordinates = list(product(range(dimensions[0]), - range(dimensions[1]), - range(dimensions[2]))) - - # Find the sum of squared error for the non-masked voxels in the brain - for i in list(range(len(coordinates))): - - # Pull out this coordinate - x, y, z = coordinates[i] - - # Is this within the mask? - if mask[x, y, z] > 0: - - # Find the the volume sum and squared values - v_count += 1 - v_sum += vol[x, y, z] - v_sq += vol[x, y, z] ** 2 - - # Get the volume variance - v_var = (v_sq - ((v_sum ** 2) / v_count)) / (v_count - 1) - - for i in list(range(len(coordinates))): - - # Pull out this coordinate - x, y, z = coordinates[i] - - # Is this within the mask? - if mask[x, y, z] > 0: - # For each xyz dimension calculate the squared - # difference of this voxel and the next - - in_range = (x < dimensions[0] - 1) - in_mask = in_range and (mask[x + 1, y, z] > 0) - included = in_mask and (~np.isnan(vol[x + 1, y, z])) - if included: - d_sum[0] += vol[x, y, z] - vol[x + 1, y, z] - d_sq[0] += (vol[x, y, z] - vol[x + 1, y, z]) ** 2 - d_count[0] += 1 - - in_range = (y < dimensions[1] - 1) - in_mask = in_range and (mask[x, y + 1, z] > 0) - included = in_mask and (~np.isnan(vol[x, y + 1, z])) - if included: - d_sum[1] += vol[x, y, z] - vol[x, y + 1, z] - d_sq[1] += (vol[x, y, z] - vol[x, y + 1, z]) ** 2 - d_count[1] += 1 - - in_range = (z < dimensions[2] - 1) - in_mask = in_range and (mask[x, y, z + 1] > 0) - included = in_mask and (~np.isnan(vol[x, y, z + 1])) - if included: - d_sum[2] += vol[x, y, z] - vol[x, y, z + 1] - d_sq[2] += (vol[x, y, z] - vol[x, y, z + 1]) ** 2 - d_count[2] += 1 - - # Find the variance - d_var = np.divide((d_sq - np.divide(np.power(d_sum, 2), - d_count)), (np.add(d_count, -1))) - - o_var = np.divide(-1, (4 * np.log(1 - (0.5 * d_var / v_var)))) - fwhm3 = np.sqrt(o_var) * 2 * np.sqrt(2 * np.log(2)) - - # Calculate the proportion of std relative to the mean - std_proportion = np.nanstd(fwhm3) / np.nanmean(fwhm3) - print(fwhm3) - assert std_proportion < 0.25, 'Variance is inconsistent across dim' - - -def test_mask_brain(): - - # Inputs for generate_signal - dimensions = np.array([10, 10, 10]) # What is the size of the brain - feature_size = [2] - feature_type = ['cube'] - feature_coordinates = np.array( - [[4, 4, 4]]) - signal_magnitude = [30] - - # Generate a volume representing the location and quality of the signal - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Mask the volume to be the same shape as a brain - mask, _ = sim.mask_brain(dimensions, mask_self=None,) - brain = volume * mask - - assert np.sum(brain != 0) == np.sum(volume != 0), "Masking did not work" - assert brain[0, 0, 0] == 0, "Masking did not work" - assert brain[4, 4, 4] != 0, "Masking did not work" - - feature_coordinates = np.array( - [[1, 1, 1]]) - - volume = sim.generate_signal(dimensions=dimensions, - feature_coordinates=feature_coordinates, - feature_type=feature_type, - feature_size=feature_size, - signal_magnitude=signal_magnitude, - ) - - # Mask the volume to be the same shape as a brain - mask, _ = sim.mask_brain(dimensions, mask_self=None, ) - brain = volume * mask - - assert np.sum(brain != 0) < np.sum(volume != 0), "Masking did not work" - - # Test that you can load the default - dimensions = np.array([100, 100, 100]) - mask, template = sim.mask_brain(dimensions, mask_self=False) - - assert mask[20, 80, 50] == 0, 'Masking didn''t work' - assert mask[25, 80, 50] == 1, 'Masking didn''t work' - assert int(template[25, 80, 50] * 100) == 57, 'Template not correct' - - # Check that you can mask self - mask_self, template_self = sim.mask_brain(template, mask_self=True) - - assert (template_self - template).sum() < 1e2, 'Mask self error' - assert (mask_self - mask).sum() == 0, 'Mask self error' - - -def test_calc_noise(): - - # Inputs for functions - onsets = [10, 30, 50, 70, 90] - event_durations = [6] - tr_duration = 2 - duration = 200 - temporal_res = 100 - tr_number = int(np.floor(duration / tr_duration)) - dimensions_tr = np.array([10, 10, 10, tr_number]) - - # Preset the noise dict - nd_orig = sim._noise_dict_update({}) - - # Create the time course for the signal to be generated - stimfunction = sim.generate_stimfunction(onsets=onsets, - event_durations=event_durations, - total_time=duration, - temporal_resolution=temporal_res, - ) - - # Mask the volume to be the same shape as a brain - mask, template = sim.mask_brain(dimensions_tr, mask_self=None) - stimfunction_tr = stimfunction[::int(tr_duration * temporal_res)] - - nd_orig['matched'] = 0 - noise = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - ) - - # Check the spatial noise match - nd_orig['matched'] = 1 - noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - iterations=[50, 0] - ) - - # Calculate the noise parameters from this newly generated volume - nd_new = sim.calc_noise(noise, mask, template) - nd_matched = sim.calc_noise(noise_matched, mask, template) - - # Check the values are reasonable" - assert nd_new['snr'] > 0, 'snr out of range' - assert nd_new['sfnr'] > 0, 'sfnr out of range' - assert nd_new['auto_reg_rho'][0] > 0, 'ar out of range' - - # Check that the dilation increases SNR - no_dilation_snr = sim._calc_snr(noise_matched, - mask, - dilation=0, - reference_tr=tr_duration, - ) - - assert nd_new['snr'] > no_dilation_snr, "Dilation did not increase SNR" - - # Check that template size is in bounds - with pytest.raises(ValueError): - sim.calc_noise(noise, mask, template * 2) - - # Check that Mask is set is checked - with pytest.raises(ValueError): - sim.calc_noise(noise, None, template) - - # Check that it can deal with missing noise parameters - temp_nd = sim.calc_noise(noise, mask, template, noise_dict={}) - assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' - - temp_nd = sim.calc_noise(noise, mask, template, noise_dict=None) - assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' - - # Check that the fitting worked - snr_diff = abs(nd_orig['snr'] - nd_new['snr']) - snr_diff_match = abs(nd_orig['snr'] - nd_matched['snr']) - assert snr_diff > snr_diff_match, 'snr fit incorrectly' - - # Test that you can generate rician and exponential noise - sim._generate_noise_system(dimensions_tr, - 1, - 1, - spatial_noise_type='exponential', - temporal_noise_type='rician', - ) - - # Check the temporal noise match - nd_orig['matched'] = 1 - noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], - stimfunction_tr=stimfunction_tr, - tr_duration=tr_duration, - template=template, - mask=mask, - noise_dict=nd_orig, - iterations=[0, 50] - ) - - nd_matched = sim.calc_noise(noise_matched, mask, template) - - sfnr_diff = abs(nd_orig['sfnr'] - nd_new['sfnr']) - sfnr_diff_match = abs(nd_orig['sfnr'] - nd_matched['sfnr']) - assert sfnr_diff > sfnr_diff_match, 'sfnr fit incorrectly' - - ar1_diff = abs(nd_orig['auto_reg_rho'][0] - nd_new['auto_reg_rho'][0]) - ar1_diff_match = abs(nd_orig['auto_reg_rho'][0] - nd_matched[ - 'auto_reg_rho'][0]) - assert ar1_diff > ar1_diff_match, 'AR1 fit incorrectly' - - # Check that you can calculate ARMA for a single voxel - vox = noise[5, 5, 5, :] - arma = sim._calc_ARMA_noise(vox, - None, - sample_num=2, - ) - assert len(arma) == 2, "Two outputs not given by ARMA" diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py deleted file mode 100644 index f37c51a7e..000000000 --- a/tests/utils/test_utils.py +++ /dev/null @@ -1,396 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - - -def test_tri_sym_convert(): - from brainiak.utils.utils import from_tri_2_sym, from_sym_2_tri - import numpy as np - - sym = np.random.rand(3, 3) - tri = from_sym_2_tri(sym) - assert tri.shape[0] == 6,\ - "from_sym_2_tri returned wrong result!" - sym1 = from_tri_2_sym(tri, 3) - assert sym1.shape[0] == sym1.shape[1],\ - "from_tri_2_sym returned wrong shape!" - tri1 = from_sym_2_tri(sym1) - assert np.array_equiv(tri, tri1),\ - "from_sym_2_tri returned wrong result!" - - -def test_sumexp(): - from brainiak.utils.utils import sumexp_stable - import numpy as np - - data = np.array([[1, 1], [0, 1]]) - sums, maxs, exps = sumexp_stable(data) - assert sums.size == data.shape[1], ( - "Invalid sum(exp(v)) computation (wrong # samples in sums)") - assert exps.shape[0] == data.shape[0], ( - "Invalid exp(v) computation (wrong # features)") - assert exps.shape[1] == data.shape[1], ( - "Invalid exp(v) computation (wrong # samples)") - assert maxs.size == data.shape[1], ( - "Invalid max computation (wrong # samples in maxs)") - - -def test_concatenate_not_none(): - from brainiak.utils.utils import concatenate_not_none - import numpy as np - arrays = [None] * 5 - - arrays[1] = np.array([0, 1, 2]) - arrays[3] = np.array([3, 4]) - - r = concatenate_not_none(arrays, axis=0) - - assert np.all(np.arange(5) == r), ( - "Invalid concatenation of a list of arrays") - - -def test_cov2corr(): - from brainiak.utils.utils import cov2corr - import numpy as np - cov = np.array([[4, 3, 0], [3, 9, 0], [0, 0, 1]]) - corr = cov2corr(cov) - assert np.allclose(corr, - np.array([[1, 0.5, 0], [0.5, 1, 0], [0, 0, 1]])), ( - "Converting from covariance matrix to correlation incorrect") - - -def test_ReadDesign(): - from brainiak.utils.utils import ReadDesign - import numpy as np - import os.path - file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") - design = ReadDesign(fname=file_path, include_orth=False, - include_pols=False) - assert design, 'Failed to read design matrix' - assert design.reg_nuisance is None, \ - 'Nuiance regressor is not None when include_orth and include_pols are'\ - ' both set to False' - read = ReadDesign() - assert read, 'Failed to initialize an instance of the class' - design = ReadDesign(fname=file_path, include_orth=True, include_pols=True) - assert np.size(design.cols_nuisance) == 10, \ - 'Mistake in counting the number of nuiance regressors' - assert np.size(design.cols_task) == 17, \ - 'Mistake in counting the number of task conditions' - assert (np.shape(design.reg_nuisance)[0] - == np.shape(design.design_task)[0] - ), 'The number of time points in nuiance regressor does not match'\ - ' that of task response' - - -def test_gen_design(): - from brainiak.utils.utils import gen_design - import numpy as np - import os.path - files = {'FSL1': 'example_stimtime_1_FSL.txt', - 'FSL2': 'example_stimtime_2_FSL.txt', - 'AFNI1': 'example_stimtime_1_AFNI.txt', - 'AFNI2': 'example_stimtime_2_AFNI.txt'} - for key in files.keys(): - files[key] = os.path.join(os.path.dirname(__file__), files[key]) - design1 = gen_design(stimtime_files=files['FSL1'], scan_duration=[48, 20], - TR=2, style='FSL') - assert design1.shape == (34, 1), 'Returned design matrix has wrong shape' - assert design1[24] == 0, ( - "gen_design should generated design matrix for each run separately " - "and concatenate them.") - design2 = gen_design(stimtime_files=[files['FSL1'], files['FSL2']], - scan_duration=[48, 20], TR=2, style='FSL') - assert design2.shape == (34, 2), 'Returned design matrix has wrong shape' - design3 = gen_design(stimtime_files=files['FSL1'], scan_duration=68, TR=2, - style='FSL') - assert design3[24] != 0, ( - 'design matrix should be non-zero 8 seconds after an event onset.') - design4 = gen_design(stimtime_files=[files['FSL2']], - scan_duration=[48, 20], TR=2, style='FSL') - assert np.all(np.isclose(design1 * 0.5, design4)), ( - 'gen_design does not treat missing values correctly') - design5 = gen_design(stimtime_files=[files['FSL2']], - scan_duration=[48, 20], TR=1) - assert (np.abs(design4 - design5[::2])).mean() < 0.1, ( - 'design matrices sampled at different frequency do not match' - ' at corresponding time points') - design6 = gen_design(stimtime_files=[files['AFNI1']], - scan_duration=[48, 20], TR=2, style='AFNI') - assert np.all(np.isclose(design1, design6)), ( - 'design matrices generated from AFNI style and FSL style do not match') - design7 = gen_design(stimtime_files=[files['AFNI2']], - scan_duration=[48], TR=2, style='AFNI') - assert np.all(design7 == 0.0), ( - 'A negative stimulus onset of AFNI style should result in an all-zero' - + ' design matrix') - - -def test_center_mass_exp(): - from brainiak.utils.utils import center_mass_exp - import numpy as np - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp([1, 2]) - assert ('interval must be a tuple' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((1, 2, 3)) - assert ('interval must be length two' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((-2, -1)) - assert ('interval_left must be non-negative' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((-2, 3)) - assert ('interval_left must be non-negative' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((3, 3)) - assert ('interval_right must be bigger than interval_left' - in str(excinfo.value)) - - with pytest.raises(AssertionError) as excinfo: - result = center_mass_exp((1, 2), -1) - assert ('scale must be positive' - in str(excinfo.value)) - - result = center_mass_exp((0, np.inf), 2.0) - assert np.isclose(result, 2.0), 'center of mass '\ - 'incorrect for the whole distribution' - result = center_mass_exp((1.0, 1.0+2e-10)) - assert np.isclose(result, 1.0+1e-10), 'for a small '\ - 'enough interval, the center of mass should be '\ - 'close to its mid-point' - - -def test_p_from_null(): - import numpy as np - from brainiak.utils.utils import p_from_null - - # Create random null and observed value in tail - null = np.random.randn(10000) - observed = np.ceil(np.percentile(null, 97.5) * 1000) / 1000 - - # Check that we catch improper side - with pytest.raises(ValueError): - _ = p_from_null(observed, null, side='wrong') - - # Check two-tailed p-value for observed - p_ts = p_from_null(observed, null) - assert np.isclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for observed - p_right = p_from_null(observed, null, side='right') - assert np.isclose(p_right, 0.025, atol=1e-02) - assert np.isclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for observed - p_left = p_from_null(observed, null, side='left') - assert np.isclose(p_left, 0.975, atol=1e-02) - assert np.isclose(1 - p_left, p_right, atol=1e-02) - assert np.isclose(1 - p_left, p_ts / 2, atol=1e-02) - - # Check 2-dimensional input (i.e., samples by voxels) - null = np.random.randn(10000, 3) - observed = np.ceil(np.percentile(null, 97.5, axis=0) * 1000) / 1000 - - # Check two-tailed p-value for observed - p_ts = p_from_null(observed, null, axis=0) - assert np.allclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for observed - p_right = p_from_null(observed, null, side='right', axis=0) - assert np.allclose(p_right, 0.025, atol=1e-02) - assert np.allclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for observed - p_left = p_from_null(observed, null, side='left', axis=0) - assert np.allclose(p_left, 0.975, atol=1e-02) - assert np.allclose(1 - p_left, p_right, atol=1e-02) - assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) - - # Check for exact test - p_ts = p_from_null(observed, null, exact=True, axis=0) - assert np.allclose(p_ts, 0.05, atol=1e-02) - - # Check two-tailed p-value for exact - p_right = p_from_null(observed, null, side='right', - exact=True, axis=0) - assert np.allclose(p_right, 0.025, atol=1e-02) - assert np.allclose(p_right, p_ts / 2, atol=1e-02) - - # Check two-tailed p-value for exact - p_left = p_from_null(observed, null, side='left', - exact=True, axis=0) - assert np.allclose(p_left, 0.975, atol=1e-02) - assert np.allclose(1 - p_left, p_right, atol=1e-02) - assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) - - -def test_phase_randomize(): - import numpy as np - from scipy.fftpack import fft - from scipy.stats import pearsonr - from brainiak.utils.utils import phase_randomize - - data = np.repeat(np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], - 30, axis=1), - 20, axis=2) - assert np.array_equal(data[..., 0], data[..., 1]) - - # Phase-randomize data across subjects (same across voxels) - shifted_data = phase_randomize(data, voxelwise=False, random_state=1) - assert shifted_data.shape == data.shape - assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) - assert not np.array_equal(shifted_data[..., 0], data[..., 0]) - - # Check that uneven n_TRs doesn't explode - _ = phase_randomize(data[:-1, ...]) - - # Check that random_state returns same shifts - shifted_data_ = phase_randomize(data, voxelwise=False, random_state=1) - assert np.array_equal(shifted_data, shifted_data_) - - shifted_data_ = phase_randomize(data, voxelwise=False, random_state=2) - assert not np.array_equal(shifted_data, shifted_data_) - - # Phase-randomize subjects and voxels - shifted_data = phase_randomize(data, voxelwise=True, random_state=1) - assert shifted_data.shape == data.shape - assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) - assert not np.array_equal(shifted_data[..., 0], data[..., 0]) - assert not np.array_equal(shifted_data[:, 0, 0], shifted_data[:, 1, 0]) - - # Try with 2-dimensional input - shifted_data = phase_randomize(data[..., 0], - voxelwise=True, - random_state=1) - assert shifted_data.ndim == 2 - assert not np.array_equal(shifted_data[:, 0], shifted_data[:, 1]) - - # Create correlated noisy data - corr_data = np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], - 2, axis=2) + np.random.randn(60, 1, 2) - - # Get correlation and frequency domain for data - corr_r = pearsonr(corr_data[:, 0, 0], - corr_data[:, 0, 1])[0] - corr_freq = fft(corr_data, axis=0) - - # Phase-randomize time series and get correlation/frequency - shifted_data = phase_randomize(corr_data) - shifted_r = pearsonr(shifted_data[:, 0, 0], - shifted_data[:, 0, 1])[0] - shifted_freq = fft(shifted_data, axis=0) - - # Check that phase-randomization reduces correlation - assert np.abs(shifted_r) < np.abs(corr_r) - - # Check that amplitude spectrum is preserved - assert np.allclose(np.abs(shifted_freq), np.abs(corr_freq)) - - -def test_check_timeseries_input(): - import numpy as np - from itertools import combinations - from brainiak.utils.utils import _check_timeseries_input - - # Set a fixed vector for comparison - vector = np.random.randn(60) - - # List of subjects with one voxel/ROI - list_1d = [vector for _ in np.arange(10)] - (data_list_1d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_1d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Array of subjects with one voxel/ROI - array_2d = np.hstack([vector[:, np.newaxis] - for _ in np.arange(10)]) - (data_array_2d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_2d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # List of 2-dimensional arrays - list_2d = [vector[:, np.newaxis] for _ in np.arange(10)] - (data_list_2d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_2d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Check if lists have mismatching size - list_bad = [list_2d[0][:-1, :]] + list_2d[1:] - with pytest.raises(ValueError): - (data_list_bad, _, _, _) = _check_timeseries_input(list_bad) - - # List of 3-dimensional arrays - list_3d = [vector[:, np.newaxis, np.newaxis] - for _ in np.arange(10)] - (data_list_3d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_3d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # 3-dimensional array - array_3d = np.dstack([vector[:, np.newaxis] - for _ in np.arange(10)]) - (data_array_3d, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_3d) - assert n_TRs == 60 - assert n_voxels == 1 - assert n_subjects == 10 - - # Check that 4-dimensional input array throws error - array_4d = array_3d[..., np.newaxis] - with pytest.raises(ValueError): - (data_array_4d, _, _, _) = _check_timeseries_input(array_4d) - - # Check they're the same - for pair in combinations([data_list_1d, data_array_2d, - data_list_2d, data_list_3d, - data_array_3d], 2): - assert np.array_equal(pair[0], pair[1]) - - # List of multivoxel arrays - matrix = np.random.randn(60, 30) - list_mv = [matrix - for _ in np.arange(10)] - (data_list_mv, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(list_mv) - assert n_TRs == 60 - assert n_voxels == 30 - assert n_subjects == 10 - - # 3-dimensional array with multiple voxels - array_mv = np.dstack([matrix for _ in np.arange(10)]) - (data_array_mv, n_TRs, - n_voxels, n_subjects) = _check_timeseries_input(array_mv) - assert n_TRs == 60 - assert n_voxels == 30 - assert n_subjects == 10 - - assert np.array_equal(data_list_mv, data_array_mv) From e1d8373a3a9371043bf19994d990899d1f72ec43 Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 17 Apr 2019 18:00:27 +0200 Subject: [PATCH 06/24] test_files back --- tests/eventseg/test_event.py | 155 +++ tests/factoranalysis/test_htfa.py | 168 +++ tests/factoranalysis/test_tfa.py | 110 ++ tests/fcma/data/expected_processed_data.npy | Bin 0 -> 2672 bytes tests/fcma/data/expected_raw_data.npy | Bin 0 -> 5264 bytes .../expected_searchlight_processed_data.npy | Bin 0 -> 1704016 bytes tests/fcma/test_classification.py | 222 ++++ tests/fcma/test_mvpa_voxel_selection.py | 51 + tests/fcma/test_preprocessing.py | 109 ++ tests/fcma/test_util.py | 59 ++ tests/fcma/test_voxel_selection.py | 116 +++ tests/hyperparamopt/test_hpo.py | 86 ++ tests/image/test_image.py | 172 ++++ tests/io/data/epoch_labels.npy | Bin 0 -> 160 bytes tests/io/data/mask.nii.gz | Bin 0 -> 634 bytes tests/io/data/subject1_bet.nii.gz | Bin 0 -> 7149 bytes tests/io/data/subject2_bet.nii.gz | Bin 0 -> 7112 bytes tests/io/test_io.py | 106 ++ tests/isc/test_isc.py | 956 ++++++++++++++++++ tests/reprsimil/example_design.1D | 272 +++++ tests/reprsimil/test_brsa.py | 651 ++++++++++++ tests/reprsimil/test_gbrsa.py | 630 ++++++++++++ tests/searchlight/test_searchlight.py | 293 ++++++ tests/utils/example_design.1D | 272 +++++ tests/utils/example_stimtime_1_AFNI.txt | 2 + tests/utils/example_stimtime_1_FSL.txt | 3 + tests/utils/example_stimtime_2_AFNI.txt | 1 + tests/utils/example_stimtime_2_FSL.txt | 3 + tests/utils/test_fmrisim.py | 858 ++++++++++++++++ tests/utils/test_utils.py | 396 ++++++++ 30 files changed, 5691 insertions(+) create mode 100644 tests/eventseg/test_event.py create mode 100644 tests/factoranalysis/test_htfa.py create mode 100644 tests/factoranalysis/test_tfa.py create mode 100644 tests/fcma/data/expected_processed_data.npy create mode 100644 tests/fcma/data/expected_raw_data.npy create mode 100644 tests/fcma/data/expected_searchlight_processed_data.npy create mode 100644 tests/fcma/test_classification.py create mode 100644 tests/fcma/test_mvpa_voxel_selection.py create mode 100644 tests/fcma/test_preprocessing.py create mode 100644 tests/fcma/test_util.py create mode 100644 tests/fcma/test_voxel_selection.py create mode 100644 tests/hyperparamopt/test_hpo.py create mode 100644 tests/image/test_image.py create mode 100644 tests/io/data/epoch_labels.npy create mode 100755 tests/io/data/mask.nii.gz create mode 100644 tests/io/data/subject1_bet.nii.gz create mode 100644 tests/io/data/subject2_bet.nii.gz create mode 100644 tests/io/test_io.py create mode 100644 tests/isc/test_isc.py create mode 100755 tests/reprsimil/example_design.1D create mode 100755 tests/reprsimil/test_brsa.py create mode 100644 tests/reprsimil/test_gbrsa.py create mode 100644 tests/searchlight/test_searchlight.py create mode 100755 tests/utils/example_design.1D create mode 100644 tests/utils/example_stimtime_1_AFNI.txt create mode 100644 tests/utils/example_stimtime_1_FSL.txt create mode 100644 tests/utils/example_stimtime_2_AFNI.txt create mode 100644 tests/utils/example_stimtime_2_FSL.txt create mode 100644 tests/utils/test_fmrisim.py create mode 100644 tests/utils/test_utils.py diff --git a/tests/eventseg/test_event.py b/tests/eventseg/test_event.py new file mode 100644 index 000000000..a82de27bd --- /dev/null +++ b/tests/eventseg/test_event.py @@ -0,0 +1,155 @@ +from brainiak.eventseg.event import EventSegment +from scipy.special import comb +import numpy as np +import pytest +from sklearn.exceptions import NotFittedError + + +def test_create_event_segmentation(): + es = EventSegment(5) + assert es, "Invalid EventSegment instance" + + +def test_fit_shapes(): + K = 5 + V = 3 + T = 10 + es = EventSegment(K, n_iter=2) + sample_data = np.random.rand(V, T) + es.fit(sample_data.T) + + assert es.segments_[0].shape == (T, K), "Segmentation from fit " \ + "has incorrect shape" + assert np.isclose(np.sum(es.segments_[0], axis=1), np.ones(T)).all(), \ + "Segmentation from learn_events not correctly normalized" + + T2 = 15 + sample_data2 = np.random.rand(V, T2) + test_segments, test_ll = es.find_events(sample_data2.T) + + assert test_segments.shape == (T2, K), "Segmentation from find_events " \ + "has incorrect shape" + assert np.isclose(np.sum(test_segments, axis=1), np.ones(T2)).all(), \ + "Segmentation from find_events not correctly normalized" + + es_invalid = EventSegment(K) + with pytest.raises(ValueError, message="T < K should cause error"): + es_invalid.model_prior(K-1) + with pytest.raises(ValueError, message="#Events < K should cause error"): + es_invalid.set_event_patterns(np.zeros((V, K-1))) + + +def test_simple_boundary(): + es = EventSegment(2) + random_state = np.random.RandomState(0) + + sample_data = np.array([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + \ + random_state.rand(2, 7) * 10 + es.fit(sample_data.T) + + events = np.argmax(es.segments_[0], axis=1) + assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ + "Failed to correctly segment two events" + + events_predict = es.predict(sample_data.T) + assert np.array_equal(events_predict, [0, 0, 0, 1, 1, 1, 1]), \ + "Error in predict interface" + + +def test_event_transfer(): + es = EventSegment(2) + sample_data = np.asarray([[1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]]) + + with pytest.raises(NotFittedError, message="Should need to set variance"): + seg = es.find_events(sample_data.T)[0] + + with pytest.raises(NotFittedError, message="Should need to set patterns"): + seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] + + es.set_event_patterns(np.asarray([[1, 0], [0, 1]])) + seg = es.find_events(sample_data.T, np.asarray([1, 1]))[0] + + events = np.argmax(seg, axis=1) + assert np.array_equal(events, [0, 0, 0, 1, 1, 1, 1]),\ + "Failed to correctly transfer two events to new data" + + +def test_weighted_var(): + es = EventSegment(2) + + D = np.zeros((8, 4)) + for t in range(4): + D[t, :] = (1/np.sqrt(4/3)) * np.array([-1, -1, 1, 1]) + for t in range(4, 8): + D[t, :] = (1 / np.sqrt(4 / 3)) * np.array([1, 1, -1, -1]) + mean_pat = D[[0, 4], :].T + + weights = np.zeros((8, 2)) + weights[:, 0] = [1, 1, 1, 1, 0, 0, 0, 0] + weights[:, 1] = [0, 0, 0, 0, 1, 1, 1, 1] + assert np.array_equal( + es.calc_weighted_event_var(D, weights, mean_pat), [0, 0]),\ + "Failed to compute variance with 0/1 weights" + + weights[:, 0] = [1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5] + weights[:, 1] = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] + true_var = (4 * 0.5 * 12)/(6 - 5/6) * np.ones(2) / 4 + assert np.allclose( + es.calc_weighted_event_var(D, weights, mean_pat), true_var),\ + "Failed to compute variance with fractional weights" + + +def test_sym(): + es = EventSegment(4) + + evpat = np.repeat(np.arange(10).reshape(-1, 1), 4, axis=1) + es.set_event_patterns(evpat) + + D = np.repeat(np.arange(10).reshape(1, -1), 20, axis=0) + ev = es.find_events(D, var=1)[0] + + # Check that events 1-4 and 2-3 are symmetric + assert np.all(np.isclose(ev[:, :2], np.fliplr(np.flipud(ev[:, 2:])))),\ + "Fit with constant data is not symmetric" + + +def test_chains(): + es = EventSegment(5, event_chains=np.array(['A', 'A', 'B', 'B', 'B'])) + + es.set_event_patterns(np.array([[1, 1, 0, 0, 0], + [0, 0, 1, 1, 1]])) + sample_data = np.array([[0, 0, 0], [1, 1, 1]]) + seg = es.find_events(sample_data.T, 0.1)[0] + + ev = np.nonzero(seg > 0.99)[1] + assert np.array_equal(ev, [2, 3, 4]),\ + "Failed to fit with multiple chains" + + +def test_prior(): + K = 10 + T = 100 + + es = EventSegment(K) + mp = es.model_prior(T)[0] + + p_bound = np.zeros((T, K-1)) + norm = comb(T-1, K-1) + for t in range(T-1): + for k in range(K-1): + # See supplementary material of Neuron paper + # https://doi.org/10.1016/j.neuron.2017.06.041 + p_bound[t+1, k] = comb(t, k) * comb(T-t-2, K-k-2) / norm + p_bound = np.cumsum(p_bound, axis=0) + + mp_gt = np.zeros((T, K)) + for k in range(K): + if k == 0: + mp_gt[:, k] = 1 - p_bound[:, 0] + elif k == K - 1: + mp_gt[:, k] = p_bound[:, k-1] + else: + mp_gt[:, k] = p_bound[:, k-1] - p_bound[:, k] + + assert np.all(np.isclose(mp, mp_gt)),\ + "Prior does not match analytic solution" diff --git a/tests/factoranalysis/test_htfa.py b/tests/factoranalysis/test_htfa.py new file mode 100644 index 000000000..58591b130 --- /dev/null +++ b/tests/factoranalysis/test_htfa.py @@ -0,0 +1,168 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_R(): + from brainiak.factoranalysis.htfa import HTFA + with pytest.raises(TypeError) as excinfo: + HTFA() + assert "missing 2 required positional arguments" in str(excinfo.value) + + +def test_X(): + from brainiak.factoranalysis.htfa import HTFA + import numpy as np + + n_voxel = 100 + n_tr = 20 + K = 5 + max_global_iter = 3 + max_local_iter = 3 + max_voxel = n_voxel + max_tr = n_tr + + R = [] + n_subj = 2 + for s in np.arange(n_subj): + R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) + + htfa = HTFA( + K, + n_subj=n_subj, + max_global_iter=max_global_iter, + max_local_iter=max_local_iter, + max_voxel=max_voxel, + max_tr=max_tr) + + X = np.random.rand(n_voxel, n_tr) + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "Input data should be a list" in str(excinfo.value) + + X = [] + # Check that does NOT run with wrong array dimension + with pytest.raises(ValueError) as excinfo: + htfa.fit(X, R=R) + assert "Need at leat one subject to train the model" in str(excinfo.value) + + X = [] + X.append([1, 2, 3]) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "data should be an array" in str(excinfo.value) + + X = [] + X.append(np.random.rand(n_voxel)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "subject data should be 2D array" in str(excinfo.value) + + X = [] + for s in np.arange(n_subj): + X.append(np.random.rand(n_voxel, n_tr)) + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert "Coordinates should be a list" in str(excinfo.value) + + R = [] + R.append([1, 2, 3]) + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("Each scanner coordinate matrix should be an array" + in str(excinfo.value)) + + R = [] + R.append(np.random.rand(n_voxel)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("Each scanner coordinate matrix should be 2D array" + in str(excinfo.value)) + + R = [] + for s in np.arange(n_subj): + R.append(np.random.rand(n_voxel - 1, 3)) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + htfa.fit(X, R=R) + assert ("n_voxel should be the same in X[idx] and R[idx]" + in str(excinfo.value)) + + +def test_can_run(): + import numpy as np + from brainiak.factoranalysis.htfa import HTFA + from mpi4py import MPI + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + + n_voxel = 100 + n_tr = 20 + K = 5 + max_global_iter = 3 + max_local_iter = 3 + max_voxel = n_voxel + max_tr = n_tr + R = [] + n_subj = 2 + for s in np.arange(n_subj): + R.append(np.random.randint(2, high=102, size=(n_voxel, 3))) + my_R = [] + for idx in np.arange(n_subj): + if idx % size == rank: + my_R.append(R[idx]) + + htfa = HTFA( + K, + n_subj=n_subj, + max_global_iter=max_global_iter, + max_local_iter=max_local_iter, + max_voxel=max_voxel, + max_tr=max_tr, + verbose=True) + assert htfa, "Invalid HTFA instance!" + + X = [] + for s in np.arange(n_subj): + X.append(np.random.rand(n_voxel, n_tr)) + my_data = [] + for idx in np.arange(n_subj): + if idx % size == rank: + my_data.append(X[idx]) + + if rank == 0: + htfa.fit(my_data, R=my_R) + assert True, "Root successfully running HTFA" + assert htfa.global_prior_.shape[0] == htfa.prior_bcast_size,\ + "Invalid result of HTFA! (wrong # element in global_prior)" + assert htfa.global_posterior_.shape[0] == htfa.prior_bcast_size,\ + "Invalid result of HTFA! (wrong # element in global_posterior)" + + else: + htfa.fit(my_data, R=my_R) + assert True, "worker successfully running HTFA" + print(htfa.local_weights_.shape) + assert htfa.local_weights_.shape[0] == n_tr * K,\ + "Invalid result of HTFA! (wrong # element in local_weights)" + assert htfa.local_posterior_.shape[0] == htfa.prior_size,\ + "Invalid result of HTFA! (wrong # element in local_posterior)" diff --git a/tests/factoranalysis/test_tfa.py b/tests/factoranalysis/test_tfa.py new file mode 100644 index 000000000..041d03cbd --- /dev/null +++ b/tests/factoranalysis/test_tfa.py @@ -0,0 +1,110 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_tfa(): + from brainiak.factoranalysis.tfa import TFA + import numpy as np + + n_voxel = 100 + n_tr = 20 + K = 5 + max_iter = 5 + max_num_voxel = n_voxel + max_num_tr = n_tr + tfa = TFA( + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + assert tfa, "Invalid TFA instance!" + + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + X = [1, 2, 3] + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "Input data should be an array" in str(excinfo.value) + + X = np.random.rand(n_voxel) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "Input data should be 2D array" in str(excinfo.value) + + X = np.random.rand(n_voxel, n_tr) + R = [1, 2, 3] + # Check that does NOT run with wrong data type + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "coordinate matrix should be an array" in str(excinfo.value) + + R = np.random.rand(n_voxel) + # Check that does NOT run with wrong array dimension + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "coordinate matrix should be 2D array" in str(excinfo.value) + + R = np.random.randint(2, high=102, size=(n_voxel - 1, 3)) + # Check that does NOT run if n_voxel in X and R does not match + with pytest.raises(TypeError) as excinfo: + tfa.fit(X, R=R) + assert "The number of voxels should be the same in X and R" in str( + excinfo.value) + + R = np.random.randint(2, high=102, size=(n_voxel, 3)) + tfa.fit(X, R=R) + assert True, "Success running TFA with one subject!" + posterior_size = K * (tfa.n_dim + 1) + assert tfa.local_posterior_.shape[ + 0] == posterior_size,\ + "Invalid result of TFA! (wrong # element in local_posterior)" + + weight_method = 'ols' + tfa = TFA( + weight_method=weight_method, + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + assert tfa, "Invalid TFA instance!" + + X = np.random.rand(n_voxel, n_tr) + tfa.fit(X, R=R) + assert True, "Success running TFA with one subject!" + + template_prior, _, _ = tfa.get_template(R) + tfa.set_K(K) + tfa.set_seed(200) + tfa.fit(X, R=R, template_prior=template_prior) + assert True, "Success running TFA with one subject and template prior!" + assert tfa.local_posterior_.shape[ + 0] == posterior_size,\ + "Invalid result of TFA! (wrong # element in local_posterior)" + + weight_method = 'odd' + tfa = TFA( + weight_method=weight_method, + K=K, + max_iter=max_iter, + verbose=True, + max_num_voxel=max_num_voxel, + max_num_tr=max_num_tr) + with pytest.raises(ValueError) as excinfo: + tfa.fit(X, R=R) + assert "'rr' and 'ols' are accepted as weight_method!" in str( + excinfo.value) diff --git a/tests/fcma/data/expected_processed_data.npy b/tests/fcma/data/expected_processed_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..460553dff13f0839b8571f9a0943cfb4eb237fe0 GIT binary patch literal 2672 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 Niah$^!R=(7{0Gj{kZb?| literal 0 HcmV?d00001 diff --git a/tests/fcma/data/expected_searchlight_processed_data.npy b/tests/fcma/data/expected_searchlight_processed_data.npy new file mode 100644 index 0000000000000000000000000000000000000000..e23174f2c7f6c3458e2fcb6f9bee6b5ecf7970c7 GIT binary patch literal 1704016 zcmeI*F>BjE6aZj%?XS@07CaS`p%k)ZYsu1~Q!$N0htd?gTM6Vp^auH`RZj06_!&i& zX%c}xfq8zXJL&vApGMv94v{Bli0hZA z4i5_))gk#+-)P?*?zdIPceTg3j`;BGoUQ&HzI{TqKH2}o>-F8w`9pzPzwFt)4mm*`;g>*DSwhl zPxvOC^(uAfqlvmkzsS3;8~wz86UdG+sygq=eoQ&WI*RtlBX+dM^~L`qkn1-zk6Cn$ zi}T{+2xRwsm%0vPU8R`&%Lx!5K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfWR08>ihWZUonPq9L=HDr|g4}JM^AUb16VFKAZ=fke1Hvs|!2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF c5FkK+009C72oNAZfB*pk1PBlyKwzlAA8SKUk^lez literal 0 HcmV?d00001 diff --git a/tests/fcma/test_classification.py b/tests/fcma/test_classification.py new file mode 100644 index 000000000..3225fa80d --- /dev/null +++ b/tests/fcma/test_classification.py @@ -0,0 +1,222 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.classifier import Classifier +from scipy.stats.mstats import zscore +from sklearn import svm +from sklearn.linear_model import LogisticRegression +import numpy as np +import math +from numpy.random import RandomState +from scipy.spatial.distance import hamming + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def create_epoch(idx, num_voxels): + row = 12 + col = num_voxels + mat = prng.rand(row, col).astype(np.float32) + # impose a pattern to even epochs + if idx % 2 == 0: + mat = np.sort(mat, axis=0) + mat = zscore(mat, axis=0, ddof=0) + # if zscore fails (standard deviation is zero), + # set all values to be zero + mat = np.nan_to_num(mat) + mat = mat / math.sqrt(mat.shape[0]) + return mat + + +def test_classification(): + fake_raw_data = [create_epoch(i, 5) for i in range(20)] + labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # 5 subjects, 4 epochs per subject + epochs_per_subj = 4 + # svm + svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + training_data = fake_raw_data[0:12] + clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data)), labels[0:12]) + expected_confidence = np.array([-1.18234421, 0.97403604, -1.04005679, + 0.92403019, -0.95567738, 1.11746593, + -0.83275891, 0.9486868]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM does not provide correct results' + confidence = clf.decision_function(list(zip(fake_raw_data[12:], + fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence) + ) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM without recomputation ' \ + 'does not provide correct results' + y = [0, 1, 0, 1, 0, 1, 0, 1] + score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data[12:])), y) + assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ + 'the prediction score is incorrect' + # svm with partial similarity matrix computation + clf = Classifier(svm_clf, num_processed_voxels=2, + epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(fake_raw_data, fake_raw_data)), + labels, + num_training_samples=12) + y_pred = clf.predict() + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM (partial sim) does not ' \ + 'provide correct results' + confidence = clf.decision_function() + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM (partial sim) without recomputation ' \ + 'does not provide correct results' + # logistic regression + lr_clf = LogisticRegression() + clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data)), labels[0:12]) + expected_confidence = np.array([-4.49666484, 3.73025553, -4.04181695, + 3.73027436, -3.77043872, 4.42613412, + -3.35616616, 3.77716609]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data[12:]))) + expected_output = [0, 0, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via logistic regression ' \ + 'does not provide correct results' + confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence) + ) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression without precomputation ' \ + 'does not provide correct results' + + +def test_classification_with_two_components(): + fake_raw_data = [create_epoch(i, 5) for i in range(20)] + fake_raw_data2 = [create_epoch(i, 6) for i in range(20)] + labels = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # 5 subjects, 4 epochs per subject + epochs_per_subj = 4 + # svm + svm_clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + training_data = fake_raw_data[0: 12] + training_data2 = fake_raw_data2[0: 12] + clf = Classifier(svm_clf, epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(training_data, training_data2)), labels[0:12]) + expected_confidence = np.array([-1.23311606, 1.02440964, -0.93898336, + 1.07028798, -1.04420007, 0.97647772, + -1.0498268, 1.04970111]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM does not provide correct results' + confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM without recomputation ' \ + 'does not provide correct results' + y = [0, 1, 0, 1, 0, 1, 0, 1] + score = clf.score(list(zip(fake_raw_data[12:], fake_raw_data2[12:])), y) + assert np.isclose([hamming(y_pred, y)], [1-score])[0], \ + 'the prediction score is incorrect' + # svm with partial similarity matrix computation + clf = Classifier(svm_clf, num_processed_voxels=2, + epochs_per_subj=epochs_per_subj) + clf.fit(list(zip(fake_raw_data, fake_raw_data2)), + labels, + num_training_samples=12) + y_pred = clf.predict() + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via SVM (partial sim) does not ' \ + 'provide correct results' + confidence = clf.decision_function() + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of SVM (partial sim) without recomputation ' \ + 'does not provide correct results' + # logistic regression + lr_clf = LogisticRegression() + clf = Classifier(lr_clf, epochs_per_subj=epochs_per_subj) + # specifying num_training_samples is for coverage + clf.fit(list(zip(training_data, training_data2)), + labels[0:12], + num_training_samples=12) + expected_confidence = np.array([-4.90819848, 4.22548132, -3.76255726, + 4.46505975, -4.19933099, 4.08313584, + -4.23070437, 4.31779758]) + recomputed_confidence = clf.decision_function(list(zip( + fake_raw_data[12:], fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(recomputed_confidence) + ) * expected_confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression with recomputation ' \ + 'does not provide correct results' + y_pred = clf.predict(list(zip(fake_raw_data[12:], fake_raw_data2[12:]))) + expected_output = [0, 1, 0, 1, 0, 1, 0, 1] + hamming_distance = hamming(y_pred, expected_output) * len(y_pred) + assert hamming_distance <= 1, \ + 'classification via logistic regression ' \ + 'does not provide correct results' + confidence = clf.decision_function(list(zip(fake_raw_data[12:], + fake_raw_data2[12:]))) + hamming_distance = hamming(np.sign(expected_confidence), + np.sign(confidence)) * confidence.size + assert hamming_distance <= 1, \ + 'decision function of logistic regression without precomputation ' \ + 'does not provide correct results' + + +if __name__ == '__main__': + test_classification() + test_classification_with_two_components() diff --git a/tests/fcma/test_mvpa_voxel_selection.py b/tests/fcma/test_mvpa_voxel_selection.py new file mode 100644 index 000000000..d551ff5ee --- /dev/null +++ b/tests/fcma/test_mvpa_voxel_selection.py @@ -0,0 +1,51 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.mvpa_voxelselector import MVPAVoxelSelector +from brainiak.searchlight.searchlight import Searchlight +from sklearn import svm +import numpy as np +from mpi4py import MPI +from numpy.random import RandomState + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def test_mvpa_voxel_selection(): + data = prng.rand(5, 5, 5, 8).astype(np.float32) + # all MPI processes read the mask; the mask file is small + mask = np.ones([5, 5, 5], dtype=np.bool) + mask[0, 0, :] = False + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + sl = Searchlight(sl_rad=1) + mvs = MVPAVoxelSelector(data, mask, labels, 2, sl) + # for cross validation, use SVM with precomputed kernel + + clf = svm.SVC(kernel='rbf', C=10) + result_volume, results = mvs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [] + for tuple in results: + if tuple[1] > 0: + output.append(int(8*tuple[1])) + expected_output = [6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + + +if __name__ == '__main__': + test_mvpa_voxel_selection() diff --git a/tests/fcma/test_preprocessing.py b/tests/fcma/test_preprocessing.py new file mode 100644 index 000000000..0ee38db49 --- /dev/null +++ b/tests/fcma/test_preprocessing.py @@ -0,0 +1,109 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +import numpy as np + +from brainiak.fcma.preprocessing import (prepare_fcma_data, prepare_mvpa_data, + prepare_searchlight_mvpa_data) +from brainiak import io + +data_dir = Path(__file__).parents[1] / 'io' / 'data' +expected_dir = Path(__file__).parent / 'data' +suffix = 'bet.nii.gz' +mask_file = data_dir / 'mask.nii.gz' +epoch_file = data_dir / 'epoch_labels.npy' +expected_labels = np.array([0, 1, 0, 1]) + + +def test_prepare_fcma_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + mask = io.load_boolean_mask(mask_file) + conditions = io.load_labels(epoch_file) + raw_data, _, labels = prepare_fcma_data(images, conditions, mask) + expected_raw_data = np.load(expected_dir / 'expected_raw_data.npy') + assert len(raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + for idx in range(len(raw_data)): + assert np.allclose(raw_data[idx], expected_raw_data[idx]), \ + 'raw data do not match in test_prepare_fcma_data' + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_fcma_data' + from brainiak.fcma.preprocessing import RandomType + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, + random=RandomType.REPRODUCIBLE) + assert len(random_raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_raw_data, _, _ = prepare_fcma_data(images, conditions, mask, + random=RandomType.UNREPRODUCIBLE) + assert len(random_raw_data) == len(expected_raw_data), \ + 'numbers of epochs do not match in test_prepare_fcma_data' + + +def test_prepare_mvpa_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + mask = io.load_boolean_mask(mask_file) + conditions = io.load_labels(epoch_file) + processed_data, labels = prepare_mvpa_data(images, conditions, mask) + expected_processed_data = np.load(expected_dir + / 'expected_processed_data.npy') + assert len(processed_data) == len(expected_processed_data), \ + 'numbers of epochs do not match in test_prepare_mvpa_data' + for idx in range(len(processed_data)): + assert np.allclose(processed_data[idx], + expected_processed_data[idx]), ( + 'raw data do not match in test_prepare_mvpa_data') + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_mvpa_data' + + +def test_prepare_searchlight_mvpa_data(): + images = io.load_images_from_dir(data_dir, suffix=suffix) + conditions = io.load_labels(epoch_file) + processed_data, labels = prepare_searchlight_mvpa_data(images, + conditions) + expected_searchlight_processed_data = np.load( + expected_dir / 'expected_searchlight_processed_data.npy') + for idx in range(len(processed_data)): + assert np.allclose(processed_data[idx], + expected_searchlight_processed_data[idx]), ( + 'raw data do not match in test_prepare_searchlight_mvpa_data') + assert np.array_equal(labels, expected_labels), \ + 'the labels do not match in test_prepare_searchlight_mvpa_data' + from brainiak.fcma.preprocessing import RandomType + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_processed_data, _ = prepare_searchlight_mvpa_data( + images, + conditions, + random=RandomType.REPRODUCIBLE) + assert (len(random_processed_data) + == len(expected_searchlight_processed_data)), ( + 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') + images = io.load_images_from_dir(data_dir, suffix=suffix) + random_processed_data, _ = prepare_searchlight_mvpa_data( + images, + conditions, + random=RandomType.UNREPRODUCIBLE) + assert (len(random_processed_data) + == len(expected_searchlight_processed_data)), ( + 'numbers of epochs do not match in test_prepare_searchlight_mvpa_data') + + +if __name__ == '__main__': + test_prepare_fcma_data() + test_prepare_mvpa_data() + test_prepare_searchlight_mvpa_data() diff --git a/tests/fcma/test_util.py b/tests/fcma/test_util.py new file mode 100644 index 000000000..6508d49b1 --- /dev/null +++ b/tests/fcma/test_util.py @@ -0,0 +1,59 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from numpy.random import RandomState +from brainiak.fcma.util import compute_correlation + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def test_correlation_computation(): + row1 = 5 + col = 10 + row2 = 6 + mat1 = prng.rand(row1, col).astype(np.float32) + mat2 = prng.rand(row2, col).astype(np.float32) + corr = compute_correlation(mat1, mat1) + expected_corr = np.corrcoef(mat1) + assert np.allclose(corr, expected_corr, atol=1e-5), ( + "high performance correlation computation does not provide correct " + "correlation results within the same set") + corr = compute_correlation(mat1, mat2) + mat = np.concatenate((mat1, mat2), axis=0) + expected_corr = np.corrcoef(mat)[0:row1, row1:] + assert np.allclose(corr, expected_corr, atol=1e-5), ( + "high performance correlation computation does not provide correct " + "correlation results between two sets") + + +def test_correlation_nans(): + row1 = 5 + col = 10 + row2 = 6 + mat1 = prng.rand(row1, col).astype(np.float32) + mat2 = prng.rand(row2, col).astype(np.float32) + mat1[0, 0] = np.nan + corr = compute_correlation(mat1, mat2, return_nans=False) + assert np.all(corr == 0, axis=1)[0] + assert np.sum(corr == 0) == row2 + corr = compute_correlation(mat1, mat2, return_nans=True) + assert np.all(np.isnan(corr), axis=1)[0] + assert np.sum(np.isnan(corr)) == row2 + + +if __name__ == '__main__': + test_correlation_computation() + test_correlation_nans() diff --git a/tests/fcma/test_voxel_selection.py b/tests/fcma/test_voxel_selection.py new file mode 100644 index 000000000..98fadc4ea --- /dev/null +++ b/tests/fcma/test_voxel_selection.py @@ -0,0 +1,116 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from brainiak.fcma.voxelselector import VoxelSelector +from scipy.stats.mstats import zscore +from sklearn import svm +from sklearn.linear_model import LogisticRegression +import numpy as np +import math +from mpi4py import MPI +from numpy.random import RandomState + +# specify the random state to fix the random numbers +prng = RandomState(1234567890) + + +def create_epoch(): + row = 12 + col = 5 + mat = prng.rand(row, col).astype(np.float32) + mat = zscore(mat, axis=0, ddof=0) + # if zscore fails (standard deviation is zero), + # set all values to be zero + mat = np.nan_to_num(mat) + mat = mat / math.sqrt(mat.shape[0]) + return mat + + +def test_voxel_selection(): + fake_raw_data = [create_epoch() for i in range(8)] + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + vs = VoxelSelector(labels, 4, 2, fake_raw_data, voxel_unit=1) + # test scipy normalization + fake_corr = prng.rand(1, 4, 5).astype(np.float32) + fake_corr = vs._correlation_normalization(fake_corr) + if MPI.COMM_WORLD.Get_rank() == 0: + expected_fake_corr = [[[1.06988919, 0.51641309, -0.46790636, + -1.31926763, 0.2270218], + [-1.22142744, -1.39881694, -1.2979387, + 1.05702305, -0.6525566], + [0.89795232, 1.27406132, 0.36460185, + 0.87538344, 1.5227468], + [-0.74641371, -0.39165771, 1.40124381, + -0.61313909, -1.0972116]]] + assert np.allclose(fake_corr, expected_fake_corr), \ + 'within-subject normalization does not provide correct results' + # for cross validation, use SVM with precomputed kernel + # no shrinking, set C=1 + clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [7, 4, 6, 4, 4] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + # for cross validation, use logistic regression + clf = LogisticRegression() + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [6, 3, 6, 4, 4] + assert np.allclose(output, expected_output, atol=1), ( + "voxel selection via logistic regression does not provide correct " + "results") + + +def test_voxel_selection_with_two_masks(): + fake_raw_data1 = [create_epoch() for i in range(8)] + fake_raw_data2 = [create_epoch() for i in range(8)] + labels = [0, 1, 0, 1, 0, 1, 0, 1] + # 2 subjects, 4 epochs per subject + vs = VoxelSelector(labels, 4, 2, fake_raw_data1, + raw_data2=fake_raw_data2, voxel_unit=1) + # for cross validation, use SVM with precomputed kernel + # no shrinking, set C=1 + clf = svm.SVC(kernel='precomputed', shrinking=False, C=1) + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [3, 3, 3, 6, 6] + assert np.allclose(output, expected_output, atol=1), \ + 'voxel selection via SVM does not provide correct results' + # for cross validation, use logistic regression + clf = LogisticRegression() + results = vs.run(clf) + if MPI.COMM_WORLD.Get_rank() == 0: + output = [None] * len(results) + for tuple in results: + output[tuple[0]] = int(8*tuple[1]) + expected_output = [3, 4, 4, 6, 6] + assert np.allclose(output, expected_output, atol=1), ( + "voxel selection via logistic regression does not provide correct " + "results") + + +if __name__ == '__main__': + test_voxel_selection() + test_voxel_selection_with_two_masks() diff --git a/tests/hyperparamopt/test_hpo.py b/tests/hyperparamopt/test_hpo.py new file mode 100644 index 000000000..3904458ee --- /dev/null +++ b/tests/hyperparamopt/test_hpo.py @@ -0,0 +1,86 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest +import numpy as np +import scipy.stats as st +from brainiak.hyperparamopt.hpo import gmm_1d_distribution, fmin + + +def test_simple_gmm(): + x = np.array([1., 1., 2., 3., 1.]) + d = gmm_1d_distribution(x, min_limit=0., max_limit=4.) + assert d(1.1) > d(3.5), "GMM distribution not behaving correctly" + assert d(2.0) > d(3.0), "GMM distribution not behaving correctly" + assert d(-1.0) == 0, "GMM distribution out of bounds error" + assert d(9.0) == 0, "GMM distribution out of bounds error" + + samples = d.get_samples(n=25) + np.testing.assert_array_less(samples, 4.) + np.testing.assert_array_less(0., samples) + + +def test_simple_gmm_weights(): + x = np.array([1., 1., 2., 3., 1., 3.]) + d = gmm_1d_distribution(x) + + x2 = np.array([1., 2., 3.]) + w = np.array([3., 1., 2.]) + d2 = gmm_1d_distribution(x2, weights=w) + y2 = d2(np.array([1.1, 2.0])) + + assert d2(1.1) == y2[0],\ + "GMM distribution array & scalar results don't match" + assert np.abs(d(1.1) - d2(1.1)) < 1e-5,\ + "GMM distribution weights not handled correctly" + assert np.abs(d(2.0) - d2(2.0)) < 1e-5,\ + "GMM distribution weights not handled correctly" + + +def test_simple_hpo(): + + def f(args): + x = args['x'] + return x*x + + s = {'x': {'dist': st.uniform(loc=-10., scale=20), 'lo': -10., 'hi': 10.}} + trials = [] + + # Test fmin and ability to continue adding to trials + best = fmin(loss_fn=f, space=s, max_evals=40, trials=trials) + best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) + + assert len(trials) == 50, "HPO continuation trials not working" + + # Test verbose flag + best = fmin(loss_fn=f, space=s, max_evals=10, trials=trials) + + yarray = np.array([tr['loss'] for tr in trials]) + np.testing.assert_array_less(yarray, 100.) + + xarray = np.array([tr['x'] for tr in trials]) + np.testing.assert_array_less(np.abs(xarray), 10.) + + assert best['loss'] < 100., "HPO out of range" + assert np.abs(best['x']) < 10., "HPO out of range" + + # Test unknown distributions + s2 = {'x': {'dist': 'normal', 'mu': 0., 'sigma': 1.}} + trials2 = [] + with pytest.raises(ValueError) as excinfo: + fmin(loss_fn=f, space=s2, max_evals=40, trials=trials2) + assert "Unknown distribution type for variable" in str(excinfo.value) + + s3 = {'x': {'dist': st.norm(loc=0., scale=1.)}} + trials3 = [] + fmin(loss_fn=f, space=s3, max_evals=40, trials=trials3) diff --git a/tests/image/test_image.py b/tests/image/test_image.py new file mode 100644 index 000000000..4796bc3f1 --- /dev/null +++ b/tests/image/test_image.py @@ -0,0 +1,172 @@ +# Copyright 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Iterable, Sequence + +import numpy as np +import pytest + +from nibabel.nifti1 import Nifti1Pair +from nibabel.spatialimages import SpatialImage + +from brainiak.image import (mask_image, mask_images, MaskedMultiSubjectData, + multimask_images, SingleConditionSpec) + + +@pytest.fixture +def masked_multi_subject_data(masked_images): + return np.stack(masked_images, axis=-1) + + +class TestMaskedMultiSubjectData: + def test_from_masked_images(self, masked_images, + masked_multi_subject_data): + result = MaskedMultiSubjectData.from_masked_images(masked_images, + len(masked_images)) + assert np.array_equal(np.moveaxis(result, 1, 0), + masked_multi_subject_data) + + +@pytest.fixture +def condition_spec() -> SingleConditionSpec: + return np.array([[[1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]]], + dtype=np.int8).view(SingleConditionSpec) + + +class TestUniqueLabelConditionSpec: + def test_extract_labels(self, condition_spec: SingleConditionSpec + ) -> None: + assert np.array_equal(condition_spec.extract_labels(), + np.array([0, 1])) + + +@pytest.fixture +def spatial_image() -> SpatialImage: + return Nifti1Pair(np.array([[[[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]]]).reshape(4, 4, 4, 1), + np.eye(4)) + + +@pytest.fixture +def mask() -> np.ndarray: + return np.array([[[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 1, 0], + [0, 0, 0, 0]], + [[0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]], dtype=np.bool) + + +@pytest.fixture +def masked_data() -> np.ndarray: + return np.array([[1, 0, 0, 1, 0, 1, 1, 0]]).reshape(8, 1) + + +@pytest.fixture +def images(spatial_image: SpatialImage) -> Iterable[SpatialImage]: + images = [spatial_image] + image_data = spatial_image.get_data().copy() + image_data[1, 1, 1, 0] = 2 + images.append(Nifti1Pair(image_data, np.eye(4))) + return images + + +@pytest.fixture +def masks(mask: np.ndarray) -> Sequence[np.ndarray]: + masks = [mask] + mask2 = mask.copy() + mask2[0, 0, 0] = 1 + masks.append(mask2) + mask3 = mask.copy() + mask3[2, 2, 2] = 0 + masks.append(mask3) + return masks + + +@pytest.fixture +def multimasked_images(masked_data) -> Iterable[Iterable[np.ndarray]]: + masked_data_2 = np.concatenate(([[2]], masked_data[1:, :])) + return [[masked_data, np.concatenate(([[0]], masked_data)), + masked_data[:-1, :]], + [masked_data_2, np.concatenate(([[0]], masked_data_2)), + masked_data_2[:-1, :]]] + + +@pytest.fixture +def masked_images(multimasked_images) -> Iterable[np.ndarray]: + return [multimasked_image[0] for multimasked_image in multimasked_images] + + +def test_mask_image(spatial_image: SpatialImage, mask: np.ndarray, + masked_data: np.ndarray) -> None: + result = mask_image(spatial_image, mask) + assert np.array_equal(result, masked_data) + + +def test_mask_image_with_type(spatial_image: SpatialImage, mask: np.ndarray, + masked_data: np.ndarray) -> None: + masked_data_type = np.float32 + result = mask_image(spatial_image, mask, masked_data_type) + assert result.dtype == masked_data_type + assert np.allclose(result, masked_data) + + +def test_multimask_images( + images: Iterable[SpatialImage], + masks: Sequence[np.ndarray], + multimasked_images: Iterable[Iterable[np.ndarray]] + ) -> None: + result = multimask_images(images, masks) + for result_images, expected_images in zip(result, + multimasked_images): + for result_image, expected_image in zip(result_images, + expected_images): + assert np.array_equal(result_image, expected_image) + + +def test_mask_images( + images: Iterable[SpatialImage], + mask: np.ndarray, + masked_images: Iterable[np.ndarray] + ) -> None: + result = mask_images(images, mask) + for result_image, expected_image in zip(result, masked_images): + assert np.array_equal(result_image, expected_image) diff --git a/tests/io/data/epoch_labels.npy b/tests/io/data/epoch_labels.npy new file mode 100644 index 0000000000000000000000000000000000000000..50b0a4304f8d755e5ba6147e7c2ebe6021326848 GIT binary patch literal 160 zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7ItmbEXrQU1P^+Mzz{Lmz4EO<939;$`jT{x$ literal 0 HcmV?d00001 diff --git a/tests/io/data/mask.nii.gz b/tests/io/data/mask.nii.gz new file mode 100755 index 0000000000000000000000000000000000000000..687ab69749ac9e33b4aa23f6e3bf269c37b6d2c9 GIT binary patch literal 634 zcmb2|=3oE==C^kYvzQzuSRY7UV(CwqYA3*PLz^Y&=2G8@+%padSBUgK5L~<>QHRC) z|C8GLMjPH7dbe+W?){o?WzD748Qp&kbN`mC348qa__r_B|F&AJds~99kSj9e&V_GzaCYpfvaTyQ~&ez z32dn14y-5dKEL;-al)M16f~)V`jdO_e|h)Y@fXje&GVN&XGhW5pmk~Y{+B;DvO5%& Vp-9kx)6iAN5cDRYH=&V@0RSu0fKUJc literal 0 HcmV?d00001 diff --git a/tests/io/data/subject1_bet.nii.gz b/tests/io/data/subject1_bet.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..f34b35c1d201cfae247bc3e4857f17eaee53a5f4 GIT binary patch literal 7149 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 P=cU0(r*}}F+er8i>K-AX literal 0 HcmV?d00001 diff --git a/tests/io/test_io.py b/tests/io/test_io.py new file mode 100644 index 000000000..9f72550f6 --- /dev/null +++ b/tests/io/test_io.py @@ -0,0 +1,106 @@ +# Copyright 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Iterable, Sequence + +import nibabel as nib +import numpy as np +import pytest + +from brainiak import io + + +@pytest.fixture +def in_dir() -> Path: + return Path(__file__).parent / "data" + + +@pytest.fixture +def expected_image_data_shape() -> Sequence[int]: + return (64, 64, 26, 10) + + +@pytest.fixture +def mask_path(in_dir: Path) -> Path: + return in_dir / "mask.nii.gz" + + +@pytest.fixture +def labels_path(in_dir: Path) -> Path: + return in_dir / "epoch_labels.npy" + + +@pytest.fixture +def expected_condition_spec_shape() -> Sequence[int]: + return (2, 2, 10) + + +@pytest.fixture +def expected_n_subjects() -> int: + return 2 + + +@pytest.fixture +def image_paths(in_dir: Path) -> Iterable[Path]: + return (in_dir / "subject1_bet.nii.gz", in_dir / "subject2_bet.nii.gz") + + +def test_load_images_from_dir_data_shape( + in_dir: Path, + expected_image_data_shape: Sequence[int], + expected_n_subjects: int + ) -> None: + for i, image in enumerate(io.load_images_from_dir(in_dir, "bet.nii.gz")): + assert image.get_data().shape == (64, 64, 26, 10) + assert i + 1 == expected_n_subjects + + +def test_load_images_data_shape( + image_paths: Iterable[Path], + expected_image_data_shape: Sequence[int], + expected_n_subjects: int + ) -> None: + for i, image in enumerate(io.load_images(image_paths)): + assert image.get_data().shape == (64, 64, 26, 10) + assert i + 1 == expected_n_subjects + + +def test_load_boolean_mask(mask_path: Path) -> None: + mask = io.load_boolean_mask(mask_path) + assert mask.dtype == np.bool + + +def test_load_boolean_mask_predicate(mask_path: Path) -> None: + mask = io.load_boolean_mask(mask_path, lambda x: np.logical_not(x)) + expected_mask = np.logical_not(io.load_boolean_mask(mask_path)) + assert np.array_equal(mask, expected_mask) + + +def test_load_labels(labels_path: Path, + expected_condition_spec_shape: Sequence[int], + expected_n_subjects: int) -> None: + condition_specs = io.load_labels(labels_path) + i = 0 + for condition_spec in condition_specs: + assert condition_spec.shape == expected_condition_spec_shape + i += 1 + assert i == expected_n_subjects + + +def test_save_as_nifti_file(tmpdir) -> None: + out_file = str(tmpdir / "nifti.nii") + shape = (4, 4, 4) + io.save_as_nifti_file(np.ones(shape), np.eye(4), out_file) + assert nib.load(out_file).get_data().shape == shape diff --git a/tests/isc/test_isc.py b/tests/isc/test_isc.py new file mode 100644 index 000000000..fc4ea17c1 --- /dev/null +++ b/tests/isc/test_isc.py @@ -0,0 +1,956 @@ +import numpy as np +import logging +import pytest +from brainiak.isc import (isc, isfc, bootstrap_isc, permutation_isc, + squareform_isfc, timeshift_isc, + phaseshift_isc) +from scipy.spatial.distance import squareform + +logger = logging.getLogger(__name__) + + +# Create simple simulated data with high intersubject correlation +def simulated_timeseries(n_subjects, n_TRs, n_voxels=30, + noise=1, data_type='array', + random_state=None): + prng = np.random.RandomState(random_state) + if n_voxels: + signal = prng.randn(n_TRs, n_voxels) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs, n_voxels) * noise + for subject in np.arange(n_subjects)] + elif not n_voxels: + signal = prng.randn(n_TRs) + prng = np.random.RandomState(prng.randint(0, 2**32 - 1)) + data = [signal + prng.randn(n_TRs) * noise + for subject in np.arange(n_subjects)] + if data_type == 'array': + if n_voxels: + data = np.dstack(data) + elif not n_voxels: + data = np.column_stack(data) + return data + + +# Create 3 voxel simulated data with correlated time series +def correlated_timeseries(n_subjects, n_TRs, noise=0, + random_state=None): + prng = np.random.RandomState(random_state) + signal = prng.randn(n_TRs) + correlated = True + while correlated: + uncorrelated = np.random.randn(n_TRs, + n_subjects)[:, np.newaxis, :] + unc_max = np.amax(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + unc_mean = np.mean(squareform(np.corrcoef( + uncorrelated[:, 0, :].T), checks=False)) + if unc_max < .3 and np.abs(unc_mean) < .001: + correlated = False + data = np.repeat(np.column_stack((signal, signal))[..., np.newaxis], + 20, axis=2) + data = np.concatenate((data, uncorrelated), axis=1) + data = data + np.random.randn(n_TRs, 3, n_subjects) * noise + return data + + +# Compute ISCs using different input types +# List of subjects with one voxel/ROI +def test_isc_input(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC inputs") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with one voxel/ROI + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=None, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + # List of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='list', + random_state=random_state) + iscs_list = isc(data, pairwise=False, summary_statistic=None) + + # Array of subjects with multiple voxels/ROIs + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs_array = isc(data, pairwise=False, summary_statistic=None) + + # Check they're the same + assert np.array_equal(iscs_list, iscs_array) + + logger.info("Finished testing ISC inputs") + + +# Check pairwise and leave-one-out, and summary statistics for ISC +def test_isc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs_loo = isc(data, pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_subjects, n_voxels) + + # Just two subjects + iscs_loo = isc(data[..., :2], pairwise=False, summary_statistic=None) + assert iscs_loo.shape == (n_voxels,) + + iscs_pw = isc(data, pairwise=True, summary_statistic=None) + assert iscs_pw.shape == (n_subjects*(n_subjects-1)/2, n_voxels) + + # Check summary statistics + isc_mean = isc(data, pairwise=False, summary_statistic='mean') + assert isc_mean.shape == (n_voxels,) + + isc_median = isc(data, pairwise=False, summary_statistic='median') + assert isc_median.shape == (n_voxels,) + + with pytest.raises(ValueError): + isc(data, pairwise=False, summary_statistic='min') + + logger.info("Finished testing ISC options") + + +# Make sure ISC recovers correlations of 1 and less than 1 +def test_isc_output(): + + logger.info("Testing ISC outputs") + + data = correlated_timeseries(20, 60, noise=0, + random_state=42) + iscs = isc(data, pairwise=False) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) + assert np.all(iscs[:, -1] < 1.) + + iscs = isc(data, pairwise=True) + assert np.allclose(iscs[:, :2], 1., rtol=1e-05) + assert np.all(iscs[:, -1] < 1.) + + logger.info("Finished testing ISC outputs") + + +# Check for proper handling of NaNs in ISC +def test_isc_nans(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Inject NaNs into data + data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + iscs_loo = isc(data, pairwise=False, tolerate_nans=False) + assert np.sum(np.isnan(iscs_loo)) == n_subjects + + # Tolerate all NaNs, only subject with NaNs yields NaN + iscs_loo = isc(data, pairwise=False, tolerate_nans=True) + assert np.sum(np.isnan(iscs_loo)) == 1 + + # Pairwise approach shouldn't care + iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) + iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) + assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(iscs_pw_T)) == + np.sum(np.isnan(iscs_pw_F)) == + n_subjects - 1) + + # Set proportion of nans to reject (70% and 90% non-NaN) + data[0, 0, :] = np.nan + data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan + data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan + + iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) + iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) + iscs_loo_95 = isc(data, pairwise=False, tolerate_nans=.95) + iscs_loo_90 = isc(data, pairwise=False, tolerate_nans=.90) + iscs_loo_80 = isc(data, pairwise=False, tolerate_nans=.8) + iscs_loo_70 = isc(data, pairwise=False, tolerate_nans=.7) + iscs_loo_60 = isc(data, pairwise=False, tolerate_nans=.6) + + assert (np.sum(np.isnan(iscs_loo_F)) == + np.sum(np.isnan(iscs_loo_95)) == 60) + assert (np.sum(np.isnan(iscs_loo_80)) == + np.sum(np.isnan(iscs_loo_90)) == 42) + assert (np.sum(np.isnan(iscs_loo_T)) == + np.sum(np.isnan(iscs_loo_60)) == + np.sum(np.isnan(iscs_loo_70)) == 28) + assert np.array_equal(np.sum(np.isnan(iscs_loo_F), axis=0), + np.sum(np.isnan(iscs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(iscs_loo_80), axis=0), + np.sum(np.isnan(iscs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(iscs_loo_T), axis=0), + np.sum(np.isnan(iscs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(iscs_loo_T), axis=0), + np.sum(np.isnan(iscs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(iscs_loo_60), axis=0), + np.sum(np.isnan(iscs_loo_70), axis=0)))) + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Make sure voxel with NaNs across all subjects is always removed + data[0, 0, :] = np.nan + iscs_loo_T = isc(data, pairwise=False, tolerate_nans=True) + iscs_loo_F = isc(data, pairwise=False, tolerate_nans=False) + assert np.allclose(iscs_loo_T, iscs_loo_F, equal_nan=True) + assert (np.sum(np.isnan(iscs_loo_T)) == + np.sum(np.isnan(iscs_loo_F)) == + n_subjects) + + iscs_pw_T = isc(data, pairwise=True, tolerate_nans=True) + iscs_pw_F = isc(data, pairwise=True, tolerate_nans=False) + assert np.allclose(iscs_pw_T, iscs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(iscs_pw_T)) == + np.sum(np.isnan(iscs_pw_F)) == + n_subjects * (n_subjects - 1) / 2) + + +# Test one-sample bootstrap test +def test_bootstrap_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + n_bootstraps = 10 + + logger.info("Testing bootstrap hypothesis test") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + iscs = isc(data, pairwise=False, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Test one-sample bootstrap test with pairwise approach + n_bootstraps = 10 + + iscs = isc(data, pairwise=True, summary_statistic=None) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95) + assert distribution.shape == (n_bootstraps, n_voxels) + + # Check random seeds + iscs = isc(data, pairwise=False, summary_statistic=None) + distributions = [] + for random_state in [42, 42, None]: + observed, ci, p, distribution = bootstrap_isc( + iscs, pairwise=False, + summary_statistic='median', + n_bootstraps=n_bootstraps, + ci_percentile=95, + random_state=random_state) + distributions.append(distribution) + assert np.array_equal(distributions[0], distributions[1]) + assert not np.array_equal(distributions[1], distributions[2]) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=False) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=False, + summary_statistic='median')) + + # Check that ISC computation and bootstrap observed are same + iscs = isc(data, pairwise=True) + observed, ci, p, distribution = bootstrap_isc(iscs, pairwise=True, + summary_statistic='median') + assert np.array_equal(observed, isc(data, pairwise=True, + summary_statistic='median')) + + logger.info("Finished testing bootstrap hypothesis test") + + +# Test permutation test with group assignments +def test_permutation_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + group_assignment = [1] * 10 + [2] * 10 + + logger.info("Testing permutation test") + + # Create dataset with two groups in pairwise approach + data = np.dstack((simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=5, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=200) + + # Create data with two groups in leave-one-out approach + data_1 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(10, n_TRs, n_voxels=n_voxels, + noise=10, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=200) + + # One-sample leave-one-out permutation test + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=False, + summary_statistic='median', + n_permutations=200) + + # One-sample pairwise permutation test + iscs = isc(data, pairwise=True, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, + pairwise=True, + summary_statistic='median', + n_permutations=200) + + # Small one-sample pairwise exact test + data = simulated_timeseries(12, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + iscs = isc(data, pairwise=False, summary_statistic=None) + + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median', + n_permutations=10000) + + # Small two-sample pairwise exact test (and unequal groups) + data = np.dstack((simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3), + simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4))) + iscs = isc(data, pairwise=True, summary_statistic=None) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=True, + summary_statistic='mean', + n_permutations=10000) + + # Small two-sample leave-one-out exact test (and unequal groups) + data_1 = simulated_timeseries(3, n_TRs, n_voxels=n_voxels, + noise=1, data_type='array', + random_state=3) + data_2 = simulated_timeseries(4, n_TRs, n_voxels=n_voxels, + noise=50, data_type='array', + random_state=4) + iscs = np.vstack((isc(data_1, pairwise=False, summary_statistic=None), + isc(data_2, pairwise=False, summary_statistic=None))) + group_assignment = [1, 1, 1, 2, 2, 2, 2] + + observed, p, distribution = permutation_isc( + iscs, + group_assignment=group_assignment, + pairwise=False, + summary_statistic='mean', + n_permutations=10000) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = permutation_isc(iscs, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = permutation_isc(iscs, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing permutaton test") + + +def test_timeshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing circular time-shift") + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, pairwise + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Circular time-shift on one sample, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = timeshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = timeshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing circular time-shift") + + +# Phase randomization test +def test_phaseshift_isc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing phase randomization") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='median', + n_shifts=200) + + # Phase randomization one-sample test, leave-one-out + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='mean', + n_shifts=200) + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True) + assert np.all(iscs[:, :2] > .5) + assert np.all(iscs[:, -1] < .5) + assert p[0] < .05 and p[1] < .05 + assert p[2] > .01 + + # Check that ISC computation and permutation observed are same + iscs = isc(data, pairwise=False) + observed, p, distribution = phaseshift_isc(data, pairwise=False, + summary_statistic='median') + assert np.allclose(observed, isc(data, pairwise=False, + summary_statistic='median'), + rtol=1e-03) + + # Check that ISC computation and permuation observed are same + iscs = isc(data, pairwise=True) + observed, p, distribution = phaseshift_isc(data, pairwise=True, + summary_statistic='mean') + assert np.allclose(observed, isc(data, pairwise=True, + summary_statistic='mean'), + rtol=1e-03) + + logger.info("Finished testing phase randomization") + + +# Test ISFC +def test_isfc_options(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + + logger.info("Testing ISFC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + isfcs, iscs = isfc(data, pairwise=False, summary_statistic=None) + assert isfcs.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) + assert iscs.shape == (n_subjects, n_voxels) + + # Without vectorized upper triangle + isfcs = isfc(data, pairwise=False, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_voxels) + + # Just two subjects + isfcs, iscs = isfc(data[..., :2], pairwise=False, summary_statistic=None) + assert isfcs.shape == (n_voxels * (n_voxels - 1) / 2,) + assert iscs.shape == (n_voxels,) + + isfcs = isfc(data[..., :2], pairwise=False, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_voxels, n_voxels) + + # ISFC with pairwise approach + isfcs, iscs = isfc(data, pairwise=True, summary_statistic=None) + assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels * (n_voxels - 1) / 2) + assert iscs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels) + + isfcs = isfc(data, pairwise=True, summary_statistic=None, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects * (n_subjects - 1) / 2, + n_voxels, n_voxels) + + # ISFC with summary statistics + isfcs, iscs = isfc(data, pairwise=True, summary_statistic='mean') + isfcs, iscs = isfc(data, pairwise=True, summary_statistic='median') + + # Check output p-values + data = correlated_timeseries(20, 60, noise=.5, + random_state=42) + isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) + assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) + assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) + + isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) + assert np.all(isfcs[:, 0, 1] > .5) and np.all(isfcs[:, 1, 0] > .5) + assert np.all(isfcs[:, :2, 2] < .5) and np.all(isfcs[:, 2, :2] < .5) + + # Check that ISC and ISFC diagonal are identical + iscs = isc(data, pairwise=False) + isfcs = isfc(data, pairwise=False, vectorize_isfcs=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) + isfcs, iscs_v = isfc(data, pairwise=False) + assert np.allclose(iscs, iscs_v, rtol=1e-03) + + # Check that ISC and ISFC diagonal are identical (pairwise) + iscs = isc(data, pairwise=True) + isfcs = isfc(data, pairwise=True, vectorize_isfcs=False) + for s in np.arange(len(iscs)): + assert np.allclose(isfcs[s, ...].diagonal(), iscs[s, :], rtol=1e-03) + isfcs, iscs_v = isfc(data, pairwise=True) + assert np.allclose(iscs, iscs_v, rtol=1e-03) + + # Generate 'targets' data and use for ISFC + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array') + n_targets = 15 + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Ensure 'square' output enforced + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=True) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Check list input for targets + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='list') + isfcs = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + # Check that mismatching subjects / TRs breaks targets + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + + with pytest.raises(ValueError): + isfcs = isfc(data, targets=targets_data[..., :-1], + pairwise=False, vectorize_isfcs=False) + assert isfcs.shape == (n_subjects, n_voxels, n_targets) + + with pytest.raises(ValueError): + isfcs = isfc(data, targets=targets_data[:-1, ...], + pairwise=False, vectorize_isfcs=False) + + # Check targets for only 2 subjects + isfcs = isfc(data[..., :2], targets=targets_data[..., :2], + pairwise=False, summary_statistic=None) + assert isfcs.shape == (2, n_voxels, n_targets) + + isfcs = isfc(data[..., :2], targets=targets_data[..., :2], + pairwise=True, summary_statistic=None) + assert isfcs.shape == (2, n_voxels, n_targets) + + # Check that supplying targets enforces leave-one-out + isfcs_pw = isfc(data, targets=targets_data, pairwise=True, + vectorize_isfcs=False, tolerate_nans=False) + assert isfcs_pw.shape == (n_subjects, n_voxels, n_targets) + + logger.info("Finished testing ISFC options") + + +# Check for proper handling of NaNs in ISFC +def test_isfc_nans(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Inject NaNs into data + data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels * 2 - 1) + + # With vectorized ISFCs + isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == n_subjects * (n_voxels - 1) + + # Tolerate all NaNs, only subject with NaNs yields NaN + isfcs_loo = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == n_voxels * 2 - 1 + + isfcs_loo, iscs_loo = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == n_voxels - 1 + + # Pairwise approach shouldn't care + isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_F)) == + (n_voxels * 2 - 1) * (n_subjects - 1)) + + isfcs_pw_T, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, + tolerate_nans=True) + isfcs_pw_F, iscs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=True, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_F)) == + (n_voxels - 1) * (n_subjects - 1)) + + # Set proportion of nans to reject (70% and 90% non-NaN) + data[0, 0, :] = np.nan + data[0, 1, :n_subjects - int(n_subjects * .7)] = np.nan + data[0, 2, :n_subjects - int(n_subjects * .9)] = np.nan + + isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + isfcs_loo_95 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.95) + isfcs_loo_90 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.90) + isfcs_loo_80 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.8) + isfcs_loo_70 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.7) + isfcs_loo_60 = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=.6) + assert (np.sum(np.isnan(isfcs_loo_F)) == + np.sum(np.isnan(isfcs_loo_95)) == 3420) + assert (np.sum(np.isnan(isfcs_loo_80)) == + np.sum(np.isnan(isfcs_loo_90)) == 2430) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_60)) == + np.sum(np.isnan(isfcs_loo_70)) == 1632) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), + np.sum(np.isnan(isfcs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), + np.sum(np.isnan(isfcs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_60), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)))) + + isfcs_loo_T, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=True) + isfcs_loo_F, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=False) + isfcs_loo_95, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.95) + isfcs_loo_90, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.90) + isfcs_loo_80, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.8) + isfcs_loo_70, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.7) + isfcs_loo_60, _ = isfc(data, pairwise=False, vectorize_isfcs=True, + tolerate_nans=.6) + assert (np.sum(np.isnan(isfcs_loo_F)) == + np.sum(np.isnan(isfcs_loo_95)) == 1680) + assert (np.sum(np.isnan(isfcs_loo_80)) == + np.sum(np.isnan(isfcs_loo_90)) == 1194) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_60)) == + np.sum(np.isnan(isfcs_loo_70)) == 802) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_F), axis=0), + np.sum(np.isnan(isfcs_loo_95), axis=0)) + assert np.array_equal(np.sum(np.isnan(isfcs_loo_80), axis=0), + np.sum(np.isnan(isfcs_loo_90), axis=0)) + assert np.all((np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_60), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_T), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)), + np.array_equal( + np.sum(np.isnan(isfcs_loo_60), axis=0), + np.sum(np.isnan(isfcs_loo_70), axis=0)))) + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Make sure voxel with NaNs across all subjects is always removed + data[0, 0, :] = np.nan + isfcs_loo_T = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_loo_F = isfc(data, pairwise=False, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_loo_T, isfcs_loo_F, equal_nan=True) + assert (np.sum(np.isnan(isfcs_loo_T)) == + np.sum(np.isnan(isfcs_loo_F)) == + 1180) + + isfcs_pw_T = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=True) + isfcs_pw_F = isfc(data, pairwise=True, vectorize_isfcs=False, + tolerate_nans=False) + assert np.allclose(isfcs_pw_T, isfcs_pw_F, equal_nan=True) + + assert (np.sum(np.isnan(isfcs_pw_T)) == + np.sum(np.isnan(isfcs_pw_T)) == + 11210) + + # Check for NaN-handling in targets + n_targets = 15 + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + targets_data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_targets, + data_type='array') + + # Inject NaNs into targets_data + targets_data[0, 0, 0] = np.nan + + # Don't tolerate NaNs, should lose zeroeth voxel + isfcs_loo = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False, tolerate_nans=False) + assert np.sum(np.isnan(isfcs_loo)) == (n_subjects - 1) * (n_targets * 2) + + # Single NaN in targets will get averaged out with tolerate + isfcs_loo = isfc(data, targets=targets_data, pairwise=False, + vectorize_isfcs=False, tolerate_nans=True) + assert np.sum(np.isnan(isfcs_loo)) == 0 + + +def test_squareform_isfc(): + + # Set parameters for toy time series data + n_subjects = 20 + n_TRs = 60 + n_voxels = 30 + random_state = 42 + + logger.info("Testing ISC options") + + data = simulated_timeseries(n_subjects, n_TRs, + n_voxels=n_voxels, data_type='array', + random_state=random_state) + + # Generate square redundant ISFCs + isfcs_r = isfc(data, vectorize_isfcs=False) + assert isfcs_r.shape == (n_subjects, n_voxels, n_voxels) + + # Squareform these into condensed ISFCs and ISCs + isfcs_c, iscs_c = squareform_isfc(isfcs_r) + assert isfcs_c.shape == (n_subjects, n_voxels * (n_voxels - 1) / 2) + assert iscs_c.shape == (n_subjects, n_voxels) + + # Go back the other way and check it's the same + isfcs_new = squareform_isfc(isfcs_c, iscs_c) + assert np.array_equal(isfcs_r, isfcs_new) + + # Check against ISC function + assert np.allclose(isc(data), iscs_c, rtol=1e-03) + + # Check for two subjects + isfcs_r = isfc(data[..., :2], vectorize_isfcs=False) + assert isfcs_r.shape == (n_voxels, n_voxels) + isfcs_c, iscs_c = squareform_isfc(isfcs_r) + assert isfcs_c.shape == (n_voxels * (n_voxels - 1) / 2,) + assert iscs_c.shape == (n_voxels,) + assert np.array_equal(isfcs_r, squareform_isfc(isfcs_c, iscs_c)) + + +if __name__ == '__main__': + test_isc_input() + test_isc_options() + test_isc_output() + test_isc_nans() + test_bootstrap_isc() + test_permutation_isc() + test_timeshift_isc() + test_phaseshift_isc() + test_isfc_options() + test_isfc_nans() + test_squareform_isfc() + logger.info("Finished all ISC tests") diff --git a/tests/reprsimil/example_design.1D b/tests/reprsimil/example_design.1D new file mode 100755 index 000000000..7385edea6 --- /dev/null +++ b/tests/reprsimil/example_design.1D @@ -0,0 +1,272 @@ +# + 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 + 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 + 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 + 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 + 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 + 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 + 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 + 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 + 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 + 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 + 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 + 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 + 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 + 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 + 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 + 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 + 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 + 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 + 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 + 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 + 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 + 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 + 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 + 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 + 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 + 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 + 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 + 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 + 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 + 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 + 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 + 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 + 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 + 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 + 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 + 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 + 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 + 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 + 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 + 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 + 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 + 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 + 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 + 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 + 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 + 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 + 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 + 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 + 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 + 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 + 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 + 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 + 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 + 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 + 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 + 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 + 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 + 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 + 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 + 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 + 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 + 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 + 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 + 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 + 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 + 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 + 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 + 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 + 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 + 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 + 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 + 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 + 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 + 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 + 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 + 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 + 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 + 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 + 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 + 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 + 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 + 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 + 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 + 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 + 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 + 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 + 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 + 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 + 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 + 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 + 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 + 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 + 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 + 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 + 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 + 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 + 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 + 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 + 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 + 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 + 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 + 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 + 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 + 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 + 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 + 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 + 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 + 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 + 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 + 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 + 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 + 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 + 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 + 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 + 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 + 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 + 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 + 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 + 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 + 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 + 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 + 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 + 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 + 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 + 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 + 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 + 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 + 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 + 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 + 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 + 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 + 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 + 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 + 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 + 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 + 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 + 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 + 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 + 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 + 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 + 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 + 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 + 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 + 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 + 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 + 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 + 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 + 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 + 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 + 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 + 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 + 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 + 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 + 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 + 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 + 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 + 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 + 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 + 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 + 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 + 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 + 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 + 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 + 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 + 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 + 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 + 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 + 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 + 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 + 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 + 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 + 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 + 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 + 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 + 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 + 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 + 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 + 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 + 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 + 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 + 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 + 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 + 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 + 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 + 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 + 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 +# + diff --git a/tests/reprsimil/test_brsa.py b/tests/reprsimil/test_brsa.py new file mode 100755 index 000000000..fbf898b86 --- /dev/null +++ b/tests/reprsimil/test_brsa.py @@ -0,0 +1,651 @@ +# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, +# Princeton University +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + + +def test_can_instantiate(): + import brainiak.reprsimil.brsa + s = brainiak.reprsimil.brsa.BRSA() + assert s, "Invalid BRSA instance!" + + s = brainiak.reprsimil.brsa.BRSA( + n_iter=50, rank=5, auto_nuisance=False, n_nureg=2, nureg_method='ICA', + baseline_single=False, init_iter=5, GP_space=True, GP_inten=True, + tol=2e-3, eta=0.001, space_smooth_range=10.0, inten_smooth_range=100.0, + tau_range=2.0, + tau2_prior=brainiak.reprsimil.brsa.prior_GP_var_inv_gamma, + optimizer='CG', random_state=100, anneal_speed=20) + assert s, "Invalid BRSA instance!" + + +def test_fit(): + from brainiak.reprsimil.brsa import BRSA + import brainiak.utils.utils as utils + import scipy.stats + import numpy as np + import os.path + np.random.seed(10) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 2 times, mimicking 2 runs of itenditcal timing + n_run = 2 + design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) + design.n_TR = design.n_TR * n_run + + # start simulating some data + n_V = 50 + n_C = np.size(design.design_task, axis=1) + n_T = design.n_TR + + noise_bot = 0.5 + noise_top = 5.0 + noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot + + # generating noise + noise = np.zeros([n_T, n_V]) + noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ + np.random.randn(n_V) * noise_level + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_level = 5.0 # test with high SNR + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends + # not only on beta but also on x. + inten = np.random.rand(n_V) * 20.0 + + # parameters of Gaussian process to generate pseuso SNR + tau = 1.0 + smooth_width = 5.0 + inten_kernel = 1.0 + + coords = np.arange(0, n_V)[:, None] + + dist2 = np.square(coords - coords.T) + + inten_tile = np.tile(inten, [n_V, 1]) + inten_diff2 = (inten_tile - inten_tile.T)**2 + + K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / + inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 + + L = np.linalg.cholesky(K) + snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level + sqrt_v = noise_level * snr + betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v + signal = np.dot(design.design_task, betas_simulated) + + # Adding noise to signal as data + Y = signal + noise + inten + + scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) + + # Test fitting with GP prior. + brsa = BRSA(GP_space=True, GP_inten=True, n_iter=5, + init_iter=10, auto_nuisance=False, tol=2e-3) + + # We also test that it can detect baseline regressor included in the + # design matrix for task conditions + wrong_design = np.insert(design.design_task, 0, 1, axis=1) + with pytest.raises(ValueError) as excinfo: + brsa.fit(X=Y, design=wrong_design, scan_onsets=scan_onsets, + coords=coords, inten=inten) + assert ('Your design matrix appears to have included baseline time series.' + in str(excinfo.value)) + # Now we fit with the correct design matrix. + brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets, + coords=coords, inten=inten) + + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], + u_i[np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + + noise_new = np.zeros([n_T, n_V]) + noise_new[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise_new[i_t, :] = noise_new[i_t - 1, :] * \ + rho1 + np.random.randn(n_V) * noise_level + + Y_new = signal + noise_new + inten + ts, ts0 = brsa.transform(Y_new, scan_onsets=scan_onsets) + p = scipy.stats.pearsonr(ts[:, 0], design.design_task[:, 0])[1] + assert p < 0.01, ( + "Recovered time series does not correlate with true time series!") + assert np.shape(ts) == (n_T, n_C) and np.shape(ts0) == (n_T, 1), ( + "Wrong shape in returned time series by transform function!") + + [score, score_null] = brsa.score( + X=Y_new, design=design.design_task, scan_onsets=scan_onsets) + assert score > score_null, ( + "Full model does not win over null model on data containing signal") + + [score, score_null] = brsa.score(X=noise_new + inten, + design=design.design_task, + scan_onsets=scan_onsets) + assert score < score_null, ( + "Null model does not win over full model on data without signal") + + # Test fitting with lower rank, nuisance regressors and without GP prior + rank = n_C - 1 + n_nureg = 1 + brsa = BRSA(rank=rank, n_nureg=n_nureg, tol=2e-3, + n_iter=8, init_iter=4, auto_nuisance=True) + brsa.fit(X=Y, design=design.design_task, scan_onsets=scan_onsets) + # u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ + np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + + assert (not hasattr(brsa, 'bGP_') + and not hasattr(brsa, 'lGPspace_') + and not hasattr(brsa, 'lGPinten_') + ), ("the BRSA object should not have parameters of GP if GP is " + "not requested.") + # GP parameters are not set if not requested + assert brsa.beta0_.shape[0] == n_nureg + 1, 'Shape of beta0 incorrect' + p = scipy.stats.pearsonr(brsa.beta0_[0, :], inten)[1] + assert p < 0.01, ( + 'recovered beta0 does not correlate with the baseline of voxels.') + assert np.shape(brsa.L_) == ( + n_C, rank), 'Cholesky factor should have shape of (n_C, rank)' + + # Test fitting with GP over just spatial coordinates. + brsa = BRSA(GP_space=True, baseline_single=False, + tol=2e-3, n_iter=4, init_iter=4) + brsa.fit(X=Y, design=design.design_task, + scan_onsets=scan_onsets, coords=coords) + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = brsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], u_i[ + np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(brsa.nSNR_, snr)[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + assert np.isclose(np.mean(np.log(brsa.nSNR_)), 0), "nSNR_ not normalized!" + p = scipy.stats.pearsonr(brsa.sigma_, noise_level)[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(brsa.rho_, rho1)[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + assert not hasattr(brsa, 'lGPinten_'), ( + "the BRSA object should not have parameters of lGPinten_ if only " + "smoothness in space is requested.") + # GP parameters are not set if not requested + + +def test_gradient(): + from brainiak.reprsimil.brsa import BRSA + import brainiak.utils.utils as utils + import numpy as np + import os.path + import numdifftools as nd + + np.random.seed(100) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + n_run = 4 + # concatenate it by 4 times, mimicking 4 runs of itenditcal timing + design.design_task = np.tile(design.design_task[:, :-1], [n_run, 1]) + design.n_TR = design.n_TR * n_run + + # start simulating some data + n_V = 30 + n_C = np.size(design.design_task, axis=1) + n_T = design.n_TR + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = np.random.rand(n_V) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = np.random.rand(n_V) * (rho1_top - rho1_bot) + rho1_bot + + # generating noise + noise = np.zeros([n_T, n_V]) + noise[0, :] = np.random.randn(n_V) * noise_level / np.sqrt(1 - rho1**2) + for i_t in range(1, n_T): + noise[i_t, :] = noise[i_t - 1, :] * rho1 + \ + np.random.randn(n_V) * noise_level + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0, 0] = 0.2 + ideal_cov[5:9, 5:9] = 0.6 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_level = 5.0 # test with high SNR + inten = np.random.randn(n_V) * 20.0 + + # parameters of Gaussian process to generate pseuso SNR + tau = 0.8 + smooth_width = 5.0 + inten_kernel = 1.0 + + coords = np.arange(0, n_V)[:, None] + + dist2 = np.square(coords - coords.T) + + inten_tile = np.tile(inten, [n_V, 1]) + inten_diff2 = (inten_tile - inten_tile.T)**2 + + K = np.exp(-dist2 / smooth_width**2 / 2.0 - inten_diff2 / + inten_kernel**2 / 2.0) * tau**2 + np.eye(n_V) * tau**2 * 0.001 + + L = np.linalg.cholesky(K) + snr = np.exp(np.dot(L, np.random.randn(n_V))) * snr_level + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + sqrt_v = noise_level * snr + betas_simulated = np.dot(L_full, np.random.randn(n_C, n_V)) * sqrt_v + signal = np.dot(design.design_task, betas_simulated) + + # Adding noise to signal as data + Y = signal + noise + + scan_onsets = np.linspace(0, design.n_TR, num=n_run + 1) + + # Test fitting with GP prior. + brsa = BRSA(GP_space=True, GP_inten=True, rank=n_C) + + # Additionally, we test the generation of re-used terms. + X0 = np.ones(n_T)[:, None] + D, F, run_TRs, n_run_returned = brsa._prepare_DF( + n_T, scan_onsets=scan_onsets) + assert np.shape(D) == (n_T, n_T), 'D has wrong shape' + assert np.shape(F) == (n_T, n_T), 'F has wrong shape' + assert np.sum(D) == (n_T - n_run) * 2, 'D is initialized incorrectly.' + assert np.sum(F) == n_T - n_run * 2, 'F is initialized incorrectly.' + assert n_run_returned == n_run, ( + 'There is mistake in counting number of runs') + assert np.sum(run_TRs) == n_T, ( + 'The segmentation of the total experiment duration is wrong') + (XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX + ) = brsa._prepare_data_XY(design.design_task, Y, D, F) + (X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, X_base, + n_X0, idx_DC + ) = brsa._prepare_data_XYX0(design.design_task, Y, X0, + np.random.randn(n_T)[:, None], D, F, run_TRs, + no_DC=False) + assert (np.shape(XTY) == (n_C, n_V) + and np.shape(XTDY) == (n_C, n_V) + and np.shape(XTFY) == (n_C, n_V) + ), 'Dimension of XTY etc. returned from _prepare_data is wrong' + assert (np.ndim(YTY_diag) == 1 + and np.ndim(YTDY_diag) == 1 + and np.ndim(YTFY_diag) == 1 + ), ("Dimension of YTY_diag etc. returned from _prepare_data is " + "wrong") + assert (np.ndim(XTX) == 2 + and np.ndim(XTDX) == 2 + and np.ndim(XTFX) == 2 + ), 'Dimension of XTX etc. returned from _prepare_data is wrong' + assert (np.ndim(X0TX0) == 2 + and np.ndim(X0TDX0) == 2 + and np.ndim(X0TFX0) == 2 + ), 'Dimension of X0TX0 etc. returned from _prepare_data is wrong' + assert (np.ndim(XTX0) == 2 + and np.ndim(XTDX0) == 2 + and np.ndim(XTFX0) == 2 + ), 'Dimension of XTX0 etc. returned from _prepare_data is wrong' + assert (np.ndim(X0TY) == 2 + and np.ndim(X0TDY) == 2 + and np.ndim(X0TFY) == 2 + ), 'Dimension of X0TY etc. returned from _prepare_data is wrong' + assert (np.shape(X0) == (n_T, n_X0) + and np.shape(X_base) == (n_T, np.size(idx_DC)) + and np.max(idx_DC) < n_X0 + and np.size(idx_DC) + 1 == n_X0 + ), ("Dimension of X0 or X_base, or n_X0 or indices of DC " + "components are wrong.") + l_idx = np.tril_indices(n_C) + n_l = np.size(l_idx[0]) + + # Make sure all the fields are in the indices. + idx_param_sing, idx_param_fitU, idx_param_fitV = brsa._build_index_param( + n_l, n_V, 2) + assert 'Cholesky' in idx_param_sing and 'a1' in idx_param_sing, \ + 'The dictionary for parameter indexing misses some keys' + assert 'Cholesky' in idx_param_fitU and 'a1' in idx_param_fitU, \ + 'The dictionary for parameter indexing misses some keys' + assert 'log_SNR2' in idx_param_fitV and 'c_space' in idx_param_fitV \ + and 'c_inten' in idx_param_fitV and 'c_both' in idx_param_fitV, \ + 'The dictionary for parameter indexing misses some keys' + + # Initial parameters are correct parameters with some perturbation + param0_fitU = np.random.randn(n_l + n_V) * 0.1 + param0_fitV = np.random.randn(n_V + 1) * 0.1 + param0_sing = np.random.randn(n_l + 1) * 0.1 + param0_sing[idx_param_sing['a1']] += np.mean(np.tan(rho1 * np.pi / 2)) + param0_fitV[idx_param_fitV['log_SNR2']] += np.log(snr[:n_V - 1]) * 2 + param0_fitV[idx_param_fitV['c_space']] += np.log(smooth_width) * 2 + param0_fitV[idx_param_fitV['c_inten']] += np.log(inten_kernel) * 2 + + # test if the gradients are correct + # log likelihood and derivative of the _singpara function + + ll0, deriv0 = brsa._loglike_AR1_singpara(param0_sing, XTX, XTDX, XTFX, + YTY_diag, YTDY_diag, YTFY_diag, + XTY, XTDY, XTFY, X0TX0, X0TDX0, + X0TFX0, XTX0, XTDX0, XTFX0, X0TY, + X0TDY, X0TFY, l_idx, n_C, n_T, + n_V, n_run, n_X0, idx_param_sing) + # We test the gradient to the Cholesky factor + vec = np.zeros(np.size(param0_sing)) + vec[idx_param_sing['Cholesky'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, + XTDX0, XTFX0, X0TY, X0TDY, X0TFY, + l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_sing)[0], + param0_sing, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of singpara wrt Cholesky is incorrect') + + # We test the gradient to a1 + vec = np.zeros(np.size(param0_sing)) + vec[idx_param_sing['a1']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_singpara(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, XTX0, + XTDX0, XTFX0, X0TY, X0TDY, X0TFY, + l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_sing)[0], + param0_sing, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), + rtol=1e-5), 'gradient of singpara wrt a1 is incorrect' + + # log likelihood and derivative of the fitU function. + ll0, deriv0 = brsa._loglike_AR1_diagV_fitU(param0_fitU, XTX, XTDX, XTFX, + YTY_diag, YTDY_diag, YTFY_diag, + XTY, XTDY, XTFY, X0TX0, X0TDX0, + X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, np.log(snr) + * 2, l_idx, n_C, n_T, n_V, + n_run, n_X0, idx_param_fitU, + n_C) + + # We test the gradient wrt the reparametrization of AR(1) coefficient of + # noise. + vec = np.zeros(np.size(param0_fitU)) + vec[idx_param_fitU['a1'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitU wrt to AR(1) coefficient incorrect') + + # We test if the numerical and analytical gradient wrt to the first + # element of Cholesky factor is correct + vec = np.zeros(np.size(param0_fitU)) + vec[idx_param_fitU['Cholesky'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitU wrt Cholesky factor incorrect') + + # Test on a random direction + vec = np.random.randn(np.size(param0_fitU)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitU(x, XTX, XTDX, XTFX, YTY_diag, + YTDY_diag, YTFY_diag, XTY, XTDY, + XTFY, X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, X0TY, X0TDY, + X0TFY, np.log(snr) * 2, l_idx, + n_C, n_T, n_V, n_run, n_X0, + idx_param_fitU, n_C)[0], + param0_fitU, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), + rtol=1e-5), 'gradient of fitU incorrect' + + # We test the gradient of _fitV wrt to log(SNR^2) assuming no GP prior. + X0TAX0, XTAX0, X0TAY, X0TAX0_i, \ + XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL = \ + brsa._precompute_ar1_quad_forms(XTY, XTDY, XTFY, + YTY_diag, YTDY_diag, YTFY_diag, + XTX, XTDX, XTFX, + X0TX0, X0TDX0, X0TFX0, + XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, + L_full, rho1, n_V, n_X0) + assert np.shape(XTAcorrX) == (n_V, n_C, n_C), ( + 'Dimension of XTAcorrX is wrong by _precompute_ar1_quad_forms()') + assert XTAcorrY.shape == XTY.shape, ( + 'Shape of XTAcorrY is wrong by _precompute_ar1_quad_forms()') + assert YTAcorrY.shape == YTY_diag.shape, ( + 'Shape of YTAcorrY is wrong by _precompute_ar1_quad_forms()') + assert np.shape(X0TAX0) == (n_V, n_X0, n_X0), ( + 'Dimension of X0TAX0 is wrong by _precompute_ar1_quad_forms()') + assert np.shape(XTAX0) == (n_V, n_C, n_X0), ( + 'Dimension of XTAX0 is wrong by _precompute_ar1_quad_forms()') + assert X0TAY.shape == X0TY.shape, ( + 'Shape of X0TAX0 is wrong by _precompute_ar1_quad_forms()') + assert np.all(np.isfinite(X0TAX0_i)), ( + 'Inverse of X0TAX0 includes NaN or Inf') + ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( + param0_fitV[idx_param_fitV['log_SNR2']], X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, + n_V, n_run, n_X0, idx_param_fitV, n_C, False, False) + vec = np.zeros(np.size(param0_fitV[idx_param_fitV['log_SNR2']])) + vec[idx_param_fitV['log_SNR2'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, + False, False)[0], + param0_fitV[idx_param_fitV['log_SNR2']], + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt log(SNR2) incorrect for model without GP') + + # We test the gradient of _fitV wrt to log(SNR^2) assuming GP prior. + ll0, deriv0 = brsa._loglike_AR1_diagV_fitV( + param0_fitV, X0TAX0, XTAX0, X0TAY, X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), l_idx, n_C, n_T, n_V, n_run, n_X0, + idx_param_fitV, n_C, True, True, dist2, inten_diff2, 100, 100) + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['log_SNR2'][0]] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV srt log(SNR2) incorrect for model with GP') + + # We test the graident wrt spatial length scale parameter of GP prior + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['c_space']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt spatial length scale of GP incorrect') + + # We test the graident wrt intensity length scale parameter of GP prior + vec = np.zeros(np.size(param0_fitV)) + vec[idx_param_fitV['c_inten']] = 1 + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV wrt intensity length scale of GP incorrect') + + # We test the graident on a random direction + vec = np.random.randn(np.size(param0_fitV)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: brsa._loglike_AR1_diagV_fitV(x, X0TAX0, XTAX0, X0TAY, + X0TAX0_i, XTAcorrX, XTAcorrY, + YTAcorrY, LTXTAcorrY, XTAcorrXL, + LTXTAcorrXL, L_full[l_idx], + np.tan(rho1 * np.pi / 2), + l_idx, n_C, n_T, n_V, n_run, + n_X0, idx_param_fitV, n_C, True, + True, dist2, inten_diff2, 100, + 100)[0], + param0_fitV, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), ( + 'gradient of fitV incorrect') + + +def test_nureg_determine(): + from brainiak.reprsimil.brsa import Ncomp_SVHT_MG_DLD_approx + import numpy as np + x = np.dot(np.random.randn(100, 5), np.random.randn(5, 40)) + \ + np.random.randn(100, 40) * 0.01 + ncomp = Ncomp_SVHT_MG_DLD_approx(x) + assert ncomp >= 3 and ncomp <= 8, ( + 'recovered number of components should be in a reasonable range') + + +def test_half_log_det(): + import numpy as np + from brainiak.reprsimil.brsa import BRSA + a = np.asarray([[1, 0.2], [0.2, 1]]) + brsa = BRSA() + half_log_det = np.log(np.linalg.det(a)) / 2 + assert np.isclose(half_log_det, brsa._half_log_det( + a)), 'half log determinant function is wrong' + + +def test_n_nureg(): + import brainiak.reprsimil.brsa + import numpy as np + # noise = np.random.randn(100,30) + noise = np.dot(np.random.randn(100, 8), np.random.randn( + 8, 30)) + np.random.randn(100, 30) * 0.01 + design = np.random.randn(100, 2) + s = brainiak.reprsimil.brsa.BRSA(n_iter=2) + s.fit(X=noise, design=design) + assert s.n_nureg_ > 2 and s.n_nureg_ < 16, ( + 'n_nureg_ estimation is wrong in BRSA') diff --git a/tests/reprsimil/test_gbrsa.py b/tests/reprsimil/test_gbrsa.py new file mode 100644 index 000000000..7038d35b4 --- /dev/null +++ b/tests/reprsimil/test_gbrsa.py @@ -0,0 +1,630 @@ +# Copyright 2016 Mingbo Cai, Princeton Neuroscience Instititute, +# Princeton University +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_can_instantiate(): + import brainiak.reprsimil.brsa + s = brainiak.reprsimil.brsa.GBRSA() + assert s, "Invalid GBRSA instance!" + + s = brainiak.reprsimil.brsa.GBRSA(n_iter=40, rank=4, auto_nuisance=False, + nureg_method='PCA', + baseline_single=False, logS_range=1.0, + SNR_bins=11, rho_bins=40, tol=2e-3, + optimizer='CG', random_state=0, + anneal_speed=20, SNR_prior='unif') + assert s, "Invalid GBRSA instance!" + + +def test_fit(): + from brainiak.reprsimil.brsa import GBRSA + import brainiak.utils.utils as utils + import scipy.stats + import numpy as np + import os.path + np.random.seed(10) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 1, 2, and 3 times, mimicking different length + # of experiments for different participants + n_run = [2, 1, 1] + design_mat = [None] * 3 + n_T = [None] * 3 + n_V = [40, 60, 60] + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + + # start simulating some data + n_C = np.size(design_mat[0], axis=1) + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = [None] * 3 + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = [None] * 3 + + # generating noise + noise = [None] * 3 + + # baseline + inten = [None] * 3 + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + n_T[i] = n_run[i] * design.n_TR + noise_level[i] = np.random.rand( + n_V[i]) * (noise_top - noise_bot) + noise_bot + # noise level is random. + rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot + + noise[i] = np.zeros([n_T[i], n_V[i]]) + noise[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ + np.random.randn(n_V[i]) * noise_level[i] + noise[i] = noise[i] + \ + np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) + inten[i] = np.random.rand(n_V[i]) * 20.0 + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_top = 5.0 # test with high SNR + snr_bot = 1.0 + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + + snr = [None] * 3 + signal = [None] * 3 + betas_simulated = [None] * 3 + scan_onsets = [None] * 3 + Y = [None] * 3 + for i in range(3): + snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot + sqrt_v = noise_level[i] * snr[i] + betas_simulated[i] = np.dot( + L_full, np.random.randn(n_C, n_V[i])) * sqrt_v + signal[i] = np.dot(design_mat[i], betas_simulated[i]) + + # Adding noise to signal as data + Y[i] = signal[i] + noise[i] + inten[i] + + scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) + + # Test fitting. + n_nureg = 2 + gbrsa = GBRSA(n_iter=15, auto_nuisance=True, logS_range=0.5, SNR_bins=11, + rho_bins=16, n_nureg=n_nureg, optimizer='L-BFGS-B') + + gbrsa.fit(X=Y, design=design_mat, scan_onsets=scan_onsets) + + # Check that result is significantly correlated with the ideal covariance + # matrix + u_b = gbrsa.U_ + u_i = ideal_cov + p = scipy.stats.spearmanr(u_b[np.tril_indices_from(u_b)], + u_i[np.tril_indices_from(u_i)])[1] + assert p < 0.01, ( + "Fitted covariance matrix does not correlate with ideal covariance " + "matrix!") + # check that the recovered SNRs makes sense + p = scipy.stats.pearsonr(gbrsa.nSNR_[0], snr[0])[1] + assert p < 0.01, "Fitted SNR does not correlate with simulated SNR!" + p = scipy.stats.pearsonr(gbrsa.sigma_[1], noise_level[1])[1] + assert p < 0.01, ( + "Fitted noise level does not correlate with simulated noise level!") + p = scipy.stats.pearsonr(gbrsa.rho_[2], rho1[2])[1] + assert p < 0.01, ( + "Fitted AR(1) coefficient does not correlate with simulated values!") + assert np.shape(gbrsa.X0_[1]) == (n_T[1], n_nureg + 1), "Wrong size of X0" + + Y_new = [None] * 3 + noise_new = [None] * 3 + for i in range(3): + noise_new[i] = np.zeros([n_T[i], n_V[i]]) + noise_new[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise_new[i][i_t, :] = noise_new[i][i_t - 1, :] * \ + rho1[i] + np.random.randn(n_V[i]) * noise_level[i] + + Y_new[i] = signal[i] + noise_new[i] + inten[i] + ts, ts0 = gbrsa.transform(Y_new, scan_onsets=scan_onsets) + [score, score_null] = gbrsa.score( + X=Y_new, design=design_mat, scan_onsets=scan_onsets) + [score_noise, score_null_noise] = gbrsa.score( + X=noise_new, design=design_mat, scan_onsets=scan_onsets) + for i in range(3): + assert np.shape(ts[i]) == (n_T[i], n_C) and np.shape( + ts0[i]) == (n_T[i], n_nureg + 1) + p = scipy.stats.pearsonr(ts[i][:, 0], design_mat[i][:, 0])[1] + assert p < 0.01, ( + "Recovered time series does not correlate with true time series!") + + assert score[i] > score_null[i], ( + "Full model does not win over null model on data containing " + "signal") + + assert score_noise[i] < score_null_noise[i], ( + "Null model does not win over full model on data without signal") + + [score, score_null] = gbrsa.score( + X=[None] * 3, design=design_mat, scan_onsets=scan_onsets) + assert score == [None] * 3 and score_null == [None] * \ + 3, "score did not return list of None when data is None" + ts, ts0 = gbrsa.transform(X=[None] * 3, scan_onsets=scan_onsets) + assert ts == [None] * 3 and ts0 == [None] * \ + 3, "transform did not return list of None when data is None" + + +def test_gradient(): + from brainiak.reprsimil.brsa import GBRSA + import brainiak.utils.utils as utils + import numpy as np + import os.path + import numdifftools as nd + + np.random.seed(100) + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + # Load an example design matrix + design = utils.ReadDesign(fname=file_path) + + # concatenate it by 1, 2, and 3 times, mimicking different length + # of experiments for different participants + n_run = [1, 2, 1] + design_mat = [None] * 3 + n_T = [None] * 3 + n_V = [30, 30, 20] + for i in range(3): + design_mat[i] = np.tile(design.design_task[:, :-1], [n_run[i], 1]) + n_T[i] = n_run[i] * design.n_TR + + # start simulating some data + n_C = np.size(design_mat[0], axis=1) + + noise_bot = 0.5 + noise_top = 1.5 + noise_level = [None] * 3 + for i in range(3): + noise_level[i] = np.random.rand( + n_V[i]) * (noise_top - noise_bot) + noise_bot + # noise level is random. + + # AR(1) coefficient + rho1_top = 0.8 + rho1_bot = -0.2 + rho1 = [None] * 3 + + # generating noise + noise = [None] * 3 + + # baseline + inten = [None] * 3 + for i in range(3): + rho1[i] = np.random.rand(n_V[i]) * (rho1_top - rho1_bot) + rho1_bot + noise[i] = np.zeros([n_T[i], n_V[i]]) + noise[i][0, :] = np.random.randn( + n_V[i]) * noise_level[i] / np.sqrt(1 - rho1[i]**2) + for i_t in range(1, n_T[i]): + noise[i][i_t, :] = noise[i][i_t - 1, :] * rho1[i] + \ + np.random.randn(n_V[i]) * noise_level[i] + noise[i] = noise[i] + \ + np.dot(np.random.randn(n_T[i], 2), np.random.randn(2, n_V[i])) + inten[i] = np.random.rand(n_V[i]) * 20.0 + + # ideal covariance matrix + ideal_cov = np.zeros([n_C, n_C]) + ideal_cov = np.eye(n_C) * 0.6 + ideal_cov[0:4, 0:4] = 0.2 + for cond in range(0, 4): + ideal_cov[cond, cond] = 2 + ideal_cov[5:9, 5:9] = 0.9 + for cond in range(5, 9): + ideal_cov[cond, cond] = 1 + L_full = np.linalg.cholesky(ideal_cov) + + # generating signal + snr_top = 5.0 # test with high SNR + snr_bot = 1.0 + # snr = np.random.rand(n_V)*(snr_top-snr_bot)+snr_bot + # Notice that accurately speaking this is not snr. the magnitude of signal + # depends not only on beta but also on x. + + snr = [None] * 3 + signal = [None] * 3 + betas_simulated = [None] * 3 + scan_onsets = [None] * 3 + Y = [None] * 3 + for i in range(3): + snr[i] = np.random.rand(n_V[i]) * (snr_top - snr_bot) + snr_bot + sqrt_v = noise_level[i] * snr[i] + betas_simulated[i] = np.dot( + L_full, np.random.randn(n_C, n_V[i])) * sqrt_v + signal[i] = np.dot(design_mat[i], betas_simulated[i]) + + # Adding noise to signal as data + Y[i] = signal[i] + noise[i] + inten[i] + + scan_onsets[i] = np.linspace(0, n_T[i], num=n_run[i] + 1) + + # Get some initial fitting. + SNR_bins = 11 + rho_bins = 20 + gbrsa = GBRSA(n_iter=3, rank=n_C, SNR_bins=SNR_bins, + rho_bins=rho_bins, logS_range=0.5) + + n_grid = SNR_bins * rho_bins + half_log_det_X0TAX0 = [np.random.randn(n_grid) for i in range(3)] + log_weights = np.random.randn(n_grid) + log_fixed_terms = [np.random.randn(n_grid) for i in range(3)] + l_idx = np.tril_indices(n_C) + L_vec = np.random.randn(int(n_C * (n_C + 1) / 2)) + n_X0 = [2, 2, 2] + s = np.linspace(1, SNR_bins, n_grid) + a = np.linspace(0.5, 1, n_grid) + s2XTAcorrX = [None] * 3 + YTAcorrY_diag = [None] * 3 + sXTAcorrY = [None] * 3 + # The calculations below are quite arbitrary and do not conform + # to the model. They simply conform to the symmetry property and shape of + # the matrix indicated by the model + for i in range(3): + YTAcorrY_diag[i] = np.sum(Y[i] * Y[i], axis=0) * a[:, None] + s2XTAcorrX[i] = np.dot(design_mat[i].T, design_mat[ + i]) * s[:, None, None]**2 * a[:, None, None] + sXTAcorrY[i] = np.dot(design_mat[i].T, Y[i]) * \ + s[:, None, None] * a[:, None, None] + + # test if the gradients are correct + print(log_fixed_terms) + ll0, deriv0 = gbrsa._sum_loglike_marginalized(L_vec, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, + log_weights, log_fixed_terms, + l_idx, n_C, n_T, n_V, n_X0, + n_grid, rank=None) + # We test the gradient to the Cholesky factor + vec = np.random.randn(np.size(L_vec)) + vec = vec / np.linalg.norm(vec) + dd = nd.directionaldiff( + lambda x: gbrsa._sum_loglike_marginalized(x, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, + half_log_det_X0TAX0, + log_weights, log_fixed_terms, + l_idx, n_C, n_T, n_V, n_X0, + n_grid, rank=None)[0], + L_vec, + vec) + assert np.isclose(dd, np.dot(deriv0, vec), rtol=1e-5), 'gradient incorrect' + + +def test_SNR_grids(): + import brainiak.reprsimil.brsa + import numpy as np + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='unif', SNR_bins=10) + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.isclose(np.sum(SNR_weights), 1) + and np.isclose(np.std(SNR_weights[1:-1]), 0) + and np.all(SNR_weights > 0) + and np.isclose(np.min(SNR_grids), 0) + and np.all(SNR_grids >= 0) + and np.isclose(np.max(SNR_grids), 1) + ), 'SNR_weights or SNR_grids are incorrect for uniform prior' + assert np.isclose(np.ptp(np.diff(SNR_grids[1:-1])), 0), \ + 'SNR grids are not equally spaced for uniform prior' + assert (np.size(SNR_grids) == np.size(SNR_weights) + and np.size(SNR_grids) == 10 + ), ("size of SNR_grids or SNR_weights is not correct for uniform " + "prior") + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='lognorm', SNR_bins=35) + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids >= 0) + and np.isclose(np.sum(SNR_weights), 1) + and np.all(SNR_weights > 0) + and np.all(np.diff(SNR_grids) > 0) + ), 'SNR_grids or SNR_weights not correct for log normal prior' + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='exp') + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids >= 0) + and np.isclose(np.sum(SNR_weights), 1) + and np.all(SNR_weights > 0) + and np.all(np.diff(SNR_grids) > 0) + ), 'SNR_grids or SNR_weights not correct for exponential prior' + + s = brainiak.reprsimil.brsa.GBRSA(SNR_prior='equal') + SNR_grids, SNR_weights = s._set_SNR_grids() + assert (np.all(SNR_grids == 1) + and np.all(SNR_weights == 1) + and np.size(SNR_grids) == 1 + ), 'SNR_grids or SNR_weights not correct for equal prior' + + +def test_n_nureg(): + import brainiak.reprsimil.brsa + import numpy as np + noise = np.dot(np.random.randn(100, 8), np.random.randn( + 8, 30)) + np.random.randn(100, 30) * 0.001 + design = np.random.randn(100, 2) + s = brainiak.reprsimil.brsa.GBRSA(n_iter=2) + s.fit(X=noise, design=design) + assert s.n_nureg_[0] == 8, 'n_nureg_ estimation is wrong in GBRSA' + + +def test_grid_flatten_num_int(): + # Check for numeric integration of SNR, and correctly flattening 2-D grids + # to 1-D grid. + import brainiak.reprsimil.brsa + import brainiak.utils.utils as utils + import numpy as np + import scipy.special + n_V = 30 + n_T = 50 + n_C = 3 + design = np.random.randn(n_T, n_C) + U_simu = np.asarray([[1.0, 0.1, 0.0], [0.1, 1.0, 0.2], [0.0, 0.2, 1.0]]) + L_simu = np.linalg.cholesky(U_simu) + SNR = np.random.exponential(size=n_V) + beta = np.dot(L_simu, np.random.randn(n_C, n_V)) * SNR + noise = np.random.randn(n_T, n_V) + Y = np.dot(design, beta) + noise + X = design + X_base = None + scan_onsets = [0] + + s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, + SNR_prior='exp') + s.fit(X=[Y], design=[design]) + rank = n_C + l_idx, rank = s._chol_idx(n_C, rank) + L = np.zeros((n_C, rank)) + n_l = np.size(l_idx[0]) + current_vec_U_chlsk_l = s.random_state_.randn(n_l) * 10 + L[l_idx] = current_vec_U_chlsk_l + + # Now we change the grids for SNR and rho for testing. + s.SNR_bins = 2 + s.rho_bins = 2 + SNR_grids, SNR_weights = s._set_SNR_grids() + # rho_grids, rho_weights = s._set_rho_grids() + rho_grids = np.ones(2) * 0.1 + rho_weights = np.ones(2) / 2 + # We purposefully set all rhos to be equal to test flattening of + # grids. + n_grid = s.SNR_bins * s.rho_bins + + D, F, run_TRs, n_run = s._prepare_DF( + n_T, scan_onsets=scan_onsets) + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, XTDX, XTFX \ + = s._prepare_data_XY(X, Y, D, F) + X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, X0TY, X0TDY, X0TFY, X0, \ + X_base, n_X0, idx_DC = s._prepare_data_XYX0( + X, Y, X_base, None, D, F, run_TRs, no_DC=False) + + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] + and half_log_det_X0TAX0[2] == half_log_det_X0TAX0[3] + and half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] + ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' + assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) + and np.array_equal(X0TAX0[2, :, :], X0TAX0[3, :, :]) + and np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0' + assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) + and np.array_equal(X0TAX0_i[2, :, :], X0TAX0_i[3, :, :]) + and np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0_i' + assert np.allclose( + np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), + np.eye(n_X0) + ), 'X0TAX0_i is not inverse of X0TAX0' + assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[1, :]) + and np.array_equal(YTAcorrY_diag[2, :], YTAcorrY_diag[3, :]) + and np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) + ), '_matrix_flattened_grid has mistake YTAcorrY_diag' + assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) + and np.array_equal(sXTAcorrY[2, :, :], sXTAcorrY[3, :, :]) + and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) + ), '_matrix_flattened_grid has mistake sXTAcorrY' + assert (np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) + and np.array_equal(X0TAY[2, :, :], X0TAY[3, :, :]) + and np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) + ), '_matrix_flattened_grid has mistake X0TAY' + assert (np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) + and np.array_equal(XTAX0[2, :, :], XTAX0[3, :, :]) + and np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) + ), '_matrix_flattened_grid has mistake XTAX0' + + # Now we test the other way + rho_grids, rho_weights = s._set_rho_grids() + # rho_grids, rho_weights = s._set_rho_grids() + SNR_grids = np.ones(2) * 0.1 + SNR_weights = np.ones(2) / 2 + # We purposefully set all SNR to be equal to test flattening of + # grids. + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + assert (half_log_det_X0TAX0[0] == half_log_det_X0TAX0[2] + and half_log_det_X0TAX0[1] == half_log_det_X0TAX0[3] + and not half_log_det_X0TAX0[0] == half_log_det_X0TAX0[1] + ), '_matrix_flattened_grid has mistake with half_log_det_X0TAX0' + assert (np.array_equal(X0TAX0[0, :, :], X0TAX0[2, :, :]) + and np.array_equal(X0TAX0[1, :, :], X0TAX0[3, :, :]) + and not np.array_equal(X0TAX0[0, :, :], X0TAX0[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0' + assert (np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[2, :, :]) + and np.array_equal(X0TAX0_i[1, :, :], X0TAX0_i[3, :, :]) + and not np.array_equal(X0TAX0_i[0, :, :], X0TAX0_i[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAX0_i' + assert np.allclose( + np.dot(X0TAX0[0, :, :], X0TAX0_i[0, :, :]), + np.eye(n_X0) + ), 'X0TAX0_i is not inverse of X0TAX0' + assert (np.array_equal(YTAcorrY_diag[0, :], YTAcorrY_diag[2, :]) + and np.array_equal(YTAcorrY_diag[1, :], YTAcorrY_diag[3, :]) + and not np.array_equal(YTAcorrY_diag[0, :], + YTAcorrY_diag[1, :]) + ), '_matrix_flattened_grid has mistake YTAcorrY_diag' + assert (np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[2, :, :]) + and np.array_equal(sXTAcorrY[1, :, :], sXTAcorrY[3, :, :]) + and not np.array_equal(sXTAcorrY[0, :, :], sXTAcorrY[1, :, :]) + ), '_matrix_flattened_grid has mistake sXTAcorrY' + assert (np.array_equal(X0TAY[0, :, :], X0TAY[2, :, :]) + and np.array_equal(X0TAY[1, :, :], X0TAY[3, :, :]) + and not np.array_equal(X0TAY[0, :, :], X0TAY[1, :, :]) + ), '_matrix_flattened_grid has mistake X0TAY' + assert (np.array_equal(XTAX0[0, :, :], XTAX0[2, :, :]) + and np.array_equal(XTAX0[1, :, :], XTAX0[3, :, :]) + and not np.array_equal(XTAX0[0, :, :], XTAX0[1, :, :]) + ), '_matrix_flattened_grid has mistake XTAX0' + + # Now test the integration over SNR + s.SNR_bins = 50 + s.rho_bins = 1 + SNR_grids, SNR_weights = s._set_SNR_grids() + rho_grids, rho_weights = s._set_rho_grids() + n_grid = s.SNR_bins * s.rho_bins + + def setup_for_test(): + # This function will be re-used to set up the variables necessary for + # testing. + + X0TAX0, X0TAX0_i, XTAcorrX, XTAcorrY, YTAcorrY_diag, X0TAY, XTAX0 \ + = s._precompute_ar1_quad_forms_marginalized( + XTY, XTDY, XTFY, YTY_diag, YTDY_diag, YTFY_diag, XTX, + XTDX, XTFX, X0TX0, X0TDX0, X0TFX0, XTX0, XTDX0, XTFX0, + X0TY, X0TDY, X0TFY, rho_grids, n_V, n_X0) + + half_log_det_X0TAX0, X0TAX0, X0TAX0_i, s2XTAcorrX, YTAcorrY_diag, \ + sXTAcorrY, X0TAY, XTAX0 = s._matrix_flattened_grid( + X0TAX0, X0TAX0_i, SNR_grids, XTAcorrX, YTAcorrY_diag, XTAcorrY, + X0TAY, XTAX0, n_C, n_V, n_X0, n_grid) + + log_weights = np.reshape( + np.log(SNR_weights[:, None]) + np.log(rho_weights), n_grid) + all_rho_grids = np.reshape(np.repeat( + rho_grids[None, :], s.SNR_bins, axis=0), n_grid) + log_fixed_terms = - (n_T - n_X0) / 2 * np.log(2 * np.pi) + n_run \ + / 2 * np.log(1 - all_rho_grids**2) + scipy.special.gammaln( + (n_T - n_X0 - 2) / 2) + (n_T - n_X0 - 2) / 2 * np.log(2) + return s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, \ + log_weights, log_fixed_terms + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, log_weights, + log_fixed_terms, l_idx, n_C, n_T, + n_V, n_X0, n_grid, rank=rank) + LL_total = - LL_total + # Now we re-calculate using scipy.integrate + s.SNR_bins = 100 + SNR_grids = np.linspace(0, 12, s.SNR_bins) + SNR_weights = np.exp(- SNR_grids) + SNR_weights = SNR_weights / np.sum(SNR_weights) + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, half_log_det_X0TAX0, + log_weights, log_fixed_terms, + n_C, n_T, n_V, n_X0, + n_grid, rank) + result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) + scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) + LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) + + tol = 1e-3 + assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ + 'Error of log likelihood calculation exceeds the tolerance' + + # Now test the log normal prior + s = brainiak.reprsimil.brsa.GBRSA(n_iter=1, auto_nuisance=False, + SNR_prior='lognorm') + s.SNR_bins = 50 + s.rho_bins = 1 + SNR_grids, SNR_weights = s._set_SNR_grids() + rho_grids, rho_weights = s._set_rho_grids() + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_total, _ = s._loglike_marginalized(current_vec_U_chlsk_l, s2XTAcorrX, + YTAcorrY_diag, sXTAcorrY, + half_log_det_X0TAX0, log_weights, + log_fixed_terms, l_idx, n_C, n_T, + n_V, n_X0, n_grid, rank=rank) + LL_total = - LL_total + # Now we re-calculate using scipy.integrate + s.SNR_bins = 400 + SNR_grids = np.linspace(1e-8, 20, s.SNR_bins) + log_SNR_weights = scipy.stats.lognorm.logpdf(SNR_grids, s=s.logS_range) + result_sum, max_value, result_exp = utils.sumexp_stable( + log_SNR_weights[:, None]) + SNR_weights = np.squeeze(result_exp / result_sum) + n_grid = s.SNR_bins * s.rho_bins + + (s2XTAcorrX, YTAcorrY_diag, sXTAcorrY, half_log_det_X0TAX0, log_weights, + log_fixed_terms) = setup_for_test() + LL_raw, _, _, _ = s._raw_loglike_grids(L, s2XTAcorrX, YTAcorrY_diag, + sXTAcorrY, half_log_det_X0TAX0, + log_weights, log_fixed_terms, + n_C, n_T, n_V, n_X0, + n_grid, rank) + result_sum, max_value, result_exp = utils.sumexp_stable(LL_raw) + scipy_sum = scipy.integrate.simps(y=result_exp, axis=0) + LL_total_scipy = np.sum(np.log(scipy_sum) + max_value) + + tol = 1e-3 + assert(np.isclose(LL_total_scipy, LL_total, rtol=tol)), \ + 'Error of log likelihood calculation exceeds the tolerance' diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py new file mode 100644 index 000000000..7d472e6f5 --- /dev/null +++ b/tests/searchlight/test_searchlight.py @@ -0,0 +1,293 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import namedtuple + +import numpy as np +from mpi4py import MPI + +from brainiak.searchlight.searchlight import Searchlight +from brainiak.searchlight.searchlight import Diamond, Ball + +"""Distributed Searchlight Test +""" + + +def cube_sfn(l, msk, myrad, bcast_var): + if np.all(msk) and np.any(msk): + return 1.0 + return None + + +def test_searchlight_with_cube(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = True + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(cube_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def test_searchlight_with_cube_poolsize_1(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = True + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(cube_sfn, pool_size=1) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def diamond_sfn(l, msk, myrad, bcast_var): + assert not np.any(msk[~Diamond(3).mask_]) + if np.all(msk[Diamond(3).mask_]): + return 1.0 + return None + + +def test_searchlight_with_diamond(): + sl = Searchlight(sl_rad=3, shape=Diamond) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = Diamond(3).mask_ + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(diamond_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +def ball_sfn(l, msk, myrad, bcast_var): + x, y, z = np.mgrid[-myrad:myrad+1, -myrad:myrad+1, -myrad:myrad+1] + correct_mask = np.square(x) + np.square(y) + np.square(z) <= myrad ** 2 + assert not np.any(msk[~Ball(3).mask_]) + if np.all(correct_mask == msk): + return 1.0 + return None + + +def test_searchlight_with_ball(): + sl = Searchlight(sl_rad=3, shape=Ball) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50, 50, 50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0, dim1, dim2), dtype=np.bool) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17, 10:17, 10:17] = Ball(3).mask_ + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(ball_sfn) + + if rank == 0: + assert global_outputs[13, 13, 13] == 1.0 + global_outputs[13, 13, 13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i, j, k] is None + + +MaskRadBcast = namedtuple("MaskRadBcast", "mask rad") + + +def test_instantiate(): + sl = Searchlight(sl_rad=5, max_blk_edge=10) + assert sl + + +def voxel_test_sfn(l, msk, myrad, bcast): + rad = bcast.rad + # Check each point + for subj in l: + for _tr in range(subj.shape[3]): + tr = subj[:, :, :, _tr] + midpt = tr[rad, rad, rad] + for d0 in range(tr.shape[0]): + for d1 in range(tr.shape[1]): + for d2 in range(tr.shape[2]): + assert np.array_equal(tr[d0, d1, d2] - midpt, + np.array([d0-rad, d1-rad, + d2-rad, 0])) + + # Determine midpoint + midpt = l[0][rad, rad, rad, 0] + midpt = (midpt[0], midpt[1], midpt[2]) + + for d0 in range(msk.shape[0]): + for d1 in range(msk.shape[1]): + for d2 in range(msk.shape[2]): + pt = (midpt[0] - rad + d0, midpt[1] - rad + d1, + midpt[2] - rad + d2) + assert bcast.mask[pt] == msk[d0, d1, d2] + + # Return midpoint + return midpt + + +def block_test_sfn(l, msk, myrad, bcast_var, extra_params): + outmat = l[0][:, :, :, 0] + outmat[~msk] = None + return outmat[myrad:-myrad, myrad:-myrad, myrad:-myrad] + + +def test_correctness(): # noqa: C901 + def voxel_test(data, mask, max_blk_edge, rad): + + comm = MPI.COMM_WORLD + rank = comm.rank + + (dim0, dim1, dim2) = mask.shape + + # Initialize dataset with known pattern + for subj in data: + if subj is not None: + for tr in range(subj.shape[3]): + for d1 in range(dim0): + for d2 in range(dim1): + for d3 in range(dim2): + subj[d1, d2, d3, tr] = np.array( + [d1, d2, d3, tr]) + + sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) + sl.distribute(data, mask) + sl.broadcast(MaskRadBcast(mask, rad)) + global_outputs = sl.run_searchlight(voxel_test_sfn) + + if rank == 0: + for d0 in range(rad, global_outputs.shape[0]-rad): + for d1 in range(rad, global_outputs.shape[1]-rad): + for d2 in range(rad, global_outputs.shape[2]-rad): + if mask[d0, d1, d2]: + assert np.array_equal( + np.array(global_outputs[d0, d1, d2]), + np.array([d0, d1, d2])) + + def block_test(data, mask, max_blk_edge, rad): + + comm = MPI.COMM_WORLD + rank = comm.rank + + (dim0, dim1, dim2) = mask.shape + + # Initialize dataset with known pattern + for subj in data: + if subj is not None: + for tr in range(subj.shape[3]): + for d1 in range(dim0): + for d2 in range(dim1): + for d3 in range(dim2): + subj[d1, d2, d3, tr] = np.array( + [d1, d2, d3, tr]) + + sl = Searchlight(sl_rad=rad, max_blk_edge=max_blk_edge) + sl.distribute(data, mask) + sl.broadcast(mask) + global_outputs = sl.run_block_function(block_test_sfn) + + if rank == 0: + for d0 in range(rad, global_outputs.shape[0]-rad): + for d1 in range(rad, global_outputs.shape[1]-rad): + for d2 in range(rad, global_outputs.shape[2]-rad): + if mask[d0, d1, d2]: + assert np.array_equal( + np.array(global_outputs[d0, d1, d2]), + np.array([d0, d1, d2, 0])) + + # Create dataset + def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + mask = np.random.choice([True, False], (dim0, dim1, dim2)) + data = [np.empty((dim0, dim1, dim2, ntr), dtype=np.object) + if i % size == rank + else None + for i in range(0, nsubj)] + voxel_test(data, mask, max_blk_edge, rad) + block_test(data, mask, max_blk_edge, rad) + + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=1) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=1, dim1=5, dim2=9, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=0, dim1=10, dim2=8, ntr=5, nsubj=5, max_blk_edge=4, rad=1) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) + do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) diff --git a/tests/utils/example_design.1D b/tests/utils/example_design.1D new file mode 100755 index 000000000..7385edea6 --- /dev/null +++ b/tests/utils/example_design.1D @@ -0,0 +1,272 @@ +# + 1 -1.0000000112159 0.99459451576695 -0.99999999935909 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0067129032686353 0.010406452231109 -0.009523656219244 -0.050046753138304 0.00098924792837352 0.014952690340579 + 1 -0.98918920040512 0.9623373937801 -0.93600852797536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.0085870968177915 -0.0038935476914048 -0.010923656169325 0.019553247839212 0.0039892479544505 -0.20644730236381 + 1 -0.97837838959431 0.93043089268441 -0.87375120856684 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.030487096868455 0.1236064536497 -0.040723655372858 -0.054246752988547 -0.0075107525335625 -0.0066473102197051 + 1 -0.9675675787835 0.89887501247988 -0.81320908865291 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067987094633281 0.064706451259553 -0.047223657369614 0.010453246533871 -0.024510751594789 0.085652687586844 + 1 -0.95675676797268 0.86766975316651 -0.75436321575299 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.067487093620002 0.11460645031184 -0.074723657220602 -0.12964675202966 -0.030410750885494 0.30405268166214 + 1 -0.94594595716187 0.8368151147443 -0.69719463738645 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.027775989845395 -0.099387097172439 0.11130645405501 -0.059223655611277 -0.0059467516839504 -0.010510752093978 0.0095526902005076 + 1 -0.93513514635106 0.80631109721326 -0.64168440107271 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14959700405598 -0.089187095873058 0.15680645685643 -0.070323657244444 -0.050846753118094 -0.0029107519658282 0.14565269742161 + 1 -0.92432433554025 0.77615770057338 -0.58781355433115 0 0 0 0 0 0.037642534822226 0 0 0 0 0 0 0 0 0 0 0.12429390102625 -0.083387094549835 0.12480645161122 -0.054423656314611 -0.050246753133251 0.013389248284511 0.11435268912464 + 1 -0.91351352472944 0.74635492482465 -0.53556314468118 0 0 0 0 0 0.12830232083797 0 0 0 0 0 0 0 0 0 0 0.049149610102177 -0.062687094323337 0.13770644646138 -0.042223654687405 -0.046746753156185 0.018289248342626 0.072352689690888 + 1 -0.90270271391863 0.71690276996709 -0.48491421964219 0 0 0 0 0 0.17503398656845 0 0 0 0 0 0 0 0 0 0 0.0058261859230697 -0.045587095431983 0.2022064505145 -0.047923658043146 -0.12844675406814 0.016089248354547 0.16415269766003 + 1 -0.89189190310782 0.6878012360007 -0.43584782673358 0.00055640988284722 0 0 0 0 0.12353418022394 0 0 0 0 0 0 0 0 0 0 -0.011150837875903 -0.056987094692886 0.12560645025223 -0.06052365526557 -0.073546752333641 0.0068892481504008 -0.025347310118377 + 1 -0.88108109229701 0.65905032292546 -0.38834501347474 0.085981301963329 0 0 0 0 0.043058145791292 0 0 0 0 0 0 0 0 0 0 -0.0138487406075 0.029312903992832 -0.087693546898663 -0.014023656025529 0.021853249520063 -0.022210751776583 -0.050747311674058 + 1 -0.8702702814862 0.63065003074138 -0.34238682738507 0.13706742227077 0 0 0 0 -0.0014883950352669 0 0 0 0 0 0 0 0 0.00446742400527 0 -0.010243398137391 0.030112902633846 -0.098093544133008 -0.025723656639457 0.082353252917528 -0.023310751770623 -0.20704731252044 + 1 -0.85945947067539 0.60260035944847 -0.29795431598397 0.074951887130737 0 0 0 0 -0.016573801636696 0 0 0 0 0 0 0 0 0.13216799497604 0 -0.0057357279583812 0.043912903405726 -0.12029355484992 -0.026223655790091 0.087853241711855 0.0078892478486523 -0.07564730849117 + 1 -0.84864865986458 0.57490130904671 -0.25502852679083 0.020650556311011 0 0 0 0 -0.016711676493287 0 0.064530149102211 0 0 0 0 0 0 0.16308039426804 0 -0.0026250404771417 0.069412906654179 -0.029693548567593 -0.015923656057566 -0.02844675257802 0.027189248125069 0.088752687908709 + 1 -0.83783784905377 0.54755287953612 -0.21359050732505 -0.0044987495057285 0 0 0 0 -0.011434393003583 0 0.20859688520432 0 0 0 0 0 0 0.07964064925909 0 0 0.029712903313339 0.11960645299405 -0.00012365635484457 -0.19364675506949 0.012089247698896 0.15545268449932 + 1 -0.82702703824295 0.52055507091669 -0.17362130510603 -0.012204987928271 0 0 0 0 -0.0061583844944835 0 0.24455913901329 0 0 0 0 0 0 0.018422532826662 0 0 0.013712903484702 0.10110645275563 -0.00042365584522486 -0.062646753154695 0.0082892481004819 0.024252690374851 + 1 -0.81621622743214 0.49390788318842 -0.13510196765317 -0.011045500636101 0 0 0 0 -0.0020890964660794 0 0.26294341683388 0 0 0 0 0 0 -0.0083925630897284 0 0 0.01411290327087 0.092506448738277 0.0008763438090682 -0.053346753120422 -0.0057107518659905 -0.13064731564373 + 1 -0.80540541662133 0.46761131635132 -0.098013542485854 -0.0070121213793755 0 0 0 0 0 0 0.20453441143036 0 0 0 0 0 0 -0.015550730749965 0 0 0.025512902997434 0.13680644612759 0.0012763440608978 -0.14484675601125 -0.0033107522176579 0.010352690238506 + 1 -0.79459460581052 0.44166537040537 -0.062337077123487 -0.0035262261517346 0 0 0 0 0 0 0.19082318246365 0 0 0 0 0 0 -0.01305516064167 0 0 0.022012903355062 0.15430645924062 0.006576344370842 -0.14244675263762 0.0068892481504008 0.073352691717446 + 1 -0.78378379499971 0.41607004535059 -0.028053619085463 -0.0014841681113467 0 0 0 0 0 0 0.21973279118538 0 0 0 0 0 0 -0.0079289497807622 0 0 0.01741290371865 0.040706452913582 0.0064763445407152 -0.052846753038466 0.00058924793847837 0.0023526903241873 + 1 -0.7729729841889 0.39082534118696 0.0048557841088193 0 0 0 0 0 0 0 0.16675978899002 0 0 0 0 0 0 -0.0038589551113546 0 0 -0.0357870971784 -0.13709354121238 -0.022623656317592 0.030053246766329 -0.017310752649792 -0.0009473105892539 + 1 -0.76216217337809 0.3659312579145 0.036410084939965 0 0 0 0 0 0 0 0.18963772058487 0 0 0 0 0 0 0 0 0 -0.024887095205486 -0.13729354459792 -0.045723658055067 0.063253249973059 -0.003910752129741 -0.030147309415042 + 1 -0.75135136256728 0.3413877955332 0.066628235888576 0 0 0 0 0 0 0 0.15496288239956 0 0 0 0 0 0 0 0 0 -0.011787096969783 -0.19019354600459 -0.030823655426502 0.10135324671865 0.017589247669093 -0.20194731559604 + 1 -0.74054055175647 0.31719495404306 0.095529189435256 0 0 6.292293255683e-05 0 0 0 0 0.17730142176151 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.17039355356246 -0.030923657119274 0.12055324390531 0.014289247686975 -0.15214730706066 + 1 -0.72972974094566 0.29335273344408 0.12313189806061 0 0 0.095935180783272 0 0 0 0 0.12749932706356 0 0 0 0 0 0 0 0 0 0.034612902440131 0.1443064538762 0.0044763442128897 -0.13524674996734 -0.022810752619989 0.2602526852861 + 1 -0.71891893013485 0.26986113373627 0.14945531424524 0 0 0.19222097098827 0 0 0 0 0.036020509898663 0 0 0 0 0 0 0 0 0 0.03431290294975 0.077506455592811 0.0023763440549374 -0.011446751654148 -0.02061075263191 0.042452690191567 + 1 -0.70810811932404 0.24672015491961 0.17451839046975 0 0 0.11517268419266 0 0 0 0 -0.012899462133646 0.047586746513844 0 0 0 0 0 0 0 0 0.0031129033304751 -0.11499355453998 0.007476344704628 0.12395324185491 -0.0040107519598678 -0.13654731120914 + 1 -0.69729730851323 0.22392979699412 0.19834007921474 0 0 0.035251531749964 0 1.7466900317231e-06 0 0 -0.025363964959979 0.17251434922218 0 0 0 0 0 0 0 0 -0.00048709660768509 -0.16689355392009 -0.008623656234704 0.1636532433331 0.00328924797941 -0.26004731561989 + 1 -0.68648649770241 0.20149005995979 0.22093933296081 0 0 -0.0034804616589099 0 0.058831561356783 0 0 -0.021560948342085 0.1271647810936 0 0 0 0 0 0 0 0 -0.0016870964318514 -0.099493545480072 0.0053763436153531 0.046653244644403 0.0083892479306087 0.076252688653767 + 1 -0.6756756868916 0.17940094381662 0.24233510418858 0 0 -0.016322674229741 0 0.15306103229523 0 0 -0.014806993305683 0.046425126492977 0 0 0 0 0 0 0 0 0.0041129032615572 -0.1288935514167 0.0052763437852263 0.10215324535966 0.013489248114638 -0.063347308896482 + 1 -0.66486487608079 0.15766244856461 0.26254634537864 0 0 -0.015743028372526 0 0.22671715915203 0 0 -0.0065927244722843 0.0028545362874866 0 0 0 0 0 0 0 0 -0.0088870963081717 -0.06669354904443 -0.0042236563749611 0.062953244894743 0.0053892479045317 -0.028147309087217 + 1 -0.65405406526998 0.13627457420376 0.28159200901159 0 0 -0.010351501405239 0 0.21719300746918 0 0 -0.0031336443498731 -0.013423582538962 0 0 0 0 0 0 0 0 -0.0025870967656374 -0.10389354545623 -0.0022236560471356 0.034853246062994 0.014289247686975 -0.067447311244905 + 1 -0.64324325445917 0.11523732073408 0.29949104756804 0 0 -0.0053398911841214 0 0.18410536646843 0 0 0 -0.015115818940103 0 0 0 0 0 0 0 0 -0.01188709679991 -0.087793548591435 0.0061763431876898 0.040753249078989 -0.0085107517661527 0.015252690354828 + 1 -0.63243244364836 0.094550688155553 0.3162624135286 0 0 -0.0022931022103876 0 0.17885300517082 0 0 0 -0.010725096799433 0 0 0 0 0 0 0 0 -0.0017870971933007 -0.059793551452458 0.0063763447105885 0.015653248876333 -0.018010751460679 0.079052687622607 + 1 -0.62162163283755 0.07421467646819 0.33192505937386 0 0 0 0 0.16898131370544 0 0 0 -0.005837693810463 0 0 0 0 0 0 0 0 -0.0010870965197682 -0.1001935498789 0.010876344516873 0.028153244405985 8.924794383347e-05 -0.035947310738266 + 1 -0.61081082202674 0.054229285671989 0.34649793758443 0 0 0 0 0.14792582392693 0 0 0 -0.0026140084955841 0 0 0 0 0 0 0 0 -0.015187096782029 -0.1052935468033 0.0063763447105885 0.056053247302771 0.003689247998409 -0.019047311507165 + 1 -0.60000001121593 0.034594515766948 0.36000000064091 0 0 0 0 0.12118621170521 0 0 0 0 0 0 0 0 0 0 0 0 -0.0023870971053839 -0.094993551261723 0.015476344153285 0.02055324986577 -0.0034107520477846 0.099752687849104 + 1 -0.58918920040512 0.01531036675307 0.37245020102391 0 0.0016005024081096 0 0 0.12434333562851 0 0 0 0 0 0 0 0 0 0 0 0 -0.015487096272409 -0.11449354607612 0.0011763442307711 0.074553247541189 -0.0011107519967481 -0.027947309426963 + 1 -0.57837838959431 -0.0036231613696476 0.38386749121403 0 0.096228934824467 0 0 0.054626442492008 0 0 0 0 0 0 0 0 0 0 0 0 0.018212903290987 -0.08299354929477 0.015376344323158 0.0053532458841801 0.00078924793342594 0.028352689929307 + 1 -0.5675675787835 -0.022206068601203 0.39427082369186 0 0.13414172828197 0 0 0.0015350170433521 0 0 0 0 0 0 0 0 1.3503349691746e-05 0 0 0 0.012112903408706 -0.099593547172844 0.012776343151927 0.0025532469153404 0.0090892481384799 -0.10724730696529 + 1 -0.55675676797268 -0.040438354941598 0.40367915093803 0 0.069246374070644 0 0 -0.018672101199627 0 0 0 0 0 0 0 0 0.072324976325035 0 0 0 0.0093129032757133 -0.038593546487391 0.010976344347 -0.0063467547297478 0.0104892476229 0.028652690351009 + 1 -0.54594595716187 -0.058320020390831 0.41211142543312 0 0.017592553049326 0 0.014113682322204 -0.019757304340601 0 0 0 0 0 0 0 0 0.15773610770702 0 0 0 0.0073129032971337 -0.064593550749123 0.015476344153285 0.036053244024515 0.0092892477987334 -0.10364731308073 + 1 -0.53513514635106 -0.075851064948903 0.41958659965775 0 -0.0056622386910021 0 0.12503287196159 -0.013525931164622 0 0 0 0 0 0 0 0 0.097588993608952 0 0 0 0.012712903320789 -0.0035935482010245 0.0058763436973095 -0.023246753960848 -0.0033107522176579 0.025152690708637 + 1 -0.52432433554025 -0.093031488615813 0.42612362609251 0 -0.012351076118648 0 0.11872909218073 -0.0082509722560644 0 0 0 0 0 0 0.012349472381175 0 0.030836544930935 0 0 0 0.016412903554738 -0.037193548865616 0.01207634434104 0.019053246825933 -0.0079107518540695 -0.10244730766863 + 1 -0.51351352472944 -0.10986129139156 0.43174145721801 0 -0.010752606205642 0 0.050772178918123 -0.0035070178564638 0 0 0 0 0 0 0.1094037592411 0 -0.0020397072657943 0 0 0 0.017712903209031 0.030806452967227 0.0045763440430164 -0.016246754676104 -0.011410752427764 0.01055269036442 + 1 -0.50270271391863 -0.12634047327615 0.43645904551486 0 -0.0066744363866746 0 0.0084341736510396 -0.0015163091011345 0.04815686494112 0 0 0 0 0 0.10388795286417 0 -0.01319295912981 0 0 0 0.023812904022634 -0.030193549580872 0.012576343491673 0.026353243738413 -0.0029107519658282 -0.13134731259197 + 1 -0.49189190310782 -0.14246903426957 0.44029534346365 0 -0.0033013850916177 0 -0.0088908206671476 0 0.13978426158428 0 0 0 0 0 0.044425655156374 0 -0.013019875623286 0 0 0 0.025112903676927 0.036006451584399 0.00097634363919497 -0.051846753107384 0.011489247786812 0.041552689857781 + 1 -0.48108109229701 -0.15824697437184 0.44326930354498 0 -0.0013714701635763 0 -0.012442322447896 0 0.13858208060265 0 0 0 0 0 0.0073799015954137 0 -0.0086652403697371 0 0 0 0.021412903442979 -0.0093935476616025 0.01237634383142 0.044653248041868 0.013189247692935 -0.1755473157391 + 1 -0.4702702814862 -0.17367429358294 0.44539987823947 0.0030656999442726 0 0 -0.0096218045800924 0 0.18833549320698 0 0 0 0 0 -0.0077794678509235 0 -0.0045090229250491 0 0 0 0.022612904198468 -0.014093547128141 0.0094763431698084 0.061953250318766 -0.0081107524456456 -0.16874730493873 + 1 -0.45945947067539 -0.18875099190288 0.44670602002772 0.12636050581932 0 0 -0.0055486336350441 0 0.11498866230249 0 0 0 0 0 -0.010887031443417 0 -0.0019495403394103 0 0 0 0.040712905116379 0.071606452576816 0.0063763447105885 -0.038346753455698 -0.0038107522996143 -0.03504731040448 + 1 -0.44864865986458 -0.20347706933166 0.44720668139032 0.16548715531826 0 0 -0.0025971501599997 0 0.030592679977417 0 0 0 0 0 -0.0084190787747502 0 0 0.053775411099195 0 0 0.020712903700769 0.052206452004611 0.0067763440310955 -0.020446753129363 -0.0027107520727441 0.13395269308239 + 1 -0.43783784905377 -0.21785252586928 0.44692081480789 0.083070278167725 0 0.00069552229251713 0 0 -0.0093372892588377 0 0 0 0 0 -0.0048550544306636 0 0 0.17381578683853 0 0 0.023612902499735 0.031806453131139 0.0089763440191746 0.01885324344039 -0.0023107520537451 0.0600526900962 + 1 -0.42702703824295 -0.23187736151574 0.44586737276102 0.020175436511636 0 0.10747677832842 0 0 -0.020309561863542 0 0 0 0 0 -0.0022725064773113 0 0 0.12343865633011 0 0 0.054112904705107 0.063506453298032 0.013076344504952 -0.10814675316215 -0.00681075186003 0.09105268958956 + 1 -0.41621622743214 -0.24555157627104 0.44406530773032 -0.0077522136271 0 0.17133423686028 0 0 -0.018001658841968 0 0 0 0 0.00055644899839535 0 0 0 0.043845765292645 0 0 0.05091290269047 0.047706454060972 0.011776342988014 -0.072146752849221 0.0052892480744049 -0.058047308586538 + 1 -0.40540541662133 -0.25887517013517 0.4415335721964 -0.015503321774304 0 0.093689776957035 0 0 -0.011593883857131 0 0 0 0 0.085981898009777 0 0 0 0.0017423888202757 0 0 0.063712903298438 0.080906453542411 -0.001023655757308 -0.16354675218463 0.0059892478166148 0.03335269074887 + 1 -0.39459460581052 -0.27184814310814 0.43829111863984 -0.013250176794827 0 0.025813156738877 0 0 -0.0052539245225489 0 0 0 0 0.13706727325916 0 0 0 -0.013717586174607 0 0 0.062212903983891 0.095306451432407 -0.00042365584522486 -0.17324675247073 -0.001310752122663 -0.022447309456766 + 1 -0.38378379499971 -0.28447049518995 0.43435689954127 -0.008134912699461 0 -0.0056234514340758 0 0 0.006489459425211 0 0 0 0 0.074951559305191 0 0 0 -0.015004207380116 0 0 0.051512901671231 0.058706454001367 0.0025763437151909 -0.10944675281644 -0.011610752088018 -0.15204730536789 + 1 -0.3729729841889 -0.2967422263806 0.42974986738128 -0.0039911866188049 0 -0.015256236307323 0 0 0.11816956847906 0 0 0 0 0.020650375634432 0 0 0 -0.010505273938179 0 0 0.053812903352082 0.1143064526841 -0.011323656188324 -0.19274675473571 -0.012810751912184 0.055052691139281 + 1 -0.36216217337809 -0.30866333668009 0.42448897464048 0 0 -0.013806872069836 0 0 0.12816107273102 0 0 0 0 -0.0044988198205829 0 0 0 -0.0056656016968191 0 0 0.045312904752791 0.049706450663507 0.0063763447105885 -0.060146752744913 0.004089248017408 0.00065269041806459 + 1 -0.35135136256728 -0.32023382608842 0.41859317379946 0 0 -0.008765147998929 0 0 0.15216007828712 0 0 0 0 -0.012204997241497 0 0 0 -0.0025189006701112 0 0 0.041512903757393 0.10200645308942 0.0028763441368937 -0.084046754986048 -0.0025107521796599 0.19385269377381 + 1 -0.34054055175647 -0.33145369460559 0.41208141733884 0.00042675601434894 0 -0.0044077797792852 0 0 0.12315040826797 0 0 0 0 -0.011045484803617 0 0 0 0 0 0 0.043612902052701 0.092406454496086 0.0044763442128897 -0.044746753294021 -0.016510752146132 0.060752690769732 + 1 -0.32972974094566 -0.34232294223159 0.40497265773921 0.12098247557878 0 -0.001855208654888 0 0 0.045295763760805 0 0 0 0 -0.0070121022872627 0 0 0 0 0 0 0.047312905080616 0.0077064521610737 0.011176344007254 0.020053248852491 -0.019710752298124 -0.024547311477363 + 1 -0.31891893013485 -0.35284156896643 0.39728584748118 0.20731872320175 0 0 0 0 -0.00076612085103989 0 0 0 0 -0.0035262131132185 0 0.017642103135586 0 0 0 0 0.04471290204674 0.050606450997293 0.006076343357563 -0.032946752384305 -0.0031107520917431 0.017152690328658 + 1 -0.30810811932404 -0.36300957481012 0.38903993904535 0.11678945273161 0 0 0 0 -0.01625781878829 0 0 0 0 -0.0014841614756733 0 0.15629108250141 0 0 0 0 0.055312902666628 0.081206451170146 0.005676344037056 -0.071046752855182 -0.001310752122663 0.070552689023316 + 1 -0.29729730851323 -0.37282695976264 0.38025388491233 0.033387824892998 0 0 0 0 -0.016767358407378 0.028632424771786 0 0 0 0 0 0.14841136336327 0 0 0 0 0.052412902005017 0.038706452585757 0.01857634447515 -0.032746752724051 -0.0019107520347461 -0.017247310839593 + 1 -0.28648649770241 -0.38229372382399 0.37094663756271 -0.0058099492453039 0 0 0 0 -0.011784705333412 0.17964339256287 0 0 0 0 0 0.063465222716331 0 0 0 0 0.048512903042138 0.039306451566517 0.0098763443529606 -0.030746752396226 0.005889247986488 -0.020847308449447 + 1 -0.2756756868916 -0.39140986699419 0.36113714947711 -0.018164224922657 0 0 0 0 -0.005407671444118 0.15981185436249 0 0 0 0 0 0.010542716830969 0 0 0 0 0.053912905044854 0.058206452988088 0.0033763442188501 -0.060346753336489 0.0084892477607355 0.01635269029066 + 1 -0.26486487608079 -0.40017538927323 0.35084437313612 -0.016777858138084 0 0 0 0 -0.0026109907776117 0.18218821287155 0 0 0 0 0 -0.011113525368273 0 0 0 0 0.045912903733552 0.074306453578174 -0.0034236563369632 -0.05854675360024 0.016989248688333 0.071852688677609 + 1 -0.25405406526998 -0.4085902906611 0.34008726102035 -0.010774576105177 0 0 0 0 0 0.17693194746971 0 0 0 0 0 -0.015552902594209 0 0 0 0 0.04131290409714 0.071206453256309 0.0021763434633613 -0.045946753118187 -0.0020107520977035 0.024952690117061 + 1 -0.24324325445917 -0.41665457115781 0.32888476561041 -0.0054639861918986 0 0 0 0 0 0.17483820021152 0 0 0 0 0 -0.012027255259454 0 0 0 0 0.039012902416289 0.069406452588737 -0.001923656091094 -0.024946752935648 -0.003910752129741 -0.0061473092064261 + 1 -0.23243244364836 -0.42436823076337 0.31725583938689 -0.0023150618653744 0 0 0 0 0 0.12542612850666 0 0 0 0 0 -0.0069357920438051 0 0 0 0 0.035812904126942 0.060206453315914 0.0042763436213136 0.026653248816729 0.00018924794858322 -0.099747306667268 + 1 -0.22162163283755 -0.43173126947776 0.3052194348304 0 0 0 0 0 0 0.13749043643475 0 0 0 0 0 -0.0032464377582073 0 0 0 0 0.029112902469933 0.11720644962043 0.0043763434514403 0.0016532465815544 0.0012892479426228 0.0097526903264225 + 1 -0.21081082202674 -0.43874368730098 0.29279450442154 0 0 0 0 0 0 0.14846354722977 0 0 0 0 0 0 0 0 0 0 0.031112902797759 0.03860645275563 0.014176344498992 0.043153245002031 -0.0030107522616163 -0.070447309873998 + 1 -0.20000001121593 -0.44540548423305 0.28000000064091 0 0 0 0 0 0 0.19279418885708 0 0 0 0 0 0 0 0 0 0 0.022912903688848 0.10360645037144 0.0073763430118561 0.010053247213364 0.00078924793342594 0.13845268730074 + 1 -0.18918920040512 -0.45171666027396 0.26685487596912 0 0 0 0 0 0 0.21137630939484 0 0 0 0 0 0 0 0 0 0 0.015312903560698 0.082306454889476 0.0033763442188501 0.041253250092268 0.004089248017408 0.18055269215256 + 1 -0.17837838959431 -0.4576772154237 0.25337808288678 0 0 0 0.043020330369473 0 0 0.14208625257015 0 0 0 0 0 0 0 0 0 0 0.0055129032116383 0.077706451527774 -0.0071236561052501 -0.037946753203869 0.0025892478879541 0.2391526857391 + 1 -0.1675675787835 -0.46328714968228 0.23958857387448 0 0 0 0.13905262947083 0 0 0.041933041065931 0 0 0 0 0 0 0 0 0 0 0.023912903852761 0.15820645075291 -0.025423657149076 -0.1429467536509 0.0025892478879541 0.15375268552452 + 1 -0.15675676797268 -0.46854646304971 0.22550530141283 0 0 0 0.098750926554203 0 0 -0.012082259170711 0 0 0.018220633268356 0 0 0 0 0 0 0 0.011012903414667 0.10640645306557 -0.010123656247742 -0.062346752732992 -0.0088107521878555 -0.021447311155498 + 1 -0.14594595716187 -0.47345515552597 0.21114721798243 0 0 0 0.03507661446929 0 0 -0.026482511311769 0 0 0.11431851238012 0 0 0 0 0 0 0 -0.011787096969783 -0.048293546773493 0.00067634414881468 0.074753243476152 -0.0072107521118596 -0.055747306905687 + 1 -0.13513514635106 -0.47801322711106 0.19653327606389 0 0.017292505130172 0 0.0013939110795036 0 0 -0.024222373962402 0 0 0.099139772355556 0 0 0 0 0 0 0 -0.0036870967596769 -0.067493547685444 0.0023763440549374 0.068653244525194 -0.010410752263851 -0.10984730627388 + 1 -0.12432433554025 -0.482220677805 0.1816824281378 0 0.12800069153309 0 -0.01097406912595 0 0 -0.015291653573513 0 0 0.04024151340127 0 0 0 0 0 0 0 0.016112903133035 -0.025893547572196 0.00077634397894144 -0.031946752220392 -0.024710751255043 0.090352692641318 + 1 -0.11351352472944 -0.48607750760778 0.16661362668478 0 0.11605546623468 0 -0.012003365904093 0 0 -0.0082694683223963 0 0 0.0054445695132017 0 0 0 0 0 0.0059029059484601 0 -0.016587096266448 -0.076993544586003 0.012976344674826 0.1091532446444 -0.021610752795823 -0.16014730837196 + 1 -0.10270271391863 -0.48958371651939 0.15134582418542 0 0.048352032899857 0 -0.0084042195230722 0 0 -0.0028834280092269 0 0 -0.0083946632221341 0 0 0 0 0 0.10000795125961 0 -0.02848709654063 -0.032693547196686 0.0011763442307711 0.096353251487017 0.0047892479924485 -0.094947307370603 + 1 -0.09189190310782 -0.49273930453984 0.13589797312033 0 0.0073072644881904 0.004996741656214 -0.0045324815437198 0 0 0 0 0 -0.010818615555763 0 0 0 0 0 0.11038105934858 0 -0.03128709550947 -0.040093549527228 0.0051763439550996 0.11825324967504 0.0080892479745671 -0.25064730551094 + 1 -0.081081092297009 -0.49554427166914 0.12028902597011 0 -0.009253709577024 0.11014425009489 -0.0020151205826551 0 0 0 0 0 -0.0081188511103392 0 0 0 0 0 0.051077321171761 0 -0.028087097220123 0.020306452177465 -0.0021236562170088 0.028753247112036 -0.019210751284845 0.077652693726122 + 1 -0.070270281486198 -0.49799861790727 0.10453793521537 0 -0.012408400885761 0.12837927043438 0 0 0 0 0 0.0099470904096961 -0.004590333905071 0 0 0 0 0 0.010568294674158 0 -0.039187095128 -0.094093550927937 0.0050763441249728 0.22715326026082 -0.021210751612671 -0.30834731366485 + 1 -0.059459470675387 -0.50010234325423 0.088663653336703 0 -0.0094509832561016 0.061017215251923 0 1.7468818214184e-06 0 0 0 0.1329403668642 -0.0021164785139263 0 0 0 0 0 -0.0067023267038167 0 -0.033887098543346 0.002206452190876 0.0094763431698084 0.092953253537416 0.002489247941412 -0.019747308455408 + 1 -0.048648659864576 -0.50185544771004 0.07268513281472 0 -0.0053962054662406 0.013384091667831 0 0.058831807225943 0 0 0 0.13926292955875 0 0 0 0 0 0 -0.010917749255896 0 -0.044387097470462 -0.085793544538319 0.0021763434633613 0.14515324309468 -0.004710752167739 -0.266447304748 + 1 -0.037837849053766 -0.50325793127469 0.056621326130022 0 -0.0025067001115531 -0.0071999179199338 0 0.14035293459892 0 0 0 0.062757529318333 0 0 0 0 0 0 -0.0088573601096869 0 -0.019987096078694 0.0048064524307847 0.0029763439670205 0.027753245085478 -0.0056107520358637 0.03505268972367 + 1 -0.027027038242955 -0.50430979394817 0.040491185763213 0 0.053441412746906 -0.012465350329876 0 0.089743755757809 0 0 0 0.012170788832009 0 0 0 0 0 0 -0.0052667763084173 0 -0.026087096892297 -0.04249354917556 0.0045763440430164 0.039753247052431 -0.019710752298124 -0.044147307984531 + 1 -0.016216227432144 -0.50501103573049 0.024313664194895 0 0.14021460711956 -0.010284850373864 0 0.029239932075143 0 0 0 -0.0091062942519784 0 0 0 0 0.0018005651654676 0 -0.0025229994207621 0 -0.039087097160518 -0.041293547488749 0.005676344037056 0.12005325034261 -0.015110752661712 -0.17924730386585 + 1 -0.0054054166213331 -0.50536165662166 0.0081077139056723 0 0.092748634517193 -0.0061801802366972 0 -0.0010622846893966 0 0 0 -0.014036962762475 0 0 0 0 0.10825754702091 0 0 0 -0.02738709654659 0.0081064521800727 0.00407634396106 0.039853248745203 -0.025110752438195 -0.0091473096981645 + 1 0.0054053941894776 -0.50536165662166 -0.008107712623852 0 0.031126981601119 -0.0029839819762856 0 -0.011565917171538 0 0.028552377596498 0 -0.011202690191567 0 0 0 0 0.15090943872929 0 0 0 -0.03188709821552 -0.042693548835814 0.0083763431757689 0.05095325037837 0.0030892478534952 0.014852690343105 + 1 0.016216205000289 -0.50501103573049 -0.024313662913075 0 -0.00027817318914458 0 0 -0.011690891347826 0 0.10350869596004 0 -0.0065927179530263 0 0 0 0 0.077902160584927 0 0 0 -0.065387095324695 -0.029893548227847 -0.011423656251281 0.11095324531198 0.010989247704856 0.1039526918903 + 1 0.027027015811099 -0.50430979394817 -0.040491184481393 0 -0.011387062259018 0 0.017292505130172 -0.007877248339355 0 0.076298668980598 0 -0.0031336380634457 0 0 0 0 0.01979162171483 0 0 0 -0.054587095044553 -0.038393546827137 -0.0098236562334932 0.07965325191617 0.0057892481563613 0.071052690036595 + 1 0.03783782662191 -0.50325793127469 -0.056621324848202 0 -0.011802065186203 0 0.12800069153309 -0.0041351648978889 0 0.027854926884174 0 0 1.9652418359328e-06 0 0 0 -0.0063700182363391 0 0 0 -0.047287098132074 -0.025793549604714 -0.0038236561231315 0.066753249615431 0.004089248017408 0.1022526929155 + 1 0.048648637432721 -0.50185544771004 -0.0726851315329 0 -0.008052596822381 0 0.11605546623468 -0.0018002366414294 0 0.0017126569291577 0 0 0.066185779869556 0 0 0 -0.013894959352911 0 0 0 -0.065787094645202 -0.0054935486987233 -0.016723656095564 0.030953247100115 -0.0070107524516061 0.095652692951262 + 1 0.059459448243532 -0.50010234325423 -0.088663652054883 0 -0.0028645200654864 0 0.048352032899857 0 0 -0.0080541670322418 0 0 0.15789704024792 0 0 0 -0.012096680700779 0 0 0 -0.051787096075714 -0.030793548561633 -0.014523656107485 0.094953242689371 -0.0089107520179823 -0.056247307918966 + 1 0.070270259054343 -0.49799861790727 -0.10453793393355 0 0.082330644130707 0 0.0073072644881904 0 0 -0.0090694855898619 0 0 0.10096172243357 0 0 0 -0.007508740760386 0 0 0 -0.048587097786367 0.04140645172447 -0.0070236562751234 -0.040346752852201 -0.029810751904733 0.063552689738572 + 1 0.081081069865153 -0.49554427166914 -0.12028902468829 0 0.11737401038408 0 -0.009253709577024 0 0 -0.0064350459724665 0 0 0.032894920557737 0 0 0 -0.0037140580825508 0 0.014120560139418 0 -0.046387097798288 -0.042093546129763 -0.0041236560791731 0.059353243559599 -0.010510752093978 -0.18344731535763 + 1 0.091891880675964 -0.49273930453984 -0.13589797183851 0 0.060590572655201 2.4012949066087e-26 -0.012408400885761 0 0 -0.0035026066470891 0 0 -0.0011950702173635 0 0 0 -0.0015429038321599 0 0.15219141542912 0 -0.053587096743286 0.0074064522050321 -0.015923656057566 0.002253245562315 0.0090892481384799 -0.063547312282026 + 1 0.10270269148678 -0.48958371651939 -0.1513458229036 0 0.015393483452499 0.084276184439659 -0.0094509832561016 0 0 -0.0015683997189626 0 0 -0.013011656701565 0 0 0 0 0 0.15163862705231 0 -0.041287097148597 -0.031393547542393 -0.0029236562550068 0.088153246790171 0.01738924800884 -0.19314731564373 + 1 0.11351350229759 -0.48607750760778 -0.16661362540296 0 -0.0049544586800039 0.2446226477623 -0.0053962054662406 0 0 0 0 0 -0.013152251951396 0.0011044126003981 0 0 0 0 0.066562041640282 0 -0.029687098227441 -0.078893546946347 -0.0059236562810838 0.017553243786097 -0.0053107520798221 -0.036047308705747 + 1 0.1243243131084 -0.482220677805 -0.18168242685598 0 -0.01080719102174 0.16756856441498 -0.0025067604146898 0 0 0 0 0 -0.0088619040325284 0.10258268564939 0 0 0 0 0.012005048803985 0 -0.021687096916139 -0.032093548215926 -0.006923656212166 0.062953244894743 -0.0041107522556558 -0.077147311531007 + 1 0.13513512391921 -0.47801322711106 -0.19653327478207 0.02777655608952 -0.0094085298478603 0.057876385748386 0 0 0 0 0 0 -0.0046520605683327 0.15266855061054 0 0 0 0 -0.010630848817527 0 -0.028887095861137 -0.02389354724437 -0.0087236562394537 0.0098532475531101 0.0017892479081638 0.061852690763772 + 1 0.14594593473002 -0.47345515552597 -0.21114721670061 0.1495973020792 -0.0058401320129633 0.00094535760581493 0 0 0 0 0 0 -0.0020252661779523 0.081088602542877 0 7.5448255643096e-08 0 0 -0.015581800602376 0 -0.025487097911537 -0.00049354787915945 -0.011723656207323 0.024553250521421 0.00038924789987504 -0.0054473103955388 + 1 0.15675674554083 -0.46854646304971 -0.22550530013101 0.12429348379374 -0.002888711867854 -0.019582450389862 0 0 0 0 0 0 0 0.021482115611434 0 0.066801764070988 0 0 -0.01223857421428 0 -0.02568709757179 -0.0035935482010245 -0.0018236562609673 0.02225324884057 -0.0075107525335625 -0.031247309409082 + 1 0.16756755635164 -0.46328714968228 -0.23958857259266 0.049149297177792 -0.001200036262162 -0.020836120471358 0 0 0 0.0026805191300809 0 0 0 -0.0057318191975355 0 0.17526826262474 0 0 -0.007129117846489 0 -0.019887096248567 -0.010493547655642 -0.0091236562293489 0.0025532469153404 0.0011892479233211 -0.095247312448919 + 1 0.17837836716245 -0.4576772154237 -0.25337808160496 0.0058260434307158 0 -0.014399533160031 0 0 0 0.079301044344902 0 0 0 -0.013821039348841 0 0.11593579500914 0 0 -0.003362531773746 0 -0.029887097887695 -0.024893549270928 -0.012123656226322 0.053453247994184 0.0093892476288602 -0.051047309301794 + 1 0.18918917797326 -0.45171666027396 -0.2668548746873 -0.011150880716741 0 -0.0076954429969192 0.0011043065460399 0 0 0.097848139703274 0 0 0 -0.012263779528439 0 0.03890872746706 0 0 0 0 -0.036987095139921 0.0021064523607492 -0.016323656309396 0.029853243380785 0.0075892478926107 -0.0091473096981645 + 1 0.19999998878407 -0.44540548423305 -0.27999999935909 -0.013848732225597 0 -0.0033972456585616 0.10258162766695 0 0 0.047784235328436 0 0 0 -0.0076978113502264 0 -0.00034771647187881 0 0 0 0 -0.037287096492946 0.020606452599168 -0.013623656239361 -0.017846751958132 -0.0050107521237805 0.12615268770605 + 1 0.21081079959488 -0.43874368730098 -0.29279450313971 -0.010243374854326 0 0 0.15266886353493 0 0 0.011053452268243 0 0 0 -0.0038390003610402 0.00055644899839535 -0.014233827590942 0 0 0 0 -0.036187096498907 0.0076064523309469 -0.011723656207323 0.018653247505426 -0.0022107519907877 0.023652690462768 + 1 0.22162161040569 -0.43173126947776 -0.30521943354858 -0.0057357028126717 0 0 0.081089183688164 0 0 -0.0050355666317046 0 0 0 -0.0016052267747 0.085981898009777 -0.014752581715584 0 0 0 0 -0.040587096475065 0.023506452329457 -0.021523656323552 0.022353250533342 0.016689247335307 -0.076547308824956 + 1 0.2324324212165 -0.42436823076337 -0.31725583810507 -0.002625027904287 0 0 0.021482426673174 0 0.00028454218409024 -0.009330440312624 0 0 0 0 0.13706727325916 -0.010065745562315 0 0 0 0 -0.032887096516788 -0.013293548487127 -0.017923655919731 0.014253247529268 0.016589247505181 -0.096347308717668 + 1 0.24324323202732 -0.41665457115781 -0.32888476432859 0 0 0 -0.0057317009195685 0 0.080655962228775 -0.0078330878168344 0 0 0 0 0.074951559305191 -0.0053311996161938 0 0 0 0 -0.044587097130716 -0.039993547834456 -0.019323656335473 0.11035325005651 -0.022410751436837 -0.097647308371961 + 1 0.25405404283813 -0.4085902906611 -0.34008725973853 0.00062593474285677 0 0 -0.01382102444768 0 0.13821229338646 -0.0047573610208929 0 0 0 0 0.020650375634432 -0.0023370909038931 0 0 0 0 -0.049287098459899 0.028606452979147 -0.027623657137156 0.03745324537158 -0.014610751648434 0.037952690385282 + 1 0.26486485364894 -0.40017538927323 -0.3508443718543 0.096728548407555 0 0 -0.012263810262084 0 0.077859088778496 -0.0023153687361628 0 0 0 0 -0.0044988198205829 0 0 0 0 0 -0.025887097232044 -0.1584935458377 0.010576343163848 0.28665323927999 0.0081892478046939 -0.28344732429832 + 1 0.27567566445975 -0.39140986699419 -0.36113714819529 0.15420092642307 0 0 -0.0076978439465165 0 0.022258253768086 0 0 0 0 0 -0.012204997241497 0 0 0.0040207784622908 0 0 -0.03188709821552 -0.1056935461238 -0.016523656435311 0.082253251224756 0.012089247698896 0.23425269220024 + 1 0.28648647527056 -0.38229372382399 -0.37094663628089 0.084321089088917 0 0 -0.0038390222471207 0 -0.0038734152913094 0 0 0 0 0 -0.011045484803617 0 0 0.11895155161619 0 0 -0.011487097479403 0.15390645246953 -0.02732365578413 -0.33114675804973 -0.0015107520157471 0.46645268891007 + 1 0.29729728608137 -0.37282695976264 -0.38025388363051 0.023231994360685 0 0 -0.0016052387654781 0 -0.012109502218664 0 0.021616401150823 0 0 0 -0.0070121022872627 0 0 0.14677220582962 0 0 -0.014587095938623 0.12170645128936 -0.021123656071723 -0.25024675950408 -0.0087107523577288 0.30875268671662 + 1 0.30810809689218 -0.36300957481012 -0.38903993776353 -0.0050610406324267 0 8.3067540401771e-08 0 0 -0.011185213923454 0 0.16000151634216 0 0 0 -0.0035262131132185 0 0 0.071676351130009 0 0 -0.011187096126378 -0.021293547935784 0.013976342976093 0.075053248554468 -0.0037107520038262 -0.023047308437526 + 1 0.31891890770299 -0.35284156896643 -0.39728584619936 -0.013730604201555 0 0.073483273386955 0 0 -0.0071830214001238 0 0.14506870508194 0 0 0 -0.0014841614756733 0 0 0.016580177471042 0 0 -0.010987096466124 -0.010493547655642 0.0095763429999352 -0.01044675335288 -0.004710752167739 -0.013647309504449 + 1 0.3297297185138 -0.34232294223159 -0.40497265645739 -0.012426199391484 0 0.19279517233372 0 0 -0.0036426351871341 0 0.060439497232437 0.0062459269538522 0 0 0 0 0 -0.0075533492490649 0 0 -0.02088709641248 -0.050193549133837 -0.0023236563429236 0.046753246337175 -0.017410752479918 -0.12824730481952 + 1 0.34054052932461 -0.33145369460559 -0.41208141605701 -0.007888650521636 0 0.12752863764763 0 0 -0.0015433629741892 0 0.0091338297352195 0.13768030703068 0 0 0 0 0 -0.013995659537613 0 0 0.0065129032736877 0.018406452611089 0.0043763434514403 -0.090446751564741 -0.023310751770623 0.15285268519074 + 1 0.35135134013542 -0.32023382608842 -0.41859317251764 -0.0039670118130744 0.0024526501074433 0.042799100279808 0 0 0 0 -0.011567215435207 0.16047409176826 0 0 0 0 0 -0.011749630793929 0 0 -0.017287096939981 -0.054993544705212 0.003176343627274 0.041453246027231 0.011889248038642 -0.00044730957597494 + 1 0.36216215094623 -0.30866333668009 -0.42448897335866 -0.0016696940874681 0.10108884423971 -0.00038268772186711 0 0 0 0 -0.015510492026806 0.076271519064903 0 0 0 0 0 -0.0071360412985086 0 0 -0.0055870963260531 -0.018493547104299 -0.0011236565187573 -0.0085467547178268 0.015189248020761 0.18875269684941 + 1 0.37297296175705 -0.2967422263806 -0.42974986609946 0 0.13238954544067 -0.01565725915134 0 0 0 0 -0.011813691817224 0.016730114817619 0 0 0 0 0 -0.0034730527549982 0.041677042841911 0 0.0019129030406475 -0.038693548180163 -0.00032365601509809 -4.6752393245697e-05 -0.00031075207516551 0.11815269384533 + 1 0.38378377256786 -0.28447049518995 -0.43435689825945 0 0.066455982625484 -0.016227813437581 0 0 0 0 -0.0067452238872647 -0.0089998971670866 0 0 0 0 0 0 0.17081795632839 0 -0.0021870965138078 -0.0014935480430722 -0.011423656251281 0.028053250163794 8.924794383347e-05 0.13765268865973 + 1 0.39459458337867 -0.27184814310814 -0.43829111735802 0 0.016140213236213 -0.0098451487720013 0 0 0 0 -0.0031334289815277 -0.015581687912345 0 0 0 0 0 0 0.13085924088955 0 -0.012287096120417 -0.031593547202647 -0.0014236560091376 0.042453248053789 -0.0017107521416619 -0.05474731232971 + 1 0.40540539418948 -0.25887517013517 -0.44153357091458 0 -0.0062018162570894 0.10811647772789 0 0 0 0 0 -0.012856062501669 0 0 0 0 0 0 0.049078978598118 0 -0.020487097091973 -0.050293547101319 -0.0031236563809216 0.083953250199556 0.00098924792837352 -0.12484730686992 + 1 0.41621620500029 -0.24555157627104 -0.4440653064485 0 -0.012402662076056 0.16706095635891 0 0 0 0 0 -0.0061247227713466 0 0 0 0 0 0 0.0040129679255188 0 -0.0066870963200927 0.034006453119218 -0.010323656199034 -0.067946752533317 0.0021892479853705 0.20965269301087 + 1 0.4270270158111 -0.23187736151574 -0.4458673714792 0 -0.010600126348436 0.090098448097706 0 0 0 0 0 0.092498958110809 0 0 0 0 0 0 -0.01310474704951 0 -0.02568709757179 -0.034993548877537 -0.015623656101525 0.092453245073557 0.010789248044603 -0.083147308789194 + 1 0.43783782662191 -0.21785252586928 -0.44692081352607 0 0.0093702841550112 0.023869017139077 0 0 0 0 0 0.13414172828197 0 0 0 0 0 0 -0.015217265114188 0 -0.034987098537385 -0.014693547971547 -0.021223655901849 0.061453249305487 0.013389248284511 -0.2369472971186 + 1 0.44864863743272 -0.20347706933166 -0.4472066801085 0 0.1374693363905 -0.0063686883077025 0 0 0 0 0 0.069246374070644 0 0 0 0 1.2008145858999e-05 0 -0.010944607667625 0 -0.021287097595632 0.0089064522180706 -0.017023656051606 -0.023146752268076 0.013789247605018 -0.078247307799757 + 1 0.45945944824353 -0.18875099190288 -0.4467060187459 0 0.13356995582581 -0.015356711111963 0 0 0 0 0 0.017592553049326 0 0 0 0 0.06429036706686 0 -0.0060129929333925 0 -0.030487096868455 -0.014193546958268 -0.017123656347394 0.047353249043226 0.00018924794858322 0.023852690123022 + 1 0.47027025905434 -0.17367429358294 -0.44539987695765 0 0.057118438184261 -0.013626421801746 0.0040210401639342 0 0 0 0 -0.0056622386910021 0 0 0 0 0.14020979404449 0 -0.0027119813021272 0 -0.042687098495662 -0.0022935476154089 -0.020723655819893 0.055353250354528 0.0013892479182687 0.095052690245211 + 1 0.48108106986515 -0.15824697437184 -0.44326930226316 0 0.009488333016634 -0.0085531240329146 0.11895248293877 0 0 0 0 -0.012351076118648 0 0 0 0 0.086744964122772 0 0 0 -0.017187097109854 0.080606455914676 -0.026223655790091 -0.095046754926443 -0.011210751836188 0.24795268569142 + 1 0.49189188067596 -0.14246903426957 -0.44029534218182 0 -0.01000221259892 -0.0042655561119318 0.14677180349827 0 0 0 0 -0.010752606205642 0.0067461780272424 0 0 0 0.027409762144089 0 0 0 -0.043387095443904 0.013006452238187 -0.0074236562941223 0.03295324370265 -0.014210752327926 -0.0089473100379109 + 1 0.50270269148678 -0.12634047327615 -0.43645904423304 0 -0.013997609727085 -0.0017835852922872 0.071675792336464 0 0 0 0 -0.0066744363866746 0.11429480463266 0 0 0 -0.0018132763216272 0 0 0 -0.014087096787989 -0.10489354748279 0.028776343911886 0.20875324681401 -0.00061075203120708 -0.30814731027931 + 1 0.51351350229759 -0.10986129139156 -0.43174145593619 0 -0.010824510827661 0 0.016579894348979 0.0021460687275976 0 0 0 -0.0033013850916177 0.12614978849888 0 0 0 -0.011727117933333 0 0 0 -0.022487097419798 -0.0638935463503 0.0048763435333967 0.085353244096041 0.011989247868769 -0.43564730789512 + 1 0.5243243131084 -0.093031488615813 -0.42612362481069 0 -0.0062421360053122 0 -0.0075534516945481 0.088452741503716 0 0 0 -0.0013714701635763 0.058374084532261 0 0 0 -0.011573190800846 0 0 0 0.0099129031877965 0.14590645115823 -0.021523656323552 -0.26314675435424 -0.0051107519539073 0.32535269949585 + 1 0.53513512391921 -0.075851064948902 -0.41958659837593 0 0.050519626587629 0 -0.013995666056871 0.11584085226059 0 0 0 0 0.012078051455319 0 0 0 -0.0077023864723742 0 0 0 0.0036129031796008 0.11790645401925 -0.02732365578413 -0.18784674629569 -0.0058107521617785 0.11255268845707 + 1 0.54594593473002 -0.058320020390831 -0.4121114241513 0 0.14021460711956 0 -0.011749600991607 0.058148983865976 0 0 0 0 -0.0076598017476499 0 0 0 -0.0040079848840833 0 0.0030658126343042 0 -0.016887097619474 -0.08299354929477 -0.00062365643680096 0.10565324500203 0.0095892482204363 -0.24984732177109 + 1 0.55675674554083 -0.040438354941598 -0.40367914965621 0 0.092748634517193 0 -0.0071360059082508 0.014122685417533 0 0 0 0 -0.012477427721024 0 0 0 -0.0017329071415588 0 0.12636105716228 0 -0.0057870969176292 -0.050393548794091 -0.0076236561872065 0.075053248554468 0.0042892479104921 -0.18254731502384 + 1 0.56756755635164 -0.022206068601203 -0.39427082241004 0 0.031126981601119 0 -0.0034730304032564 -0.0054265889339149 0 0 0.030862841755152 0 -0.010122697800398 0 0 0 0 0 0.16548693180084 0 -0.0066870963200927 -0.099193547852337 0.0012763440608978 0.084453243762255 -0.010610751924105 -0.032047308050096 + 1 0.57837836716245 -0.0036231613696475 -0.3838674899322 0 -0.00027817318914458 0 0 -0.010852329432964 0 0 0.16621923446655 0 -0.0060191727243364 0 0 0 0 0 0.083069980144501 0 0.018612903542817 -0.030793548561633 0.0016763433814049 -0.032246753573418 -0.0034107520477846 0.16925269458443 + 1 0.58918917797326 0.01531036675307 -0.37245019974209 0 -0.011387062259018 0.00035563000710681 0 -0.0092751104384661 0 0 0.13810387253761 0 -0.0028834280092269 0 0 0 0 0 0.020175265148282 0 -0.0045870970934629 -0.039693546481431 0.0030763437971473 0.017853248864412 0.016789247165434 0.058652688749135 + 1 0.59999998878407 0.034594515766949 -0.35999999935909 0 -0.011802065186203 0.10081872344017 0 -0.0056944275274873 0 0 0.054610330611467 0 0 0 0 0 0 0 -0.0077522699721158 0 0.0062129032448865 -0.0081935478374362 0.0058763436973095 -0.051746753160842 0.0091892479686067 0.24165268335491 + 1 0.61081079959488 0.054229285671989 -0.34649793630261 0 -0.008052596822381 0.17276559770107 0 -0.0027938217390329 0 0 0.0064733815379441 0 0 0.0016005024081096 0 0 0 0 -0.015503327362239 0 -0.0093870973214507 -0.08979354519397 0.0080763436853886 0.088053245097399 -0.018010751460679 -0.10724730696529 + 1 0.62162161040569 0.07421467646819 -0.33192505809204 0 -0.0042649595998228 0.097324542701244 0 0 0 0 -0.012389867566526 0 0 0.096228934824467 0 0 0 0 -0.013250158168375 0 0.0053129033185542 -0.0081935478374362 0.014976343140006 -0.0060467533767223 -0.015910751302727 0.14865268860012 + 1 0.6324324212165 0.094550688155554 -0.31626241224678 0.028872592374682 -0.0018696727929637 0.027823185548186 0 0 0 0 -0.015387481078506 0 0 0.13414172828197 0 0 0 0 -0.0081348968669772 0 0.0047129031736404 -0.052293551154435 0.021276343613863 0.0077532455325127 0.011589247616939 0.18645268771797 + 1 0.64324323202732 0.11523732073408 -0.29949104628622 0.1349770128727 0 -0.0048416242934763 0 0 0 0 -0.01138152834028 0 0 0.069246374070644 0 0 0 0 -0.0039911740459502 0 0.0067129032686353 -0.016593548469245 0.027076344937086 0.018253248184919 0.024089247803204 0.1892526904121 + 1 0.65405404283813 0.13627457420376 -0.28159200772977 0.10760726034641 0 -0.015136853791773 0 0 0 0 -0.0063730031251907 0 0 0.017592553049326 0 0.053776569664478 0 0 0 0 0.0062129032448865 -0.032093548215926 0.020576342940331 -0.028046753257513 0.0091892479686067 0.028452690690756 + 1 0.66486485364894 0.15766244856461 -0.26254634409682 0.041445430368185 0 -0.01398154720664 0.0027592312544584 0 0 0 -0.0029166976455599 0 0 -0.0056622386910021 0 0.17381598055363 0 0 0 0 -0.001187096349895 -0.050693546421826 0.021176343783736 0.041453246027231 0.020289248670451 -0.12694731261581 + 1 0.67567566445975 0.17940094381662 -0.24233510290676 0.0041748615913093 0 -0.0089788129553199 0.11372494697571 0 0 0 0 0 0 -0.012351076118648 0 0.12343798577785 0 0 0 0 0.017612903378904 0.042106452398002 0.018076343461871 -0.12294675037265 0.017289248178713 0.13255269173533 + 1 0.68648647527056 0.20149005995979 -0.22093933167899 -0.010208387859166 0 -0.0045533217489719 0.14893823862076 0 0 0 0 0 0 -0.010752606205642 0 0.043845307081938 0 0 0 0 0.041512903757393 0.049206453375518 0.027976345270872 -0.12854675576091 0.038189248065464 0.040852691046894 + 1 0.69729728608137 0.22392979699412 -0.19834007793292 -0.012246385216713 0 -0.0019292180659249 0.074762977659702 0 0 0 0 0 0.020822903141379 -0.0066744363866746 0 0.0017421913798898 0 0 0 0 0.027312903665006 -0.001593547873199 0.015076342970133 -0.037746753543615 0.0091892479686067 -0.22444730903953 + 1 0.70810809689218 0.24672015491961 -0.17451838918792 -0.0089307744055986 0 0 0.018157737329602 0 0 0 0 0 0.13064929842949 -0.0033013850916177 0 -0.013717637397349 0 0 0 0 0.039112904109061 0.054306454025209 0.013676343485713 -0.081246752291918 0.0090892481384799 -0.077847308479249 + 1 0.71891890770299 0.26986113373627 -0.14945531296342 -0.0049531273543835 0.054176468402147 0 -0.0069770426489413 0 0 0 0 0 0.11330310255289 -0.0013714701635763 0 -0.015004185028374 0 0 0 0 0.048112903721631 0.041506451554596 0.025376342236996 -0.12344675138593 0.0063892480684444 0.25445269141346 + 1 0.7297297185138 0.29335273344409 -0.12313189677879 -0.0022502727806568 0.15725728869438 0 -0.013952994719148 0 0 0 0 0 0.045990746468306 0 0 -0.010505232028663 0 0 0 0 0.07791290152818 -0.0022935476154089 0.018676344305277 -0.024246752262115 0.04248924634885 0.085852690972388 + 1 0.74054052932461 0.31719495404306 -0.095529188153436 0 0.10772325843573 0 -0.011925142258406 0 0 0 0 0 0.0062225582078099 0 0 -0.005665572360158 0.072235301136971 0 0 0 0.067712903954089 -0.037293546833098 0.020676344633102 0.018553245812654 0.011789248208515 -0.20184731390327 + 1 0.75135134013542 0.3413877955332 -0.066628234606755 0 0.037206649780273 0 -0.0073214070871472 0 0 0 0 0 -0.0095938388258219 0 0 -0.0025188846047968 0.209676399827 0 0 0 0.099112902767956 0.036506452597678 0.025876343250275 -0.070646753534675 0.028789247269742 -0.1198473116383 + 1 0.76216215094623 0.3659312579145 -0.036410083658144 0 0.00060790922725573 0 0.028887817636132 0 0 0 0 0 -0.012364141643047 0 0 0 0.1436310261488 0 0 0 0.088712905533612 0.01690645236522 0.023276343941689 -0.088646754622459 0.010689248214476 0.071252689696848 + 1 0.77297296175705 0.39082534118696 -0.0048557828269987 0 -0.012588676065207 0 0.15184837579727 0 0 0 0 0 -0.0092787202447653 0 2.1845621631655e-06 0 0.049608871340752 0 0 0 0.085712906904519 -0.034393546171486 0.020676344633102 -0.018446754664183 0.009689248050563 -0.024347308091819 + 1 0.78378377256786 0.41607004535059 0.028053620367284 0 -0.013394667766988 0 0.1210593432188 0 0 0 0 0 -0.0052461246959865 0 0.07354100048542 0 0.00081054569454864 0 0 0 0.13121290225536 0.022306452505291 0.01967634446919 -0.16344675049186 0.0023892478784546 0.16675269696862 + 1 0.79459458337867 0.44166537040537 0.062337078405308 0 -0.0092568807303905 0 0.046627011150122 0 0 0 0 0 -0.002418847521767 0 0.17544116079807 0 -0.016784902662039 0 0 0 0.15361290145665 0.026106451638043 0.037276342511177 -0.11474675312638 0.014089248026721 0.012452690396458 + 1 0.80540539418948 0.46761131635132 0.098013543767675 0.02917392924428 -0.004947098903358 0 0.0046971309930086 0 0 0 0 0 0 0 0.11217899620533 0 -0.017859559506178 0 0 0 -0.02118709590286 -0.047993549145758 0.013076344504952 0.07745324447751 0.0021892479853705 -0.17064730729908 + 1 0.81621620500029 0.49390788318842 0.13510196893499 0.11957256495953 -0.0021839591208845 0 -0.011484319344163 0 0 0 0 0 0 0 0.036549493670464 0 -0.012342507950962 0 0 0 -0.0039870971813798 0.018306451849639 0.019076343625784 -0.05524675315246 0.010389247792773 -0.03394731041044 + 1 0.8270270158111 0.52055507091669 0.17362130638786 0.091601468622684 0 0 -0.013777210377157 0 0 0 0 0 0 0 -0.0013280296698213 0 -0.0065961321815848 0 0 0 0.0037129032425582 0.036206453107297 0.028176344931126 -0.061146752908826 0.025189247797243 0.092052691616118 + 1 0.83783782662191 0.54755287953612 0.21359050860688 0.034355282783508 0 0 -0.010047196410596 0 0 0 0 0 0 0 -0.01445743534714 0 -0.0029119455721229 0.020822903141379 0 0 -0.0069870967417955 -0.029893548227847 0.016676343977451 0.052853245288134 0.021489248494618 -0.052347308956087 + 1 0.84864863743272 0.57490130904672 0.25502852807265 0.002809077501297 0 2.1826426745974e-06 -0.0055723269470036 0 0 0 0 0 0 0 -0.014613589271903 0 0 0.13064929842949 0 0 -0.016887097619474 -0.011393547989428 0.014476343989372 0.037353243678808 0.001989247975871 -0.094247310422361 + 1 0.85945944824353 0.60260035944847 0.29795431726579 -0.009173322468996 0 0.073538526892662 -0.002531590173021 0 0 0 0 0 0 0 -0.0098465224727988 0 0 0.11330310255289 0 0 -0.0071870964020491 0.041306451894343 0.024676345288754 -0.050646753166802 -0.012310751830228 0.016052690334618 + 1 0.87027025905434 0.63065003074138 0.34238682866689 -0.010652085766196 0 0.17544116079807 0 0 0 0 0 0 0 0.0040210401639342 -0.0051689259707928 0 0 0.045990746468306 0 0 -0.0098870964720845 0.0033064521849155 0.026976343244314 0.055753249675035 -0.014910753001459 -0.020947310142219 + 1 0.88108106986515 0.65905032292546 0.38834501475656 -0.0076612252742052 0 0.11218037456274 0 0 0 0 0 0 0 0.11895248293877 -0.0022502795327455 0 0 0.0062225582078099 0 0 0.0030129032675177 0.061606450937688 0.023776344954967 -0.057646753266454 -0.0056107520358637 0.17195269558579 + 1 0.89189188067596 0.6878012360007 0.4358478280154 0.015245664864779 0 0.036550357937813 0 0 0 0 0 0 0 0.14677180349827 0 0 0 -0.0095938388258219 0 0 0.014212903100997 0.0087064522085711 0.037576343864202 -0.044646752998233 -0.0071107522817329 0.09555269125849 + 1 0.90270269148678 0.71690276996709 0.48491422092401 0.14210297167301 0 -0.0013276820536703 0 0 0 0 0 0 0 0.071675792336464 0 0 0 -0.012364141643047 0 0 0.029112902469933 -0.037493546493351 0.033676344901323 -0.028546752408147 -0.00081075204070657 0.06235269177705 + 1 0.91351350229759 0.74635492482465 0.535563145963 0.13056182861328 0 -0.014457356184721 0 0 0 0 0 0 0 0.016579894348979 0 0.00069556123344228 0 -0.0092787202447653 0 0 0.017512903548777 -0.012193548493087 0.027576342225075 -0.073646754026413 -0.0079107518540695 0.090652690269053 + 1 0.9243243131084 0.77615770057338 0.58781355561298 0.054395545274019 0 -0.014613640494645 0 0 0 0 0 0 0 -0.0075534516945481 0 0.10747737437487 0 -0.0052461246959865 0 0 -0.028887095861137 -0.040393547154963 0.02277634292841 0.032053243368864 -0.030210751225241 -0.11324730422348 + 1 0.93513512391921 0.80631109721326 0.64168440235453 0.0082204462960362 0 -0.0098466016352177 0 0 0 0 0 0 0 -0.013995666056871 0 0.17133408784866 0 -0.002418847521767 0 0 0.0011129034683108 -0.024293546564877 0.026476342231035 -0.008346751332283 0.0063892480684444 -0.13364730682224 + 1 0.94594593473002 0.8368151147443 0.69719463866827 -0.01041049323976 0 -0.0051689869724214 0.042828563600779 0 0 0 0 0 0 -0.011749600991607 0 0.093689449131489 0 0 0 0 -0.030687096528709 0.0040064523927867 0.022076342254877 -0.0096467547118664 -0.012210752000101 -0.096747308038175 + 1 0.95675674554083 0.86766975316651 0.75436321703481 -0.013959442265332 0 -0.0022503104992211 0.15526303648949 0 0 0 0 0 0 -0.0071360059082508 0 0.025812968611717 0 0 0 0 -0.034287097863853 -0.044993546791375 0.019276343286037 0.092153239995241 -0.0098107523517683 -0.099347307346761 + 1 0.96756755635164 0.89887501247988 0.81320908993473 -0.01063232216984 0 0 0.11444798856974 0 0 0 0 0 0 -0.0034730304032564 0.037509337067604 -0.0056235245428979 0 0 0 0 -0.016187096945941 0.00020645186305046 0.022976342588663 0.016353245824575 -0.015210752491839 0.12425269279629 + 1 0.97837836716245 0.93043089268441 0.87375120984866 -0.006070701405406 0 0 0.041782386600971 0 0 0 0 0 0 0 0.15373615920544 -0.015256246551871 0 0 0 0 -0.013587097637355 -0.029593546874821 0.029876343905926 0.02615324780345 0.0034892478724942 0.088752687908709 + 1 0.98918917797326 0.9623373937801 0.93600852925718 -0.0028200859669596 0 0 0.0025689851026982 0 0.028872592374682 0 0 0 0 0 0.11777331680059 -0.013806855306029 0 0 0 0 -0.0050870971754193 -0.018593546934426 0.036676343530416 -0.0050467513501644 0.0057892481563613 0.12315268907696 + 1 0.99999998878407 0.99459451576695 1.0000000006409 0 0 0 -0.01208124961704 0 0.1349770128727 0 0 0 0 0 0.044171079993248 -0.0087651275098324 0 0 0 0 0.0079129033256322 -0.18649354297668 0.025376342236996 0.10375324264169 0.0030892478534952 -0.1448473026976 +# + diff --git a/tests/utils/example_stimtime_1_AFNI.txt b/tests/utils/example_stimtime_1_AFNI.txt new file mode 100644 index 000000000..48200041d --- /dev/null +++ b/tests/utils/example_stimtime_1_AFNI.txt @@ -0,0 +1,2 @@ +5.2*2.0:2.0 40.0*4.0:1.5 +2.0*2.0:1.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_1_FSL.txt b/tests/utils/example_stimtime_1_FSL.txt new file mode 100644 index 000000000..ce14b1d55 --- /dev/null +++ b/tests/utils/example_stimtime_1_FSL.txt @@ -0,0 +1,3 @@ +5.2 2.0 2.0 +40.0 1.5 4.0 +50.0 1.0 2.0 \ No newline at end of file diff --git a/tests/utils/example_stimtime_2_AFNI.txt b/tests/utils/example_stimtime_2_AFNI.txt new file mode 100644 index 000000000..39d03d9ee --- /dev/null +++ b/tests/utils/example_stimtime_2_AFNI.txt @@ -0,0 +1 @@ +-10.0 diff --git a/tests/utils/example_stimtime_2_FSL.txt b/tests/utils/example_stimtime_2_FSL.txt new file mode 100644 index 000000000..e60beb2a6 --- /dev/null +++ b/tests/utils/example_stimtime_2_FSL.txt @@ -0,0 +1,3 @@ +5.2 2.0 +40.0 1.5 2.0 +50.0 \ No newline at end of file diff --git a/tests/utils/test_fmrisim.py b/tests/utils/test_fmrisim.py new file mode 100644 index 000000000..7c442661a --- /dev/null +++ b/tests/utils/test_fmrisim.py @@ -0,0 +1,858 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""fMRI Simulator test script + +Test script for generating a run of a participant's data. + + Authors: Cameron Ellis (Princeton) 2016 +""" +import numpy as np +import math +from brainiak.utils import fmrisim as sim +import pytest +from itertools import product + + +def test_generate_signal(): + + # Inputs for generate_signal + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [3] + feature_type = ['cube'] + feature_coordinates = np.array([[5, 5, 5]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + assert np.all(volume.shape == dimensions), "Check signal shape" + assert np.max(volume) == signal_magnitude, "Check signal magnitude" + assert np.sum(volume > 0) == math.pow(feature_size[0], 3), ( + "Check feature size") + assert volume[5, 5, 5] == signal_magnitude, "Check signal location" + assert volume[5, 5, 1] == 0, "Check noise location" + + feature_coordinates = np.array( + [[5, 5, 5], [3, 3, 3], [7, 7, 7]]) + + # Check feature size is correct + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=['loop', 'cavity', 'sphere'], + feature_size=[3], + signal_magnitude=signal_magnitude) + assert volume[5, 5, 5] == 0, "Loop is empty" + assert volume[3, 3, 3] == 0, "Cavity is empty" + assert volume[7, 7, 7] != 0, "Sphere is not empty" + + # Check feature size manipulation + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=['loop', 'cavity', 'sphere'], + feature_size=[1], + signal_magnitude=signal_magnitude) + assert volume[5, 6, 6] == 0, "Loop is too big" + assert volume[3, 5, 5] == 0, "Cavity is too big" + assert volume[7, 9, 9] == 0, "Sphere is too big" + + # Check that out of bounds feature coordinates are corrected + feature_coordinates = np.array([0, 2, dimensions[2]]) + x, y, z = sim._insert_idxs(feature_coordinates, feature_size[0], + dimensions) + assert x[1] - x[0] == 2, "x min not corrected" + assert y[1] - y[0] == 3, "y was corrected when it shouldn't be" + assert z[1] - z[0] == 1, "z max not corrected" + + # Check that signal patterns are created + feature_coordinates = np.array([[5, 5, 5]]) + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + signal_constant=0, + ) + assert volume[4:7, 4:7, 4:7].std() > 0, "Signal is constant" + + +def test_generate_stimfunction(): + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 100 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + assert stimfunction.shape[0] == duration * 100, "stimfunc incorrect length" + eventNumber = np.sum(event_durations * len(onsets)) * 100 + assert np.sum(stimfunction) == eventNumber, "Event number" + + # Create the signal function + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + stim_dur = stimfunction.shape[0] / (tr_duration * 100) + assert signal_function.shape[0] == stim_dur, "The length did not change" + + # Test + onsets = [0] + tr_duration = 1 + event_durations = [1] + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + max_response = np.where(signal_function != 0)[0].max() + assert 25 < max_response <= 30, "HRF has the incorrect length" + assert np.sum(signal_function < 0) > 0, "No values below zero" + + # Export a stimfunction + sim.export_3_column(stimfunction, + 'temp.txt', + ) + + # Load in the stimfunction + stimfunc_new = sim.generate_stimfunction(onsets=None, + event_durations=None, + total_time=duration, + timing_file='temp.txt', + ) + + assert np.all(stimfunc_new == stimfunction), "Export/import failed" + + # Break the timing precision of the generation + stimfunc_new = sim.generate_stimfunction(onsets=None, + event_durations=None, + total_time=duration, + timing_file='temp.txt', + temporal_resolution=0.5, + ) + + assert stimfunc_new.sum() == 0, "Temporal resolution not working right" + + # Set the duration to be too short so you should get an error + onsets = [10, 30, 50, 70, 90] + event_durations = [5] + with pytest.raises(ValueError): + sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=89, + ) + + # Clip the event offset + stimfunc_new = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=95, + ) + assert stimfunc_new[-1] == 1, 'Event offset was not clipped' + + # Test exporting a group of participants to an epoch file + cond_a = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=110, + ) + + cond_b = sim.generate_stimfunction(onsets=[x + 5 for x in onsets], + event_durations=event_durations, + total_time=110, + ) + + stimfunction_group = [np.hstack((cond_a, cond_b))] * 2 + sim.export_epoch_file(stimfunction_group, + 'temp.txt', + tr_duration, + ) + + # Check that convolve throws a warning when the shape is wrong + sim.convolve_hrf(stimfunction=np.hstack((cond_a, cond_b)).T, + tr_duration=tr_duration, + temporal_resolution=1, + ) + + +def test_apply_signal(): + + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[5, 5, 5]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 100 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + # Check that you can compute signal change appropriately + # Preset a bunch of things + stimfunction_tr = stimfunction[::int(tr_duration * 100)] + mask, template = sim.mask_brain(dimensions, mask_self=False) + noise_dict = sim._noise_dict_update({}) + noise = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=noise_dict, + iterations=[0, 0] + ) + coords = feature_coordinates[0] + noise_function_a = noise[coords[0], coords[1], coords[2], :] + noise_function_a = noise_function_a.reshape(duration // tr_duration, 1) + + noise_function_b = noise[coords[0] + 1, coords[1], coords[2], :] + noise_function_b = noise_function_b.reshape(duration // tr_duration, 1) + + # Create the calibrated signal with PSC + method = 'PSC' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + sig_b = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [1.0], + method, + ) + + assert sig_b.max() / sig_a.max() == 2, 'PSC modulation failed' + + # Create the calibrated signal with SFNR + method = 'SFNR' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a / (noise_function_a.mean() / noise_dict['sfnr']) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b / (noise_function_b.mean() / noise_dict['sfnr']) + + assert scaled_b.max() / scaled_a.max() == 2, 'SFNR modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-SD + method = 'CNR_Amp/Noise-SD' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a / noise_function_a.std() + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b / noise_function_b.std() + + assert scaled_b.max() / scaled_a.max() == 2, 'CNR_Amp modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-Var_dB + method = 'CNR_Amp2/Noise-Var_dB' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = np.log(sig_a.max() / noise_function_a.std()) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = np.log(sig_b.max() / noise_function_b.std()) + + assert np.round(scaled_b / scaled_a) == 2, 'CNR_Amp dB modulation failed' + + # Create the calibrated signal with CNR_Signal-SD/Noise-SD + method = 'CNR_Signal-SD/Noise-SD' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + scaled_a = sig_a.std() / noise_function_a.std() + sig_b = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [1.0], + method, + ) + scaled_b = sig_b.std() / noise_function_a.std() + + assert (scaled_b / scaled_a) == 2, 'CNR signal modulation failed' + + # Create the calibrated signal with CNR_Amp/Noise-Var_dB + method = 'CNR_Signal-Var/Noise-Var_dB' + sig_a = sim.compute_signal_change(signal_function, + noise_function_a, + noise_dict, + [0.5], + method, + ) + + scaled_a = np.log(sig_a.std() / noise_function_a.std()) + sig_b = sim.compute_signal_change(signal_function, + noise_function_b, + noise_dict, + [1.0], + method, + ) + scaled_b = np.log(sig_b.std() / noise_function_b.std()) + + assert np.round(scaled_b / scaled_a) == 2, 'CNR signal dB modulation ' \ + 'failed' + + # Convolve the HRF with the stimulus sequence + signal = sim.apply_signal(signal_function=signal_function, + volume_signal=volume, + ) + + assert signal.shape == (dimensions[0], dimensions[1], dimensions[2], + duration / tr_duration), "The output is the " \ + "wrong size" + + signal = sim.apply_signal(signal_function=stimfunction, + volume_signal=volume, + ) + + assert np.any(signal == signal_magnitude), "The stimfunction is not binary" + + # Check that there is an error if the number of signal voxels doesn't + # match the number of non zero brain voxels + with pytest.raises(IndexError): + sig_vox = (volume > 0).sum() + vox_pattern = np.tile(stimfunction, (1, sig_vox - 1)) + sim.apply_signal(signal_function=vox_pattern, + volume_signal=volume, + ) + + +def test_generate_noise(): + + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[5, 5, 5]]) + signal_magnitude = [1] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Inputs for generate_stimfunction + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 200 + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + ) + + signal_function = sim.convolve_hrf(stimfunction=stimfunction, + tr_duration=tr_duration, + ) + + # Convolve the HRF with the stimulus sequence + signal = sim.apply_signal(signal_function=signal_function, + volume_signal=volume, + ) + + # Generate the mask of the signal + mask, template = sim.mask_brain(signal, + mask_self=None) + + assert min(mask[mask > 0]) > 0.1, "Mask thresholding did not work" + assert len(np.unique(template) > 2), "Template creation did not work" + + stimfunction_tr = stimfunction[::int(tr_duration * 100)] + + # Create the noise volumes (using the default parameters) + noise = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + iterations=[1, 0], + ) + + assert signal.shape == noise.shape, "The dimensions of signal and noise " \ + "the same" + + noise_high = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={'sfnr': 50, 'snr': 25}, + iterations=[1, 0], + ) + + noise_low = sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={'sfnr': 100, 'snr': 25}, + iterations=[1, 0], + ) + + system_high = np.std(noise_high[mask > 0], 1).mean() + system_low = np.std(noise_low[mask > 0], 1).mean() + + assert system_low < system_high, "SFNR noise could not be manipulated" + + # Check that you check for the appropriate template values + with pytest.raises(ValueError): + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template * 2, + mask=mask, + noise_dict={}, + ) + + # Check that iterations does what it should + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={}, + iterations=[0, 0], + ) + + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict={}, + iterations=None, + ) + + # Test drift noise + trs = 1000 + period = 100 + drift = sim._generate_noise_temporal_drift(trs, + tr_duration, + 'sine', + period, + ) + + # Check that the max frequency is the appropriate frequency + power = abs(np.fft.fft(drift))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs + period_freq = np.where(freq == 1 / (period // tr_duration)) + max_freq = np.argmax(power) + + assert period_freq == max_freq, 'Max frequency is not where it should be' + + # Do the same but now with cosine basis functions, answer should be close + drift = sim._generate_noise_temporal_drift(trs, + tr_duration, + 'discrete_cos', + period, + ) + + # Check that the appropriate frequency is peaky (may not be the max) + power = abs(np.fft.fft(drift))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / trs + period_freq = np.where(freq == 1 / (period // tr_duration))[0][0] + + assert power[period_freq] > power[period_freq + 1], 'Power is low' + assert power[period_freq] > power[period_freq - 1], 'Power is low' + + # Check it gives a warning if the duration is too short + drift = sim._generate_noise_temporal_drift(50, + tr_duration, + 'discrete_cos', + period, + ) + + # Test physiological noise (using unrealistic parameters so that it's easy) + timepoints = list(np.linspace(0, (trs - 1) * tr_duration, trs)) + resp_freq = 0.2 + heart_freq = 1.17 + phys = sim._generate_noise_temporal_phys(timepoints, + resp_freq, + heart_freq, + ) + + # Check that the max frequency is the appropriate frequency + power = abs(np.fft.fft(phys))[1:trs // 2] + freq = np.linspace(1, trs // 2 - 1, trs // 2 - 1) / (trs * tr_duration) + peaks = (power > (power.mean() + power.std())) # Where are the peaks + peak_freqs = freq[peaks] + + assert np.any(resp_freq == peak_freqs), 'Resp frequency not found' + assert len(peak_freqs) == 2, 'Two peaks not found' + + # Test task noise + sim._generate_noise_temporal_task(stimfunction_tr, + motion_noise='gaussian', + ) + sim._generate_noise_temporal_task(stimfunction_tr, + motion_noise='rician', + ) + + # Test ARMA noise + with pytest.raises(ValueError): + noise_dict = {'fwhm': 4, 'auto_reg_rho': [1], 'ma_rho': [1, 1]} + sim._generate_noise_temporal_autoregression(stimfunction_tr, + noise_dict, + dimensions, + mask, + ) + + # Generate spatial noise + vol = sim._generate_noise_spatial(np.array([10, 10, 10, trs])) + assert len(vol.shape) == 3, 'Volume was not reshaped to ignore TRs' + + # Switch some of the noise types on + noise_dict = dict(physiological_sigma=1, drift_sigma=1, task_sigma=1, + auto_reg_sigma=0) + sim.generate_noise(dimensions=dimensions, + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=noise_dict, + iterations=[0, 0], + ) + + +def test_generate_noise_spatial(): + + # Set up the inputs + dimensions = np.array([10, 5, 10]) + mask = np.ones(dimensions) + vol = sim._generate_noise_spatial(dimensions, mask) + + # Run the analysis from _calc_FHWM but for th elast step of aggregating + # across dimensions + v_count = 0 + v_sum = 0 + v_sq = 0 + + d_sum = [0.0, 0.0, 0.0] + d_sq = [0.0, 0.0, 0.0] + d_count = [0, 0, 0] + + # Pull out all the voxel coordinates + coordinates = list(product(range(dimensions[0]), + range(dimensions[1]), + range(dimensions[2]))) + + # Find the sum of squared error for the non-masked voxels in the brain + for i in list(range(len(coordinates))): + + # Pull out this coordinate + x, y, z = coordinates[i] + + # Is this within the mask? + if mask[x, y, z] > 0: + + # Find the the volume sum and squared values + v_count += 1 + v_sum += vol[x, y, z] + v_sq += vol[x, y, z] ** 2 + + # Get the volume variance + v_var = (v_sq - ((v_sum ** 2) / v_count)) / (v_count - 1) + + for i in list(range(len(coordinates))): + + # Pull out this coordinate + x, y, z = coordinates[i] + + # Is this within the mask? + if mask[x, y, z] > 0: + # For each xyz dimension calculate the squared + # difference of this voxel and the next + + in_range = (x < dimensions[0] - 1) + in_mask = in_range and (mask[x + 1, y, z] > 0) + included = in_mask and (~np.isnan(vol[x + 1, y, z])) + if included: + d_sum[0] += vol[x, y, z] - vol[x + 1, y, z] + d_sq[0] += (vol[x, y, z] - vol[x + 1, y, z]) ** 2 + d_count[0] += 1 + + in_range = (y < dimensions[1] - 1) + in_mask = in_range and (mask[x, y + 1, z] > 0) + included = in_mask and (~np.isnan(vol[x, y + 1, z])) + if included: + d_sum[1] += vol[x, y, z] - vol[x, y + 1, z] + d_sq[1] += (vol[x, y, z] - vol[x, y + 1, z]) ** 2 + d_count[1] += 1 + + in_range = (z < dimensions[2] - 1) + in_mask = in_range and (mask[x, y, z + 1] > 0) + included = in_mask and (~np.isnan(vol[x, y, z + 1])) + if included: + d_sum[2] += vol[x, y, z] - vol[x, y, z + 1] + d_sq[2] += (vol[x, y, z] - vol[x, y, z + 1]) ** 2 + d_count[2] += 1 + + # Find the variance + d_var = np.divide((d_sq - np.divide(np.power(d_sum, 2), + d_count)), (np.add(d_count, -1))) + + o_var = np.divide(-1, (4 * np.log(1 - (0.5 * d_var / v_var)))) + fwhm3 = np.sqrt(o_var) * 2 * np.sqrt(2 * np.log(2)) + + # Calculate the proportion of std relative to the mean + std_proportion = np.nanstd(fwhm3) / np.nanmean(fwhm3) + print(fwhm3) + assert std_proportion < 0.25, 'Variance is inconsistent across dim' + + +def test_mask_brain(): + + # Inputs for generate_signal + dimensions = np.array([10, 10, 10]) # What is the size of the brain + feature_size = [2] + feature_type = ['cube'] + feature_coordinates = np.array( + [[4, 4, 4]]) + signal_magnitude = [30] + + # Generate a volume representing the location and quality of the signal + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Mask the volume to be the same shape as a brain + mask, _ = sim.mask_brain(dimensions, mask_self=None,) + brain = volume * mask + + assert np.sum(brain != 0) == np.sum(volume != 0), "Masking did not work" + assert brain[0, 0, 0] == 0, "Masking did not work" + assert brain[4, 4, 4] != 0, "Masking did not work" + + feature_coordinates = np.array( + [[1, 1, 1]]) + + volume = sim.generate_signal(dimensions=dimensions, + feature_coordinates=feature_coordinates, + feature_type=feature_type, + feature_size=feature_size, + signal_magnitude=signal_magnitude, + ) + + # Mask the volume to be the same shape as a brain + mask, _ = sim.mask_brain(dimensions, mask_self=None, ) + brain = volume * mask + + assert np.sum(brain != 0) < np.sum(volume != 0), "Masking did not work" + + # Test that you can load the default + dimensions = np.array([100, 100, 100]) + mask, template = sim.mask_brain(dimensions, mask_self=False) + + assert mask[20, 80, 50] == 0, 'Masking didn''t work' + assert mask[25, 80, 50] == 1, 'Masking didn''t work' + assert int(template[25, 80, 50] * 100) == 57, 'Template not correct' + + # Check that you can mask self + mask_self, template_self = sim.mask_brain(template, mask_self=True) + + assert (template_self - template).sum() < 1e2, 'Mask self error' + assert (mask_self - mask).sum() == 0, 'Mask self error' + + +def test_calc_noise(): + + # Inputs for functions + onsets = [10, 30, 50, 70, 90] + event_durations = [6] + tr_duration = 2 + duration = 200 + temporal_res = 100 + tr_number = int(np.floor(duration / tr_duration)) + dimensions_tr = np.array([10, 10, 10, tr_number]) + + # Preset the noise dict + nd_orig = sim._noise_dict_update({}) + + # Create the time course for the signal to be generated + stimfunction = sim.generate_stimfunction(onsets=onsets, + event_durations=event_durations, + total_time=duration, + temporal_resolution=temporal_res, + ) + + # Mask the volume to be the same shape as a brain + mask, template = sim.mask_brain(dimensions_tr, mask_self=None) + stimfunction_tr = stimfunction[::int(tr_duration * temporal_res)] + + nd_orig['matched'] = 0 + noise = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + ) + + # Check the spatial noise match + nd_orig['matched'] = 1 + noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + iterations=[50, 0] + ) + + # Calculate the noise parameters from this newly generated volume + nd_new = sim.calc_noise(noise, mask, template) + nd_matched = sim.calc_noise(noise_matched, mask, template) + + # Check the values are reasonable" + assert nd_new['snr'] > 0, 'snr out of range' + assert nd_new['sfnr'] > 0, 'sfnr out of range' + assert nd_new['auto_reg_rho'][0] > 0, 'ar out of range' + + # Check that the dilation increases SNR + no_dilation_snr = sim._calc_snr(noise_matched, + mask, + dilation=0, + reference_tr=tr_duration, + ) + + assert nd_new['snr'] > no_dilation_snr, "Dilation did not increase SNR" + + # Check that template size is in bounds + with pytest.raises(ValueError): + sim.calc_noise(noise, mask, template * 2) + + # Check that Mask is set is checked + with pytest.raises(ValueError): + sim.calc_noise(noise, None, template) + + # Check that it can deal with missing noise parameters + temp_nd = sim.calc_noise(noise, mask, template, noise_dict={}) + assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' + + temp_nd = sim.calc_noise(noise, mask, template, noise_dict=None) + assert temp_nd['voxel_size'][0] == 1, 'Default voxel size not set' + + # Check that the fitting worked + snr_diff = abs(nd_orig['snr'] - nd_new['snr']) + snr_diff_match = abs(nd_orig['snr'] - nd_matched['snr']) + assert snr_diff > snr_diff_match, 'snr fit incorrectly' + + # Test that you can generate rician and exponential noise + sim._generate_noise_system(dimensions_tr, + 1, + 1, + spatial_noise_type='exponential', + temporal_noise_type='rician', + ) + + # Check the temporal noise match + nd_orig['matched'] = 1 + noise_matched = sim.generate_noise(dimensions=dimensions_tr[0:3], + stimfunction_tr=stimfunction_tr, + tr_duration=tr_duration, + template=template, + mask=mask, + noise_dict=nd_orig, + iterations=[0, 50] + ) + + nd_matched = sim.calc_noise(noise_matched, mask, template) + + sfnr_diff = abs(nd_orig['sfnr'] - nd_new['sfnr']) + sfnr_diff_match = abs(nd_orig['sfnr'] - nd_matched['sfnr']) + assert sfnr_diff > sfnr_diff_match, 'sfnr fit incorrectly' + + ar1_diff = abs(nd_orig['auto_reg_rho'][0] - nd_new['auto_reg_rho'][0]) + ar1_diff_match = abs(nd_orig['auto_reg_rho'][0] - nd_matched[ + 'auto_reg_rho'][0]) + assert ar1_diff > ar1_diff_match, 'AR1 fit incorrectly' + + # Check that you can calculate ARMA for a single voxel + vox = noise[5, 5, 5, :] + arma = sim._calc_ARMA_noise(vox, + None, + sample_num=2, + ) + assert len(arma) == 2, "Two outputs not given by ARMA" diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py new file mode 100644 index 000000000..f37c51a7e --- /dev/null +++ b/tests/utils/test_utils.py @@ -0,0 +1,396 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + + +def test_tri_sym_convert(): + from brainiak.utils.utils import from_tri_2_sym, from_sym_2_tri + import numpy as np + + sym = np.random.rand(3, 3) + tri = from_sym_2_tri(sym) + assert tri.shape[0] == 6,\ + "from_sym_2_tri returned wrong result!" + sym1 = from_tri_2_sym(tri, 3) + assert sym1.shape[0] == sym1.shape[1],\ + "from_tri_2_sym returned wrong shape!" + tri1 = from_sym_2_tri(sym1) + assert np.array_equiv(tri, tri1),\ + "from_sym_2_tri returned wrong result!" + + +def test_sumexp(): + from brainiak.utils.utils import sumexp_stable + import numpy as np + + data = np.array([[1, 1], [0, 1]]) + sums, maxs, exps = sumexp_stable(data) + assert sums.size == data.shape[1], ( + "Invalid sum(exp(v)) computation (wrong # samples in sums)") + assert exps.shape[0] == data.shape[0], ( + "Invalid exp(v) computation (wrong # features)") + assert exps.shape[1] == data.shape[1], ( + "Invalid exp(v) computation (wrong # samples)") + assert maxs.size == data.shape[1], ( + "Invalid max computation (wrong # samples in maxs)") + + +def test_concatenate_not_none(): + from brainiak.utils.utils import concatenate_not_none + import numpy as np + arrays = [None] * 5 + + arrays[1] = np.array([0, 1, 2]) + arrays[3] = np.array([3, 4]) + + r = concatenate_not_none(arrays, axis=0) + + assert np.all(np.arange(5) == r), ( + "Invalid concatenation of a list of arrays") + + +def test_cov2corr(): + from brainiak.utils.utils import cov2corr + import numpy as np + cov = np.array([[4, 3, 0], [3, 9, 0], [0, 0, 1]]) + corr = cov2corr(cov) + assert np.allclose(corr, + np.array([[1, 0.5, 0], [0.5, 1, 0], [0, 0, 1]])), ( + "Converting from covariance matrix to correlation incorrect") + + +def test_ReadDesign(): + from brainiak.utils.utils import ReadDesign + import numpy as np + import os.path + file_path = os.path.join(os.path.dirname(__file__), "example_design.1D") + design = ReadDesign(fname=file_path, include_orth=False, + include_pols=False) + assert design, 'Failed to read design matrix' + assert design.reg_nuisance is None, \ + 'Nuiance regressor is not None when include_orth and include_pols are'\ + ' both set to False' + read = ReadDesign() + assert read, 'Failed to initialize an instance of the class' + design = ReadDesign(fname=file_path, include_orth=True, include_pols=True) + assert np.size(design.cols_nuisance) == 10, \ + 'Mistake in counting the number of nuiance regressors' + assert np.size(design.cols_task) == 17, \ + 'Mistake in counting the number of task conditions' + assert (np.shape(design.reg_nuisance)[0] + == np.shape(design.design_task)[0] + ), 'The number of time points in nuiance regressor does not match'\ + ' that of task response' + + +def test_gen_design(): + from brainiak.utils.utils import gen_design + import numpy as np + import os.path + files = {'FSL1': 'example_stimtime_1_FSL.txt', + 'FSL2': 'example_stimtime_2_FSL.txt', + 'AFNI1': 'example_stimtime_1_AFNI.txt', + 'AFNI2': 'example_stimtime_2_AFNI.txt'} + for key in files.keys(): + files[key] = os.path.join(os.path.dirname(__file__), files[key]) + design1 = gen_design(stimtime_files=files['FSL1'], scan_duration=[48, 20], + TR=2, style='FSL') + assert design1.shape == (34, 1), 'Returned design matrix has wrong shape' + assert design1[24] == 0, ( + "gen_design should generated design matrix for each run separately " + "and concatenate them.") + design2 = gen_design(stimtime_files=[files['FSL1'], files['FSL2']], + scan_duration=[48, 20], TR=2, style='FSL') + assert design2.shape == (34, 2), 'Returned design matrix has wrong shape' + design3 = gen_design(stimtime_files=files['FSL1'], scan_duration=68, TR=2, + style='FSL') + assert design3[24] != 0, ( + 'design matrix should be non-zero 8 seconds after an event onset.') + design4 = gen_design(stimtime_files=[files['FSL2']], + scan_duration=[48, 20], TR=2, style='FSL') + assert np.all(np.isclose(design1 * 0.5, design4)), ( + 'gen_design does not treat missing values correctly') + design5 = gen_design(stimtime_files=[files['FSL2']], + scan_duration=[48, 20], TR=1) + assert (np.abs(design4 - design5[::2])).mean() < 0.1, ( + 'design matrices sampled at different frequency do not match' + ' at corresponding time points') + design6 = gen_design(stimtime_files=[files['AFNI1']], + scan_duration=[48, 20], TR=2, style='AFNI') + assert np.all(np.isclose(design1, design6)), ( + 'design matrices generated from AFNI style and FSL style do not match') + design7 = gen_design(stimtime_files=[files['AFNI2']], + scan_duration=[48], TR=2, style='AFNI') + assert np.all(design7 == 0.0), ( + 'A negative stimulus onset of AFNI style should result in an all-zero' + + ' design matrix') + + +def test_center_mass_exp(): + from brainiak.utils.utils import center_mass_exp + import numpy as np + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp([1, 2]) + assert ('interval must be a tuple' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((1, 2, 3)) + assert ('interval must be length two' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((-2, -1)) + assert ('interval_left must be non-negative' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((-2, 3)) + assert ('interval_left must be non-negative' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((3, 3)) + assert ('interval_right must be bigger than interval_left' + in str(excinfo.value)) + + with pytest.raises(AssertionError) as excinfo: + result = center_mass_exp((1, 2), -1) + assert ('scale must be positive' + in str(excinfo.value)) + + result = center_mass_exp((0, np.inf), 2.0) + assert np.isclose(result, 2.0), 'center of mass '\ + 'incorrect for the whole distribution' + result = center_mass_exp((1.0, 1.0+2e-10)) + assert np.isclose(result, 1.0+1e-10), 'for a small '\ + 'enough interval, the center of mass should be '\ + 'close to its mid-point' + + +def test_p_from_null(): + import numpy as np + from brainiak.utils.utils import p_from_null + + # Create random null and observed value in tail + null = np.random.randn(10000) + observed = np.ceil(np.percentile(null, 97.5) * 1000) / 1000 + + # Check that we catch improper side + with pytest.raises(ValueError): + _ = p_from_null(observed, null, side='wrong') + + # Check two-tailed p-value for observed + p_ts = p_from_null(observed, null) + assert np.isclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for observed + p_right = p_from_null(observed, null, side='right') + assert np.isclose(p_right, 0.025, atol=1e-02) + assert np.isclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for observed + p_left = p_from_null(observed, null, side='left') + assert np.isclose(p_left, 0.975, atol=1e-02) + assert np.isclose(1 - p_left, p_right, atol=1e-02) + assert np.isclose(1 - p_left, p_ts / 2, atol=1e-02) + + # Check 2-dimensional input (i.e., samples by voxels) + null = np.random.randn(10000, 3) + observed = np.ceil(np.percentile(null, 97.5, axis=0) * 1000) / 1000 + + # Check two-tailed p-value for observed + p_ts = p_from_null(observed, null, axis=0) + assert np.allclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for observed + p_right = p_from_null(observed, null, side='right', axis=0) + assert np.allclose(p_right, 0.025, atol=1e-02) + assert np.allclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for observed + p_left = p_from_null(observed, null, side='left', axis=0) + assert np.allclose(p_left, 0.975, atol=1e-02) + assert np.allclose(1 - p_left, p_right, atol=1e-02) + assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) + + # Check for exact test + p_ts = p_from_null(observed, null, exact=True, axis=0) + assert np.allclose(p_ts, 0.05, atol=1e-02) + + # Check two-tailed p-value for exact + p_right = p_from_null(observed, null, side='right', + exact=True, axis=0) + assert np.allclose(p_right, 0.025, atol=1e-02) + assert np.allclose(p_right, p_ts / 2, atol=1e-02) + + # Check two-tailed p-value for exact + p_left = p_from_null(observed, null, side='left', + exact=True, axis=0) + assert np.allclose(p_left, 0.975, atol=1e-02) + assert np.allclose(1 - p_left, p_right, atol=1e-02) + assert np.allclose(1 - p_left, p_ts / 2, atol=1e-02) + + +def test_phase_randomize(): + import numpy as np + from scipy.fftpack import fft + from scipy.stats import pearsonr + from brainiak.utils.utils import phase_randomize + + data = np.repeat(np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], + 30, axis=1), + 20, axis=2) + assert np.array_equal(data[..., 0], data[..., 1]) + + # Phase-randomize data across subjects (same across voxels) + shifted_data = phase_randomize(data, voxelwise=False, random_state=1) + assert shifted_data.shape == data.shape + assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) + assert not np.array_equal(shifted_data[..., 0], data[..., 0]) + + # Check that uneven n_TRs doesn't explode + _ = phase_randomize(data[:-1, ...]) + + # Check that random_state returns same shifts + shifted_data_ = phase_randomize(data, voxelwise=False, random_state=1) + assert np.array_equal(shifted_data, shifted_data_) + + shifted_data_ = phase_randomize(data, voxelwise=False, random_state=2) + assert not np.array_equal(shifted_data, shifted_data_) + + # Phase-randomize subjects and voxels + shifted_data = phase_randomize(data, voxelwise=True, random_state=1) + assert shifted_data.shape == data.shape + assert not np.array_equal(shifted_data[..., 0], shifted_data[..., 1]) + assert not np.array_equal(shifted_data[..., 0], data[..., 0]) + assert not np.array_equal(shifted_data[:, 0, 0], shifted_data[:, 1, 0]) + + # Try with 2-dimensional input + shifted_data = phase_randomize(data[..., 0], + voxelwise=True, + random_state=1) + assert shifted_data.ndim == 2 + assert not np.array_equal(shifted_data[:, 0], shifted_data[:, 1]) + + # Create correlated noisy data + corr_data = np.repeat(np.random.randn(60)[:, np.newaxis, np.newaxis], + 2, axis=2) + np.random.randn(60, 1, 2) + + # Get correlation and frequency domain for data + corr_r = pearsonr(corr_data[:, 0, 0], + corr_data[:, 0, 1])[0] + corr_freq = fft(corr_data, axis=0) + + # Phase-randomize time series and get correlation/frequency + shifted_data = phase_randomize(corr_data) + shifted_r = pearsonr(shifted_data[:, 0, 0], + shifted_data[:, 0, 1])[0] + shifted_freq = fft(shifted_data, axis=0) + + # Check that phase-randomization reduces correlation + assert np.abs(shifted_r) < np.abs(corr_r) + + # Check that amplitude spectrum is preserved + assert np.allclose(np.abs(shifted_freq), np.abs(corr_freq)) + + +def test_check_timeseries_input(): + import numpy as np + from itertools import combinations + from brainiak.utils.utils import _check_timeseries_input + + # Set a fixed vector for comparison + vector = np.random.randn(60) + + # List of subjects with one voxel/ROI + list_1d = [vector for _ in np.arange(10)] + (data_list_1d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_1d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Array of subjects with one voxel/ROI + array_2d = np.hstack([vector[:, np.newaxis] + for _ in np.arange(10)]) + (data_array_2d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_2d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # List of 2-dimensional arrays + list_2d = [vector[:, np.newaxis] for _ in np.arange(10)] + (data_list_2d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_2d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Check if lists have mismatching size + list_bad = [list_2d[0][:-1, :]] + list_2d[1:] + with pytest.raises(ValueError): + (data_list_bad, _, _, _) = _check_timeseries_input(list_bad) + + # List of 3-dimensional arrays + list_3d = [vector[:, np.newaxis, np.newaxis] + for _ in np.arange(10)] + (data_list_3d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_3d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # 3-dimensional array + array_3d = np.dstack([vector[:, np.newaxis] + for _ in np.arange(10)]) + (data_array_3d, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_3d) + assert n_TRs == 60 + assert n_voxels == 1 + assert n_subjects == 10 + + # Check that 4-dimensional input array throws error + array_4d = array_3d[..., np.newaxis] + with pytest.raises(ValueError): + (data_array_4d, _, _, _) = _check_timeseries_input(array_4d) + + # Check they're the same + for pair in combinations([data_list_1d, data_array_2d, + data_list_2d, data_list_3d, + data_array_3d], 2): + assert np.array_equal(pair[0], pair[1]) + + # List of multivoxel arrays + matrix = np.random.randn(60, 30) + list_mv = [matrix + for _ in np.arange(10)] + (data_list_mv, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(list_mv) + assert n_TRs == 60 + assert n_voxels == 30 + assert n_subjects == 10 + + # 3-dimensional array with multiple voxels + array_mv = np.dstack([matrix for _ in np.arange(10)]) + (data_array_mv, n_TRs, + n_voxels, n_subjects) = _check_timeseries_input(array_mv) + assert n_TRs == 60 + assert n_voxels == 30 + assert n_subjects == 10 + + assert np.array_equal(data_list_mv, data_array_mv) From 458823fe978cd57fb485f4b21d316b988c47e3d8 Mon Sep 17 00:00:00 2001 From: hrichard Date: Thu, 18 Apr 2019 16:52:52 +0200 Subject: [PATCH 07/24] Add tests for fastSRM --- brainiak/funcalign/fastsrm.py | 104 ++++++--- tests/funcalign/test_fastsrm.py | 377 ++++++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+), 27 deletions(-) create mode 100644 tests/funcalign/test_fastsrm.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index c093a1be3..da45a4a2a 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -80,23 +80,37 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir= reduced data """ if atlas is None and inv_atlas is None: - AssertionError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") + raise ValueError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") if inv_atlas is None and atlas is not None: + # this means that it is a deterministic atlas + n_voxels = atlas.shape[0] atlas_values = np.unique(atlas) if 0 in atlas_values: atlas_values = atlas_values[1:] data = np.load(img) + + if data.shape[1] != n_voxels: + raise ValueError("%s have %i number of voxels and the atlas has %i number of voxels." + "This is not compatible." % (img, data.shape[1], n_voxels)) + reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T else: + # this means that it is a deterministic atlas + n_voxels = inv_atlas.shape[0] data = np.load(img) + + if data.shape[1] != n_voxels: + raise ValueError("%s have %i number of voxels and the atlas has %i number of voxels." + "This is not compatible." % (img, data.shape[1], n_voxels)) + reduced_data = data.dot(inv_atlas) if low_ram: name = hashlib.md5(img.encode()).hexdigest() path = os.path.join(temp_dir, "reduced_data_" + name) np.save(path, reduced_data) - return path + return path + ".npy" else: return reduced_data @@ -147,6 +161,8 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 """ + if type(atlas) != np.ndarray: + raise ValueError("atlas should be of type np.ndarray") if len(atlas.shape) == 2: A = None @@ -169,8 +185,11 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): if low_ram: reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) else: - n_timeframes, n_voxels = reduced_data_list[0].shape - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_voxels)) + if len(np.array(reduced_data_list).shape) == 1: + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) + else: + n_timeframes, n_supervoxels = np.array(reduced_data_list).shape[1:] + reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_supervoxels)) return reduced_data_list @@ -209,15 +228,20 @@ def _reduced_space_compute_shared_response(reduced_data_list, n_subjects, n_sessions = reduced_data_list.shape[:2] if type(reduced_data_list[0, 0]) == np.ndarray: low_ram = False - elif type(reduced_data_list[0, 0]) == str: + elif (type(reduced_data_list[0, 0]) == str or + type(reduced_data_list[0, 0]) == np.str_ or + type(reduced_data_list[0, 0] == np.str)): low_ram = True else: - AssertionError("Reduced data are stored using type %s which is neither an ndarray or str" - % type(reduced_data_list[0, 0])) - low_ram = False + print("data", reduced_data_list[0, 0]) + print(type(reduced_data_list[0, 0])) + raise ValueError("Reduced data are stored using type %s which is neither np.ndarray or str" + % type(reduced_data_list[0, 0])) s = [None] * n_sessions + # This is just to check that all subjects have same number of timeframes in a given session + list_n_timeframes = [None] * n_sessions for n in range(n_subjects): for m in range(n_sessions): if low_ram: @@ -225,8 +249,22 @@ def _reduced_space_compute_shared_response(reduced_data_list, else: data_nm = reduced_data_list[n, m] + n_timeframes, n_supervoxels = data_nm.shape + + if n_supervoxels < n_components: + raise ValueError("The number of regions in the atlas %i is smaller than " + "the number of components %i of fastSRM" % (n_supervoxels, n_components)) + + if list_n_timeframes[m] is None: + list_n_timeframes[m] = n_timeframes + elif list_n_timeframes[m] != n_timeframes: + raise ValueError("Subject %i Session %i does not have the same number of timeframes " + "as Subject %i Session %i" % (n, m, 0, m)) + + if n_timeframes < n_components: + raise ValueError("Number of timeframes is shorter than number of components") + if reduced_basis_list is None: - n_timeframes, n_supervoxels = data_nm.shape reduced_basis_list = [] for subject in range(n_subjects): q = np.eye(n_components, n_supervoxels) @@ -236,10 +274,10 @@ def _reduced_space_compute_shared_response(reduced_data_list, if s[m] is None: s[m] = data_nm.dot(basis_n.T) else: - s[m] = s + data_nm.dot(basis_n.T) + s[m] = s[m] + data_nm.dot(basis_n.T) for m in range(n_sessions): - s[m] = float(s[m]) / n_subjects + s[m] = s[m] / float(n_subjects) return s @@ -308,7 +346,7 @@ def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): else: path = os.path.join(temp_dir, "basis_" % subject_number) np.save(path, basis_i) - return path + return path + ".npy" def _compute_subject_basis(corr_mat): @@ -369,13 +407,15 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): if type(reduced_data_list[0, 0]) == np.ndarray: low_ram = False - elif type(reduced_data_list[0, 0]) == str: + elif (type(reduced_data_list[0, 0]) == str or + type(reduced_data_list[0, 0]) == np.str_ or + type(reduced_data_list[0, 0] == np.str)): low_ram = True else: - AssertionError("Reduced data are stored using type %s which is neither np.ndarray or str" - % type(reduced_data_list[0, 0])) - low_ram = False - + print("data", reduced_data_list[0, 0]) + print(type(reduced_data_list[0, 0])) + raise ValueError("Reduced data are stored using type %s which is neither np.ndarray or str" + % type(reduced_data_list[0, 0])) n_subjects, n_sessions = reduced_data_list.shape[:2] shared_response = _reduced_space_compute_shared_response( @@ -611,11 +651,13 @@ def __init__(self, "reconstructed data will therefore be kept in memory." "This can lead to memory errors when the number of subjects " "and/or sessions is large.") + self.temp_dir = None + self.low_ram = False if temp_dir is not None: if not os.path.exists(os.path.join(temp_dir, "fastsrm")): os.mkdir(os.path.join(temp_dir, "fastsrm")) - self.temp_dir = os.path.join(temp_dir, "fastsrm") + self.temp_dir = os.path.join(temp_dir, "fastsrm") # Remove files in temp folder paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) @@ -646,18 +688,23 @@ def fit(self, imgs): if self.temp_dir is not None: # Remove former basis in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "basis") + "*") + paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) for path in paths: os.remove(path) if type(imgs) != np.ndarray: - AssertionError("imgs should be of type np.ndarray but is of type %s" - % type(imgs)) + raise ValueError("imgs should be of type np.ndarray but is of type %s" + % type(imgs)) if len(imgs.shape) != 2: - AssertionError("imgs should be an array of shape [n_subjects, n_sessions] " - "but its shape is of size %i" - % len(imgs.shape)) + raise ValueError("imgs should be an array of shape [n_subjects, n_sessions] " + "but its shape is of size %i" + % len(imgs.shape)) + + n_subjects, n_sessions = imgs.shape + + if n_subjects <= 1: + raise ValueError("The number of subjects should be greater than 1") if self.verbose is True: n_subjects, n_sessions = imgs.shape @@ -675,7 +722,7 @@ def fit(self, imgs): temp_dir=self.temp_dir ) - if self.verbose: + if self.verbose is True: logger.info("[FastSRM.fit] Finds shared response using reduced data") shared_response_list = fast_srm( @@ -684,7 +731,7 @@ def fit(self, imgs): n_components=self.n_components, ) - if self.verbose: + if self.verbose is True: print("[FastSRM.fit] Finds basis using full data and shared response") if self.n_jobs == 1: @@ -696,7 +743,7 @@ def fit(self, imgs): else: path = os.path.join(self.temp_dir, "basis_%i" % i) np.save(path, basis_i) - basis.append(path) + basis.append(path + ".npy") del basis_i else: Parallel(n_jobs=self.n_jobs)( @@ -760,6 +807,9 @@ def transform(self, imgs, subjects_indexes=None): shared_response_list : list of array, element i has shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ + if self.basis_list is None: + raise NotFittedError("The model fit has not been run yet.") + if subjects_indexes is None: subjects_indexes = np.arange(len(imgs)) else: diff --git a/tests/funcalign/test_fastsrm.py b/tests/funcalign/test_fastsrm.py new file mode 100644 index 000000000..9b8addcb3 --- /dev/null +++ b/tests/funcalign/test_fastsrm.py @@ -0,0 +1,377 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from sklearn.exceptions import NotFittedError +import pytest +import numpy as np +import hashlib +import tempfile +from brainiak.funcalign.fastsrm import FastSRM, reduce_data, _reduced_space_compute_shared_response +from brainiak.funcalign.fastsrm import _compute_basis_subject_online, fast_srm +import os +from numpy.testing import assert_array_almost_equal + + +def to_path(X, dirpath): + """ + Save list of list of array to path and returns the path_like array + Parameters + ---------- + X: list of list of array + input data + dirpath: str + dirpath + Returns + ------- + paths: array of str + path arrays where all data are stored + """ + paths = [] + for i, sessions in enumerate(X): + sessions_path = [] + for j, session in enumerate(sessions): + pth = "%i_%i" % (i, j) + np.save(os.path.join(dirpath, pth), session) + sessions_path.append(os.path.join(dirpath, pth + ".npy")) + paths.append(sessions_path) + return np.array(paths) + + +def generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, noise_level=0.1): + n_sessions = len(n_timeframes) + cumsum_timeframes = np.cumsum([0] + n_timeframes) + slices_timeframes = [slice(cumsum_timeframes[i], cumsum_timeframes[i + 1]) for i in range(n_sessions)] + + # Create a Shared response S with K = 3 + theta = np.linspace(-4 * np.pi, 4 * np.pi, int(np.sum(n_timeframes))) + z = np.linspace(-2, 2, int(np.sum(n_timeframes))) + r = z ** 2 + 1 + x = r * np.sin(theta) + y = r * np.cos(theta) + + S = np.vstack((x, y, z)) + + # Generate fake data + W = [] + X = [] + for subject in range(n_subjects): + Q, R = np.linalg.qr(np.random.random((n_voxels, n_components))) + W.append(Q.T) + X_ = [] + for session in range(n_sessions): + S_s = S[:, slices_timeframes[session]] + S_s = S_s - np.mean(S_s, axis=1, keepdims=True) + noise = noise_level * np.random.random((n_voxels, n_timeframes[session])) + noise = noise - np.mean(noise, axis=1, keepdims=True) + data = Q.dot(S_s) \ + + noise + X_.append(data.T) + X.append(X_) + + # create paths such that paths[i, j] contains data of subject i during session j + paths = to_path(X, datadir) + S = [(S[:, s] - np.mean(S[:, s], axis=1, keepdims=True)).T for s in slices_timeframes] + return paths, W, S + + +def test_reduced_data(): + with tempfile.TemporaryDirectory() as datadir: + + # We authorize different timeframes for different sessions but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 2 + n_components = 3 # number of components used for SRM model + n_supervoxels = 10 # number of components of the atlas + n_jobs = 1 + n_sessions = len(n_timeframes) + + np.random.seed(0) + paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir) + + # Test if generated data has the good shape + for subject in range(n_subjects): + for session in range(n_sessions): + assert np.load(paths[subject, session]).shape == (n_timeframes[session], n_voxels) + + # Test if reduced data has the good shape + + # probabilistic atlas + atlas = np.random.rand(n_supervoxels, n_voxels) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False, temp_dir=None) + + for subject in range(n_subjects): + for session in range(n_sessions): + assert data[subject, session].shape == (n_timeframes[session], n_supervoxels) + + # deterministic atlas + det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) + n_unique = len(np.unique(det_atlas)[1:]) + while n_unique != n_supervoxels: + det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) + n_unique = len(np.unique(det_atlas)[1:]) + + data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, low_ram=True, temp_dir=datadir) + for subject in range(n_subjects): + for session in range(n_sessions): + assert np.load(data[subject, session]).shape == (n_timeframes[session], n_supervoxels) + + # test atlas that reduces nothing + atlas = np.arange(1, n_voxels + 1) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False) + for i in range(n_subjects): + for j in range(n_sessions): + assert_array_almost_equal(data[i, j], np.load(paths[i, j])) + + # test atlas that reduces everything + atlas = np.ones(n_voxels) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False) + for i in range(n_subjects): + for j in range(n_sessions): + assert_array_almost_equal(data[i, j].flatten(), np.mean(np.load(paths[i, j]), axis=1)) + + +def test_reduced_data_srm(): + with tempfile.TemporaryDirectory() as datadir: + np.random.seed(0) + + # We authorize different timeframes for different sessions but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 5 + n_components = 3 # number of components used for SRM model + n_jobs = 1 + n_sessions = len(n_timeframes) + + np.random.seed(0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, 0) + + # Test if generated data has the good shape + for subject in range(n_subjects): + for session in range(n_sessions): + assert np.load(paths[subject, session]).shape == (n_timeframes[session], n_voxels) + + # Test if generated basis have good shape + assert len(W) == n_subjects + for w in W: + assert w.shape == (n_components, n_voxels) + + assert len(S) == n_sessions + for j, s in enumerate(S): + assert s.shape == (n_timeframes[j], n_components) + + atlas = np.arange(1, n_voxels + 1) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False, temp_dir=None) + + # Test if shared response has the good shape + shared_response_list = _reduced_space_compute_shared_response(data, + reduced_basis_list=None, + n_components=n_components) + assert len(shared_response_list) == n_sessions + + for session in range(n_sessions): + assert shared_response_list[session].shape == (n_timeframes[session], n_components) + + # Test basis from shared response + for i, sessions in enumerate(paths): + basis = _compute_basis_subject_online(sessions, S) + # test shape + assert basis.shape == (n_components, n_voxels) + # test orthogonality + assert np.allclose(basis.dot(basis.T), np.eye(n_components)) + # test correctness + assert_array_almost_equal(basis, W[i], 2) + + # Test reduced_data_shared_response + shared_response_list = _reduced_space_compute_shared_response(data, + reduced_basis_list=W, + n_components=n_components + ) + for session in range(n_sessions): + S_real = np.mean([data[i, session].dot(W[i].T) for i in range(n_subjects)], axis=0) + assert_array_almost_equal(shared_response_list[session], S_real) + assert_array_almost_equal(shared_response_list[session], S[session]) + + # Test fast_srm for reduced_data + shared_response_list = fast_srm(data, n_components=n_components) + + for i, sessions in enumerate(paths): + basis = _compute_basis_subject_online(sessions, shared_response_list) + for j, session in enumerate(sessions): + assert_array_almost_equal(shared_response_list[j].dot(basis), np.load(paths[i, j])) + + +def test_fastsrm_class(): + with tempfile.TemporaryDirectory() as datadir: + np.random.seed(0) + + # We authorize different timeframes for different sessions but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 5 + n_components = 3 # number of components used for SRM model + n_jobs = 1 + n_sessions = len(n_timeframes) + + np.random.seed(0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, 0) + + atlas = np.arange(1, n_voxels + 1) + srm = FastSRM(atlas=atlas, + n_components=n_components, + n_iter=10, + temp_dir=datadir, + low_ram=True, + verbose=True) + + srm.fit(paths) + basis = srm.basis_list + shared_response = srm.transform(paths) + + for i in range(n_subjects): + for j in range(n_sessions): + basis_i = np.load(basis[i]) + assert_array_almost_equal(shared_response[j].dot(basis_i), np.load(paths[i, j])) + + + + + + + + + + + + + + + + + # for subject in range(n_subjects): + # for session in range(n_sessions): + # assert data[subject, session].shape == (n_timeframes, n_supervoxels) + # + # # Try with deterministic atlas + # det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) + # n_unique = len(np.unique(det_atlas)[1:]) + # while n_unique != n_supervoxels: + # det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) + # n_unique = len(np.unique(det_atlas)[1:]) + # + # data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, low_ram=True, temp_dir=datadir) + # for subject in range(n_subjects): + # for session in range(n_sessions): + # assert np.load(data[subject, session]).shape == (n_timeframes, n_supervoxels) + + + + + # def test_can_instantiate(): + # s = FastSRM(atlas=np.random.rand(10, 10)) + # assert s, "Invalid SRM instance!" + # + # + # + # s = FastSRM(atlas=np.ones(n_voxels), n_iter=5, n_components=n_components) + # assert s, "Invalid SRM instance!" + # + # # Create a Shared response S with K = 3 + # theta = np.linspace(-4 * np.pi, 4 * np.pi, n_timeframes) + # z = np.linspace(-2, 2, n_timeframes) + # r = z**2 + 1 + # x = r * np.sin(theta) + # y = r * np.cos(theta) + # + # S = np.vstack((x, y, z)) + # + # X = [] + # W = [] + # Q, R = np.linalg.qr(np.random.random((n_voxels, n_timeframes))) + # W.append(Q) + # + # data = Q.dot(S) + 0.1*np.random.random((n_voxels, n_timeframes)) + # X.append([data.T]) + # + # paths = to_path(X, datadir) + # + # # Check that transform does NOT run before fitting the model + # with pytest.raises(NotFittedError): + # s.transform(paths) + # print("Test: transforming before fitting the model") + # + # # Check that it does NOT run with 1 subject + # with pytest.raises(ValueError): + # s.fit(paths) + # print("Test: running SRM with 1 subject") + # + # for subject in range(1, n_subjects): + # Q, R = np.linalg.qr(np.random.random((n_voxels, n_components))) + # W.append(Q) + # data = Q.dot(S) + 0.1*np.random.random((n_voxels, samples)) + # X.append([data.T]) + # + # # Check that runs with 2 subject + # paths = to_path(X, datadir) + # s.fit(paths) + # shared_response = s.transform(paths)[0] + # from pathlib import Path + # sr_v0_4 = np.load(Path(__file__).parent / "sr_v0_4.npz")['sr'] + # assert(np.allclose(sr_v0_4, shared_response)) + # + # assert len(s.basis_list) == n_subjects, ( + # "Invalid computation of SRM! (wrong # subjects in W)") + # for subject in range(n_subjects): + # assert s.basis_list[subject].shape[0] == n_voxels, ( + # "Invalid computation of SRM! (wrong # voxels in W)") + # assert s.basis_list[subject].shape[1] == n_components, ( + # "Invalid computation of SRM! (wrong # n_components in W)") + # ortho = np.linalg.norm(s.basis_list[subject].T.dot(s.basis_list[subject]) + # - np.eye(s.basis_list[subject].shape[1]), + # 'fro') + # assert ortho < 1e-7, "A Wi mapping is not orthonormal in SRM." + # difference = np.linalg.norm(X[subject] - s.basis_list[subject].dot(shared_response), + # 'fro') + # datanorm = np.linalg.norm(X[subject], 'fro') + # assert difference/datanorm < 1.0, "Model seems incorrectly computed." + # assert shared_response.shape[0] == n_components, ( + # "Invalid computation of SRM! (wrong # n_components in S)") + # assert shared_response.shape[1] == samples, ( + # "Invalid computation of SRM! (wrong # samples in S)") + # + # # Check that it does run to compute the shared response after the model + # # computation + # new_s = s.transform(paths)[0] + # + # assert len(new_s) == n_subjects, ( + # "Invalid computation of SRM! (wrong # subjects after transform)") + # for subject in range(n_subjects): + # assert new_s[subject].shape[0] == n_components, ( + # "Invalid computation of SRM! (wrong # n_components after transform)") + # assert new_s[subject].shape[1] == samples, ( + # "Invalid computation of SRM! (wrong # samples after transform)") + # + # # Check that it does not run without enough n_timeframes (TRs). + # with pytest.raises(ValueError): + # s.set_params(n_components=(n_timeframes+1)) + # s.fit(paths) + # print("Test: not enough n_timeframes") + # + # # Check that it does not run with different number of n_timeframes (TRs) + # S2 = S[:, :-2] + # data = Q.dot(S2) + # X.append([data.T]) + # paths = to_path(X, datadir) + # with pytest.raises(ValueError): + # s.fit(paths) + # print("Test: different number of n_timeframes per subject") From 73b8b3331ad686f50795d1df9a79bc40a6fde568 Mon Sep 17 00:00:00 2001 From: hrichard Date: Thu, 18 Apr 2019 17:34:14 +0200 Subject: [PATCH 08/24] correct formatting --- brainiak/funcalign/fastsrm.py | 441 ++++++++++++++++++++++---------- tests/funcalign/test_fastsrm.py | 261 +++++++------------ 2 files changed, 386 insertions(+), 316 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index da45a4a2a..bfca3a3ab 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -17,18 +17,17 @@ """ # Author: Hugo Richard (INRIA - Parietal) -# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) and Bertrand Thirion (Inria - Parietal) -# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience Institute) and Javier Turek (Intel Labs) +# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) +# and Bertrand Thirion (Inria - Parietal) +# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience +# Institute) and Javier Turek (Intel Labs) import logging import numpy as np import scipy from sklearn.base import BaseEstimator, TransformerMixin -from sklearn.utils import assert_all_finite from sklearn.exceptions import NotFittedError -from mpi4py import MPI -import sys from joblib import Parallel, delayed import os import glob @@ -41,7 +40,8 @@ logger = logging.getLogger(__name__) -def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir=None): +def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, + temp_dir=None): """Reduce data using given atlas Parameters @@ -49,12 +49,14 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir= img : str path to data. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - atlas : array, shape=[n_supervoxels, n_voxels] or None or array, shape=[n_voxels] + atlas : array, shape=[n_supervoxels, n_voxels] + or None or array, shape=[n_voxels] Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. @@ -70,7 +72,8 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir= low_ram : bool if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number + this increases the number of IO but reduces memory complexity when the + number of subject and number of sessions are large Returns @@ -80,7 +83,8 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir= reduced data """ if atlas is None and inv_atlas is None: - raise ValueError("An atlas or the pseudo inverse of a probabilistic atlas should be provided") + raise ValueError("An atlas or the pseudo inverse of" + " a probabilistic atlas should be provided") if inv_atlas is None and atlas is not None: # this means that it is a deterministic atlas @@ -91,18 +95,23 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, temp_dir= data = np.load(img) if data.shape[1] != n_voxels: - raise ValueError("%s have %i number of voxels and the atlas has %i number of voxels." - "This is not compatible." % (img, data.shape[1], n_voxels)) + raise ValueError("%s have %i number of voxels and" + " the atlas has %i number of voxels." + "This is not compatible." % (img, data.shape[1], + n_voxels)) - reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T + reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) + for c in atlas_values]).T else: # this means that it is a deterministic atlas n_voxels = inv_atlas.shape[0] data = np.load(img) if data.shape[1] != n_voxels: - raise ValueError("%s have %i number of voxels and the atlas has %i number of voxels." - "This is not compatible." % (img, data.shape[1], n_voxels)) + raise ValueError("%s have %i number of voxels and the atlas" + " has %i number of voxels." + "This is not compatible." % (img, data.shape[1], + n_voxels)) reduced_data = data.dot(inv_atlas) @@ -123,8 +132,10 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): ---------- imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 @@ -146,18 +157,22 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): low_ram : bool if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and/or sessions is large + this increases the number of IO but reduces memory complexity when + the number of subject and/or sessions is large Returns ------- reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects + or array, shape=[n_subjects, n_sessions, n_timeframes, n_supervoxels] + Element i, j of the array is a path to the data of subject i collected + during session j. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of + shape=[n_timeframes, n_supervoxels] + n_timeframes and n_supervoxels + are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 """ @@ -183,17 +198,80 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): ) for img in imgs.flatten()) if low_ram: - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) + reduced_data_list = np.reshape(reduced_data_list, + (n_subjects, n_sessions)) else: if len(np.array(reduced_data_list).shape) == 1: - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions)) + reduced_data_list = np.reshape(reduced_data_list, + (n_subjects, n_sessions)) else: n_timeframes, n_supervoxels = np.array(reduced_data_list).shape[1:] - reduced_data_list = np.reshape(reduced_data_list, (n_subjects, n_sessions, n_timeframes, n_supervoxels)) + reduced_data_list = np.reshape(reduced_data_list, + (n_subjects, + n_sessions, + n_timeframes, + n_supervoxels)) return reduced_data_list +def check_shapes(n_supervoxels, n_components, n_timeframes): + """ + Check assumptions about input parameters + + Parameters + ---------- + + n_supervoxels: int + + n_components: int + + n_timeframes: int + + """ + + if n_supervoxels < n_components: + raise ValueError("The number of regions in the atlas " + "%i is smaller than " + "the number of components %i of fastSRM" + % (n_supervoxels, n_components)) + + if n_timeframes < n_components: + raise ValueError("Number of timeframes %i is shorter than " + "number of components %i" % (n_timeframes, + n_components)) + + +def is_low_ram(reduced_data): + """ + Depending on type of reduced_data infer if we are in low-ram mode or not + Parameters + ---------- + reduced_data : str or array, shape=[n_timeframes, n_supervoxels] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of + shape=[n_timeframes, n_supervoxels] + n_timeframes and n_supervoxels are + assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + if type(reduced_data) == np.ndarray: + low_ram = False + elif (type(reduced_data) == str or + type(reduced_data) == np.str_ or + type(reduced_data == np.str)): + low_ram = True + else: + raise ValueError("Reduced data are stored using " + "type %s which is neither np.ndarray or str" + % type(reduced_data[0, 0])) + return low_ram + + def _reduced_space_compute_shared_response(reduced_data_list, reduced_basis_list, n_components=50): @@ -203,15 +281,20 @@ def _reduced_space_compute_shared_response(reduced_data_list, ---------- reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects + or array, shape=[n_subjects, n_sessions, n_timeframes, n_supervoxels] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of + shape=[n_timeframes, n_supervoxels] + n_timeframes and n_supervoxels are + assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - reduced_basis_list : None or list of array, element i has shape=[n_components, n_supervoxels] + reduced_basis_list : None or list of array, element i has + shape=[n_components, n_supervoxels] each subject's reduced basis if None the basis will be generated on the fly @@ -221,26 +304,18 @@ def _reduced_space_compute_shared_response(reduced_data_list, Returns ------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ n_subjects, n_sessions = reduced_data_list.shape[:2] - if type(reduced_data_list[0, 0]) == np.ndarray: - low_ram = False - elif (type(reduced_data_list[0, 0]) == str or - type(reduced_data_list[0, 0]) == np.str_ or - type(reduced_data_list[0, 0] == np.str)): - low_ram = True - else: - print("data", reduced_data_list[0, 0]) - print(type(reduced_data_list[0, 0])) - raise ValueError("Reduced data are stored using type %s which is neither np.ndarray or str" - % type(reduced_data_list[0, 0])) + low_ram = is_low_ram(reduced_data_list[0, 0]) s = [None] * n_sessions - # This is just to check that all subjects have same number of timeframes in a given session + # This is just to check that all subjects have same number of + # timeframes in a given session list_n_timeframes = [None] * n_sessions for n in range(n_subjects): for m in range(n_sessions): @@ -251,19 +326,15 @@ def _reduced_space_compute_shared_response(reduced_data_list, n_timeframes, n_supervoxels = data_nm.shape - if n_supervoxels < n_components: - raise ValueError("The number of regions in the atlas %i is smaller than " - "the number of components %i of fastSRM" % (n_supervoxels, n_components)) + check_shapes(n_supervoxels, n_components, n_timeframes) if list_n_timeframes[m] is None: list_n_timeframes[m] = n_timeframes elif list_n_timeframes[m] != n_timeframes: - raise ValueError("Subject %i Session %i does not have the same number of timeframes " + raise ValueError("Subject %i Session %i does not have the " + "same number of timeframes " "as Subject %i Session %i" % (n, m, 0, m)) - if n_timeframes < n_components: - raise ValueError("Number of timeframes is shorter than number of components") - if reduced_basis_list is None: reduced_basis_list = [] for subject in range(n_subjects): @@ -289,7 +360,8 @@ def _compute_and_save_corr_mat(img, shared_response, temp_dir): ---------- img : str path to data. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 @@ -310,11 +382,14 @@ def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): ---------- subject_number: int - Number that identifies the subject. Basis will be stored in [temp_dir]/basis_[subject_number].npy + Number that identifies the subject. Basis will be stored in + [temp_dir]/basis_[subject_number].npy sessions : array of str - Element i of the array is a path to the data collected during session i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Element i of the array is a path to the data collected during + session i. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance @@ -356,15 +431,19 @@ def _compute_subject_basis(corr_mat): Parameters ---------- - corr_mat: array, shape=[n_component, n_voxels] or shape=[n_components, n_supervoxels] - correlation matrix between shared response and subject data or subject reduced data - element k, v is given by S.T.dot(X_i) where S is the shared response and + corr_mat: array, shape=[n_component, n_voxels] + or shape=[n_components, n_supervoxels] + correlation matrix between shared response and subject data or + subject reduced data + element k, v is given by S.T.dot(X_i) where S is the shared response + and X_i the data of subject i. Returns ------- - - basis: array, shape=[n_components, n_voxels] or shape=[n_components, n_supervoxels] + + basis: array, shape=[n_components, n_voxels] + or shape=[n_components, n_supervoxels] basis of subject or reduced_basis of subject """ if corr_mat.shape[0] == corr_mat.shape[1]: @@ -379,29 +458,34 @@ def _compute_subject_basis(corr_mat): def fast_srm(reduced_data_list, n_iter=10, n_components=None): """Computes shared response and basis in reduced space - + Parameters ---------- - + reduced_data_list : array of str, shape=[n_subjects, n_sessions] - or array, shape=[n_subjects, n_sessions, n_timeframes, n_voxels] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of shape=[n_timeframes, n_supervoxels] - n_timeframes and n_voxels are assumed to be the same across subjects + or array, shape=[n_subjects, n_sessions, n_timeframes, n_supervoxels] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of + shape=[n_timeframes, n_supervoxels] + n_timeframes and n_supervoxels are + assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - + n_iter : int Number of iterations performed - + n_components : int or None number of components Returns ------- - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ @@ -412,9 +496,8 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): type(reduced_data_list[0, 0] == np.str)): low_ram = True else: - print("data", reduced_data_list[0, 0]) - print(type(reduced_data_list[0, 0])) - raise ValueError("Reduced data are stored using type %s which is neither np.ndarray or str" + raise ValueError("Reduced data are stored using type" + " %s which is neither np.ndarray or str" % type(reduced_data_list[0, 0])) n_subjects, n_sessions = reduced_data_list.shape[:2] @@ -450,20 +533,23 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): def _compute_basis_subject_online(sessions, shared_response_list): """Computes subject's basis with shared response fixed - + Parameters ---------- - + sessions : array of str - Element i of the array is a path to the data collected during session i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Element i of the array is a path to the data + collected during session i. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i - + Returns ------- @@ -484,27 +570,32 @@ def _compute_basis_subject_online(sessions, shared_response_list): return _compute_subject_basis(basis_i) -def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subjects_indexes): +def _compute_shared_response_online_single(subjects, basis_list, + temp_dir, subjects_indexes): """Computes shared response during one session with basis fixed - + Parameters ---------- - + subjects : array of str Element i of the array is a path to the data of subject i. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - - basis_list : None or list of array, element i has shape=[n_components, n_voxels] + + basis_list : None or list of array, element i has + shape=[n_components, n_voxels] basis of all subjects, element i is the basis of subject i temp_dir : None or str - path to basis folder where file basis_%i.npy contains the basis of subject i + path to basis folder where file basis_%i.npy contains the basis of + subject i subjects_indexes : list of int or None - list of indexes corresponding to the subjects to use to compute shared response + list of indexes corresponding to the subjects to use to compute + shared response Returns ------- @@ -531,36 +622,43 @@ def _compute_shared_response_online_single(subjects, basis_list, temp_dir, subje return shared_response / float(n) -def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects_indexes): +def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, + subjects_indexes): """Computes shared response with basis fixed Parameters ---------- imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_voxels] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - basis_list : None or list of array, element i has shape=[n_components, n_voxels] + basis_list : None or list of array, element i has + shape=[n_components, n_voxels] basis of all subjects, element i is the basis of subject i temp_dir : None or str - path to basis folder where file basis_%i.npy contains the basis of subject i + path to basis folder where file basis_%i.npy contains the basis of + subject i n_jobs : integer, optional, default=1 The number of CPUs to use to do the computation. -1 means all CPUs, -2 all CPUs but one, and so on. subjects_indexes : list or None - list of indexes corresponding to the subjects to use to compute shared response + list of indexes corresponding to the subjects to use to compute + shared response Returns ------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ shared_response_list = Parallel(n_jobs=n_jobs)( @@ -574,8 +672,43 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, subjects return shared_response_list +def check_imgs(imgs): + """ + Check input images + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across + subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + if type(imgs) != np.ndarray: + raise ValueError("imgs should be of type " + "np.ndarray but is of type %s" + % type(imgs)) + + if len(imgs.shape) != 2: + raise ValueError("imgs should be an array of shape " + "[n_subjects, n_sessions] " + "but its shape is of size %i" + % len(imgs.shape)) + + n_subjects, n_sessions = imgs.shape + + if n_subjects <= 1: + raise ValueError("The number of subjects should be greater than 1") + + class FastSRM(BaseEstimator, TransformerMixin): - """SRM decomposition using a very low amount of memory and computational power + """SRM decomposition using a very low amount of memory and + computational power Given multi-subject data, factorize it as a shared response S among all subjects and an orthogonal transform (basis) W per subject: @@ -604,8 +737,8 @@ class FastSRM(BaseEstimator, TransformerMixin): low_ram : bool if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when the number - of subject and / or sessions is large + this increases the number of IO but reduces memory complexity when + the number of subject and / or sessions is large random_state : int or RandomState Pseudo number generator state used for random sampling. @@ -622,9 +755,11 @@ class FastSRM(BaseEstimator, TransformerMixin): Attributes ---------- - `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str + `basis_list`: list of array, element i has shape=[n_components, n_voxels] + or list of str basis of all subjects, element i is the basis of subject i - or path to basis of all subjects, element i is the path to the basis of subject i + or path to basis of all subjects, element i is the path to the + basis of subject i """ def __init__(self, atlas, @@ -647,9 +782,11 @@ def __init__(self, if temp_dir is None: if self.verbose == "warn" or self.verbose is True: - logger.warning("temp_dir has value None. All basis (spatial maps) and " - "reconstructed data will therefore be kept in memory." - "This can lead to memory errors when the number of subjects " + logger.warning("temp_dir has value None. " + "All basis (spatial maps) and reconstructed " + "data will therefore be kept in memory." + "This can lead to memory errors when the " + "number of subjects " "and/or sessions is large.") self.temp_dir = None self.low_ram = False @@ -673,9 +810,12 @@ def fit(self, imgs): ---------- imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across + subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 @@ -692,20 +832,9 @@ def fit(self, imgs): for path in paths: os.remove(path) - if type(imgs) != np.ndarray: - raise ValueError("imgs should be of type np.ndarray but is of type %s" - % type(imgs)) - - if len(imgs.shape) != 2: - raise ValueError("imgs should be an array of shape [n_subjects, n_sessions] " - "but its shape is of size %i" - % len(imgs.shape)) - + check_imgs(imgs) n_subjects, n_sessions = imgs.shape - if n_subjects <= 1: - raise ValueError("The number of subjects should be greater than 1") - if self.verbose is True: n_subjects, n_sessions = imgs.shape logger.info("Fitting using %i subjects and %i sessions per subject" @@ -723,7 +852,8 @@ def fit(self, imgs): ) if self.verbose is True: - logger.info("[FastSRM.fit] Finds shared response using reduced data") + logger.info("[FastSRM.fit] Finds shared " + "response using reduced data") shared_response_list = fast_srm( reduced_data, @@ -732,12 +862,16 @@ def fit(self, imgs): ) if self.verbose is True: - print("[FastSRM.fit] Finds basis using full data and shared response") + logger.info("[FastSRM.fit] Finds basis using " + "full data and shared response") if self.n_jobs == 1: basis = [] for i, sessions in enumerate(imgs): - basis_i = _compute_basis_subject_online(sessions, shared_response_list) + basis_i = _compute_basis_subject_online( + sessions, + shared_response_list + ) if self.temp_dir is None: basis.append(basis_i) else: @@ -756,8 +890,17 @@ def fit(self, imgs): for subject in subjects ) - basis = Parallel(n_jobs=self.n_jobs)(delayed(_compute_and_save_subject_basis)(i, sessions, self.temp_dir) - for i, sessions in enumerate(imgs)) + basis = Parallel( + n_jobs=self.n_jobs + )(delayed( + _compute_and_save_subject_basis + )( + i, + sessions, + self.temp_dir + ) + for i, sessions in enumerate(imgs) + ) self.basis_list = basis return self @@ -769,15 +912,19 @@ def fit_transform(self, imgs, **fit_params): Parameters ---------- imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across + subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 Returns -------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ self.fit(imgs) @@ -791,20 +938,25 @@ def transform(self, imgs, subjects_indexes=None): ---------- imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i collected during session j. - Data are loaded with numpy.load and expected shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across subjects + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across + subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 subjects_indexes : list or None: if None imgs[i] will be transformed using basis[i] - otherwise imgs[i] will be transformed using basis[subjects_index[i]] + otherwise imgs[i] will be transformed using + basis[subjects_index[i]] Returns ------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ if self.basis_list is None: @@ -825,18 +977,22 @@ def transform(self, imgs, subjects_indexes=None): return shared_response - def inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None): - """From shared response and basis from training data reconstruct subject's data + def inverse_transform(self, shared_response_list, subjects_indexes=None, + sessions_indexes=None): + """From shared response and basis from training data + reconstruct subject's data Parameters ---------- - shared_response_list : list of array, element i has shape=[n_timeframes, n_components] + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] shared response, element i is the shared response during session i subjects_indexes: list or None: if None reconstructs data of all subjects' used during train - otherwise reconstructs data using subject's specified by subjects_indexes + otherwise reconstructs data using subject's specified by + subjects_indexes sessions_indexes: list or None: if None reconstructs data using all sessions @@ -844,7 +1000,9 @@ def inverse_transform(self, shared_response_list, subjects_indexes=None, session Returns ------- - reconstructed_data: array shape=[len(subjects_indexes), len(sessions_indexes), n_timeframes, n_voxels] + reconstructed_data: array + shape=[len(subjects_indexes), len(sessions_indexes), + n_timeframes, n_voxels] Reconstructed data for chosen subjects and sessions """ n_subjects = len(self.basis_list) @@ -865,7 +1023,8 @@ def inverse_transform(self, shared_response_list, subjects_indexes=None, session if self.temp_dir is None: basis_i = self.basis_list[i] else: - basis_i = np.load(os.path.join(self.temp_dir, "basis_%i.npy" % i)) + basis_i = np.load(os.path.join(self.temp_dir, + "basis_%i.npy" % i)) for j in sessions_indexes: data_.append(shared_response_list[j].dot(basis_i)) diff --git a/tests/funcalign/test_fastsrm.py b/tests/funcalign/test_fastsrm.py index 9b8addcb3..3d0fdde42 100644 --- a/tests/funcalign/test_fastsrm.py +++ b/tests/funcalign/test_fastsrm.py @@ -11,12 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from sklearn.exceptions import NotFittedError -import pytest import numpy as np -import hashlib import tempfile -from brainiak.funcalign.fastsrm import FastSRM, reduce_data, _reduced_space_compute_shared_response +from brainiak.funcalign.fastsrm import FastSRM, reduce_data, \ + _reduced_space_compute_shared_response from brainiak.funcalign.fastsrm import _compute_basis_subject_online, fast_srm import os from numpy.testing import assert_array_almost_equal @@ -47,10 +45,12 @@ def to_path(X, dirpath): return np.array(paths) -def generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, noise_level=0.1): +def generate_data(n_voxels, n_timeframes, n_subjects, n_components, + datadir, noise_level=0.1): n_sessions = len(n_timeframes) cumsum_timeframes = np.cumsum([0] + n_timeframes) - slices_timeframes = [slice(cumsum_timeframes[i], cumsum_timeframes[i + 1]) for i in range(n_sessions)] + slices_timeframes = [slice(cumsum_timeframes[i], cumsum_timeframes[i + 1]) + for i in range(n_sessions)] # Create a Shared response S with K = 3 theta = np.linspace(-4 * np.pi, 4 * np.pi, int(np.sum(n_timeframes))) @@ -71,48 +71,71 @@ def generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, noi for session in range(n_sessions): S_s = S[:, slices_timeframes[session]] S_s = S_s - np.mean(S_s, axis=1, keepdims=True) - noise = noise_level * np.random.random((n_voxels, n_timeframes[session])) + noise = noise_level * np.random.random((n_voxels, + n_timeframes[session])) noise = noise - np.mean(noise, axis=1, keepdims=True) - data = Q.dot(S_s) \ - + noise + data = Q.dot(S_s) + noise X_.append(data.T) X.append(X_) - # create paths such that paths[i, j] contains data of subject i during session j + # create paths such that paths[i, j] contains data + # of subject i during session j paths = to_path(X, datadir) - S = [(S[:, s] - np.mean(S[:, s], axis=1, keepdims=True)).T for s in slices_timeframes] + S = [(S[:, s] - np.mean(S[:, s], axis=1, keepdims=True)).T + for s in slices_timeframes] return paths, W, S -def test_reduced_data(): +def test_generated_data(): with tempfile.TemporaryDirectory() as datadir: - # We authorize different timeframes for different sessions but they should be the same across subject + # We authorize different timeframes for different sessions + # but they should be the same across subject n_voxels = 100 n_timeframes = [250, 245] n_subjects = 2 n_components = 3 # number of components used for SRM model - n_supervoxels = 10 # number of components of the atlas - n_jobs = 1 n_sessions = len(n_timeframes) np.random.seed(0) - paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir) + paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir) # Test if generated data has the good shape for subject in range(n_subjects): for session in range(n_sessions): - assert np.load(paths[subject, session]).shape == (n_timeframes[session], n_voxels) + assert (np.load(paths[subject, session]).shape == + (n_timeframes[session], n_voxels)) + + +def test_reduced_data(): + with tempfile.TemporaryDirectory() as datadir: + + # We authorize different timeframes for different sessions + # but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 2 + n_components = 3 # number of components used for SRM model + n_supervoxels = 10 # number of components of the atlas + n_jobs = 1 + n_sessions = len(n_timeframes) + + np.random.seed(0) + paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir) # Test if reduced data has the good shape # probabilistic atlas atlas = np.random.rand(n_supervoxels, n_voxels) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False, temp_dir=None) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False, + temp_dir=None) for subject in range(n_subjects): for session in range(n_sessions): - assert data[subject, session].shape == (n_timeframes[session], n_supervoxels) + assert data[subject, session].shape == (n_timeframes[session], + n_supervoxels) # deterministic atlas det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) @@ -121,10 +144,12 @@ def test_reduced_data(): det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) n_unique = len(np.unique(det_atlas)[1:]) - data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, low_ram=True, temp_dir=datadir) + data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, + low_ram=True, temp_dir=datadir) for subject in range(n_subjects): for session in range(n_sessions): - assert np.load(data[subject, session]).shape == (n_timeframes[session], n_supervoxels) + assert (np.load(data[subject, session]).shape + == (n_timeframes[session], n_supervoxels)) # test atlas that reduces nothing atlas = np.arange(1, n_voxels + 1) @@ -138,14 +163,17 @@ def test_reduced_data(): data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False) for i in range(n_subjects): for j in range(n_sessions): - assert_array_almost_equal(data[i, j].flatten(), np.mean(np.load(paths[i, j]), axis=1)) + assert_array_almost_equal(data[i, j].flatten(), + np.mean(np.load(paths[i, j]), + axis=1)) def test_reduced_data_srm(): with tempfile.TemporaryDirectory() as datadir: np.random.seed(0) - # We authorize different timeframes for different sessions but they should be the same across subject + # We authorize different timeframes for different sessions but + # they should be the same across subject n_voxels = 100 n_timeframes = [250, 245] n_subjects = 5 @@ -154,12 +182,14 @@ def test_reduced_data_srm(): n_sessions = len(n_timeframes) np.random.seed(0) - paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, 0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0) # Test if generated data has the good shape for subject in range(n_subjects): for session in range(n_sessions): - assert np.load(paths[subject, session]).shape == (n_timeframes[session], n_voxels) + assert (np.load(paths[subject, session]).shape + == (n_timeframes[session], n_voxels)) # Test if generated basis have good shape assert len(W) == n_subjects @@ -171,16 +201,21 @@ def test_reduced_data_srm(): assert s.shape == (n_timeframes[j], n_components) atlas = np.arange(1, n_voxels + 1) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False, temp_dir=None) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + low_ram=False, temp_dir=None) # Test if shared response has the good shape - shared_response_list = _reduced_space_compute_shared_response(data, - reduced_basis_list=None, - n_components=n_components) + shared_response_list = \ + _reduced_space_compute_shared_response( + data, + reduced_basis_list=None, + n_components=n_components + ) assert len(shared_response_list) == n_sessions for session in range(n_sessions): - assert shared_response_list[session].shape == (n_timeframes[session], n_components) + assert (shared_response_list[session].shape == + (n_timeframes[session], n_components)) # Test basis from shared response for i, sessions in enumerate(paths): @@ -193,29 +228,35 @@ def test_reduced_data_srm(): assert_array_almost_equal(basis, W[i], 2) # Test reduced_data_shared_response - shared_response_list = _reduced_space_compute_shared_response(data, - reduced_basis_list=W, - n_components=n_components - ) + shared_response_list = _reduced_space_compute_shared_response( + data, + reduced_basis_list=W, + n_components=n_components + ) for session in range(n_sessions): - S_real = np.mean([data[i, session].dot(W[i].T) for i in range(n_subjects)], axis=0) + S_real = np.mean([data[i, session].dot(W[i].T) for i in + range(n_subjects)], axis=0) assert_array_almost_equal(shared_response_list[session], S_real) - assert_array_almost_equal(shared_response_list[session], S[session]) + assert_array_almost_equal(shared_response_list[session], + S[session]) # Test fast_srm for reduced_data shared_response_list = fast_srm(data, n_components=n_components) for i, sessions in enumerate(paths): - basis = _compute_basis_subject_online(sessions, shared_response_list) + basis = _compute_basis_subject_online(sessions, + shared_response_list) for j, session in enumerate(sessions): - assert_array_almost_equal(shared_response_list[j].dot(basis), np.load(paths[i, j])) + assert_array_almost_equal(shared_response_list[j].dot(basis), + np.load(paths[i, j])) def test_fastsrm_class(): with tempfile.TemporaryDirectory() as datadir: np.random.seed(0) - # We authorize different timeframes for different sessions but they should be the same across subject + # We authorize different timeframes for different sessions + # but they should be the same across subject n_voxels = 100 n_timeframes = [250, 245] n_subjects = 5 @@ -224,7 +265,8 @@ def test_fastsrm_class(): n_sessions = len(n_timeframes) np.random.seed(0) - paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, 0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0) atlas = np.arange(1, n_voxels + 1) srm = FastSRM(atlas=atlas, @@ -232,7 +274,8 @@ def test_fastsrm_class(): n_iter=10, temp_dir=datadir, low_ram=True, - verbose=True) + verbose=True, + n_jobs=n_jobs) srm.fit(paths) basis = srm.basis_list @@ -241,137 +284,5 @@ def test_fastsrm_class(): for i in range(n_subjects): for j in range(n_sessions): basis_i = np.load(basis[i]) - assert_array_almost_equal(shared_response[j].dot(basis_i), np.load(paths[i, j])) - - - - - - - - - - - - - - - - - # for subject in range(n_subjects): - # for session in range(n_sessions): - # assert data[subject, session].shape == (n_timeframes, n_supervoxels) - # - # # Try with deterministic atlas - # det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) - # n_unique = len(np.unique(det_atlas)[1:]) - # while n_unique != n_supervoxels: - # det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) - # n_unique = len(np.unique(det_atlas)[1:]) - # - # data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, low_ram=True, temp_dir=datadir) - # for subject in range(n_subjects): - # for session in range(n_sessions): - # assert np.load(data[subject, session]).shape == (n_timeframes, n_supervoxels) - - - - - # def test_can_instantiate(): - # s = FastSRM(atlas=np.random.rand(10, 10)) - # assert s, "Invalid SRM instance!" - # - # - # - # s = FastSRM(atlas=np.ones(n_voxels), n_iter=5, n_components=n_components) - # assert s, "Invalid SRM instance!" - # - # # Create a Shared response S with K = 3 - # theta = np.linspace(-4 * np.pi, 4 * np.pi, n_timeframes) - # z = np.linspace(-2, 2, n_timeframes) - # r = z**2 + 1 - # x = r * np.sin(theta) - # y = r * np.cos(theta) - # - # S = np.vstack((x, y, z)) - # - # X = [] - # W = [] - # Q, R = np.linalg.qr(np.random.random((n_voxels, n_timeframes))) - # W.append(Q) - # - # data = Q.dot(S) + 0.1*np.random.random((n_voxels, n_timeframes)) - # X.append([data.T]) - # - # paths = to_path(X, datadir) - # - # # Check that transform does NOT run before fitting the model - # with pytest.raises(NotFittedError): - # s.transform(paths) - # print("Test: transforming before fitting the model") - # - # # Check that it does NOT run with 1 subject - # with pytest.raises(ValueError): - # s.fit(paths) - # print("Test: running SRM with 1 subject") - # - # for subject in range(1, n_subjects): - # Q, R = np.linalg.qr(np.random.random((n_voxels, n_components))) - # W.append(Q) - # data = Q.dot(S) + 0.1*np.random.random((n_voxels, samples)) - # X.append([data.T]) - # - # # Check that runs with 2 subject - # paths = to_path(X, datadir) - # s.fit(paths) - # shared_response = s.transform(paths)[0] - # from pathlib import Path - # sr_v0_4 = np.load(Path(__file__).parent / "sr_v0_4.npz")['sr'] - # assert(np.allclose(sr_v0_4, shared_response)) - # - # assert len(s.basis_list) == n_subjects, ( - # "Invalid computation of SRM! (wrong # subjects in W)") - # for subject in range(n_subjects): - # assert s.basis_list[subject].shape[0] == n_voxels, ( - # "Invalid computation of SRM! (wrong # voxels in W)") - # assert s.basis_list[subject].shape[1] == n_components, ( - # "Invalid computation of SRM! (wrong # n_components in W)") - # ortho = np.linalg.norm(s.basis_list[subject].T.dot(s.basis_list[subject]) - # - np.eye(s.basis_list[subject].shape[1]), - # 'fro') - # assert ortho < 1e-7, "A Wi mapping is not orthonormal in SRM." - # difference = np.linalg.norm(X[subject] - s.basis_list[subject].dot(shared_response), - # 'fro') - # datanorm = np.linalg.norm(X[subject], 'fro') - # assert difference/datanorm < 1.0, "Model seems incorrectly computed." - # assert shared_response.shape[0] == n_components, ( - # "Invalid computation of SRM! (wrong # n_components in S)") - # assert shared_response.shape[1] == samples, ( - # "Invalid computation of SRM! (wrong # samples in S)") - # - # # Check that it does run to compute the shared response after the model - # # computation - # new_s = s.transform(paths)[0] - # - # assert len(new_s) == n_subjects, ( - # "Invalid computation of SRM! (wrong # subjects after transform)") - # for subject in range(n_subjects): - # assert new_s[subject].shape[0] == n_components, ( - # "Invalid computation of SRM! (wrong # n_components after transform)") - # assert new_s[subject].shape[1] == samples, ( - # "Invalid computation of SRM! (wrong # samples after transform)") - # - # # Check that it does not run without enough n_timeframes (TRs). - # with pytest.raises(ValueError): - # s.set_params(n_components=(n_timeframes+1)) - # s.fit(paths) - # print("Test: not enough n_timeframes") - # - # # Check that it does not run with different number of n_timeframes (TRs) - # S2 = S[:, :-2] - # data = Q.dot(S2) - # X.append([data.T]) - # paths = to_path(X, datadir) - # with pytest.raises(ValueError): - # s.fit(paths) - # print("Test: different number of n_timeframes per subject") + assert_array_almost_equal(shared_response[j].dot(basis_i), + np.load(paths[i, j])) From 04bcdede8f326bec2eb9ec5f041d832e47352dbc Mon Sep 17 00:00:00 2001 From: hrichard Date: Tue, 23 Apr 2019 16:20:02 +0200 Subject: [PATCH 09/24] correct bug in code and add tests (coverage 99 %) --- brainiak/funcalign/fastsrm.py | 106 +++++----- tests/funcalign/test_fastsrm.py | 358 +++++++++++++++++++++++++++----- 2 files changed, 362 insertions(+), 102 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index bfca3a3ab..15c74a750 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -55,13 +55,13 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - atlas : array, shape=[n_supervoxels, n_voxels] + atlas : array, shape=[n_supervoxels, n_voxels] or None or None or array, shape=[n_voxels] Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - inv_atlas : array, shape=[n_voxels, n_supervoxels] + inv_atlas : array, shape=[n_voxels, n_supervoxels] or None Pseudo inverse of the atlas (only for probabilistic atlases) temp_dir : str or None @@ -87,7 +87,6 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, " a probabilistic atlas should be provided") if inv_atlas is None and atlas is not None: - # this means that it is a deterministic atlas n_voxels = atlas.shape[0] atlas_values = np.unique(atlas) if 0 in atlas_values: @@ -103,7 +102,9 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T else: - # this means that it is a deterministic atlas + # this means that it is a probabilistic atlas + assert len(inv_atlas.shape) == 2 + n_voxels = inv_atlas.shape[0] data = np.load(img) @@ -177,12 +178,17 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): Each voxel's timecourse is assumed to have mean 0 and variance 1 """ if type(atlas) != np.ndarray: - raise ValueError("atlas should be of type np.ndarray") + raise ValueError("atlas should be of type np.ndarray but has type %s" + % (type(atlas))) if len(atlas.shape) == 2: A = None A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) else: + if len(atlas.shape) != 1: + raise ValueError("atlas should have shape of length 1 " + "(deterministic) or 2 (probabilistic) but input" + "atlas has shape of length %i" % len(atlas.shape)) A = atlas A_inv = None @@ -263,12 +269,12 @@ def is_low_ram(reduced_data): low_ram = False elif (type(reduced_data) == str or type(reduced_data) == np.str_ or - type(reduced_data == np.str)): + type(reduced_data) == np.str): low_ram = True else: raise ValueError("Reduced data are stored using " "type %s which is neither np.ndarray or str" - % type(reduced_data[0, 0])) + % type(reduced_data)) return low_ram @@ -415,13 +421,9 @@ def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): else: corr_mat += np.load(path) basis_i = _compute_subject_basis(corr_mat) - - if temp_dir is None: - return basis_i - else: - path = os.path.join(temp_dir, "basis_" % subject_number) - np.save(path, basis_i) - return path + ".npy" + path = os.path.join(temp_dir, "basis_%i" % subject_number) + np.save(path, basis_i) + return path + ".npy" def _compute_subject_basis(corr_mat): @@ -436,8 +438,7 @@ def _compute_subject_basis(corr_mat): correlation matrix between shared response and subject data or subject reduced data element k, v is given by S.T.dot(X_i) where S is the shared response - and - X_i the data of subject i. + and X_i the data of subject i. Returns ------- @@ -446,13 +447,7 @@ def _compute_subject_basis(corr_mat): or shape=[n_components, n_supervoxels] basis of subject or reduced_basis of subject """ - if corr_mat.shape[0] == corr_mat.shape[1]: - U, _, V = scipy.linalg.svd( - corr_mat + 1.e-18 * np.eye(corr_mat.shape[0]), - full_matrices=False - ) - else: - U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) + U, _, V = scipy.linalg.svd(corr_mat, full_matrices=False) return U.dot(V) @@ -489,17 +484,11 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): shared response, element i is the shared response during session i """ - if type(reduced_data_list[0, 0]) == np.ndarray: - low_ram = False - elif (type(reduced_data_list[0, 0]) == str or - type(reduced_data_list[0, 0]) == np.str_ or - type(reduced_data_list[0, 0] == np.str)): - low_ram = True - else: - raise ValueError("Reduced data are stored using type" - " %s which is neither np.ndarray or str" - % type(reduced_data_list[0, 0])) + if type(reduced_data_list) != np.ndarray: + raise ValueError("reduced data must have type np.ndarray but" + "has type %s" % type(reduced_data_list)) + low_ram = is_low_ram(reduced_data_list[0, 0]) n_subjects, n_sessions = reduced_data_list.shape[:2] shared_response = _reduced_space_compute_shared_response( reduced_data_list, @@ -833,14 +822,11 @@ def fit(self, imgs): os.remove(path) check_imgs(imgs) - n_subjects, n_sessions = imgs.shape if self.verbose is True: n_subjects, n_sessions = imgs.shape logger.info("Fitting using %i subjects and %i sessions per subject" % (n_subjects, n_sessions)) - - if self.verbose is True: logger.info("[FastSRM.fit] Reducing data") reduced_data = reduce_data( @@ -880,27 +866,37 @@ def fit(self, imgs): basis.append(path + ".npy") del basis_i else: - Parallel(n_jobs=self.n_jobs)( - delayed(_compute_and_save_corr_mat)( - subject, - shared_response_list[m], + if self.temp_dir is None: + basis = Parallel( + n_jobs=self.n_jobs + )(delayed( + _compute_basis_subject_online + )( + sessions, + shared_response_list + ) for sessions in imgs) + else: + Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_corr_mat)( + subject, + shared_response_list[m], + self.temp_dir + ) + for m, subjects in enumerate(imgs.T) + for subject in subjects + ) + + basis = Parallel( + n_jobs=self.n_jobs + )(delayed( + _compute_and_save_subject_basis + )( + i, + sessions, self.temp_dir ) - for m, subjects in enumerate(imgs.T) - for subject in subjects - ) - - basis = Parallel( - n_jobs=self.n_jobs - )(delayed( - _compute_and_save_subject_basis - )( - i, - sessions, - self.temp_dir - ) - for i, sessions in enumerate(imgs) - ) + for i, sessions in enumerate(imgs) + ) self.basis_list = basis return self diff --git a/tests/funcalign/test_fastsrm.py b/tests/funcalign/test_fastsrm.py index 3d0fdde42..49918e96d 100644 --- a/tests/funcalign/test_fastsrm.py +++ b/tests/funcalign/test_fastsrm.py @@ -16,7 +16,13 @@ from brainiak.funcalign.fastsrm import FastSRM, reduce_data, \ _reduced_space_compute_shared_response from brainiak.funcalign.fastsrm import _compute_basis_subject_online, fast_srm +from brainiak.funcalign.fastsrm import _compute_and_save_corr_mat +from brainiak.funcalign.fastsrm import _compute_and_save_subject_basis +from sklearn.exceptions import NotFittedError +from brainiak.funcalign.fastsrm import check_imgs, check_shapes, is_low_ram +from brainiak.funcalign.fastsrm import reduce_data_single import os +import pytest from numpy.testing import assert_array_almost_equal @@ -108,67 +114,123 @@ def test_generated_data(): (n_timeframes[session], n_voxels)) -def test_reduced_data(): +def test_reduce_data_bad_input(): with tempfile.TemporaryDirectory() as datadir: - + n_timeframes = (250, 250) # We authorize different timeframes for different sessions # but they should be the same across subject n_voxels = 100 - n_timeframes = [250, 245] - n_subjects = 2 - n_components = 3 # number of components used for SRM model - n_supervoxels = 10 # number of components of the atlas - n_jobs = 1 - n_sessions = len(n_timeframes) np.random.seed(0) - paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, - n_components, datadir) - # Test if reduced data has the good shape + # First let us test how reduce_data reacts to bad input + X = [[np.random.rand(n_timeframes[0], n_voxels)]] + X = to_path(X, datadir)[0, 0] - # probabilistic atlas - atlas = np.random.rand(n_supervoxels, n_voxels) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False, - temp_dir=None) + # atlas is not an nd array + with pytest.raises(ValueError): + reduce_data(X, atlas="path_to_atlas") - for subject in range(n_subjects): - for session in range(n_sessions): - assert data[subject, session].shape == (n_timeframes[session], - n_supervoxels) + # atlas is not an nd array of dimension > 2 + with pytest.raises(ValueError): + reduce_data(X, atlas=np.random.rand(2, 3, 4)) + + # both atlases are None + with pytest.raises(ValueError): + reduce_data_single(X, atlas=None, inv_atlas=None) + + # deterministic atlas and data are incompatible + with pytest.raises(ValueError): + reduce_data_single(X, atlas=np.random.rand(n_voxels - 1)) - # deterministic atlas - det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) - n_unique = len(np.unique(det_atlas)[1:]) - while n_unique != n_supervoxels: + # probabilistic atlas and data are incompatible + with pytest.raises(ValueError): + reduce_data_single(X, + inv_atlas=np.random.rand(n_voxels - 1, + n_timeframes[0])) + + +def test_reduce_data_dummyatlases(): + n_jobs = 1 + with tempfile.TemporaryDirectory() as datadir: + for n_timeframes in ([250, 245], [250, 250]): + # We authorize different timeframes for different sessions + # but they should be the same across subject + n_voxels = 100 + n_subjects = 2 + n_components = 3 # number of components used for SRM model + n_sessions = len(n_timeframes) + + np.random.seed(0) + paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir) + + # test atlas that reduces nothing + atlas = np.arange(1, n_voxels + 1) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + low_ram=False) + for i in range(n_subjects): + for j in range(n_sessions): + assert_array_almost_equal(data[i, j], np.load(paths[i, j])) + + # test atlas that reduces everything + atlas = np.ones(n_voxels) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + low_ram=False) + for i in range(n_subjects): + for j in range(n_sessions): + assert_array_almost_equal(data[i, j].flatten(), + np.mean(np.load(paths[i, j]), + axis=1)) + + +def test_reduce_data_outputshapes(): + n_jobs = 1 + with tempfile.TemporaryDirectory() as datadir: + for n_timeframes in ([250, 245], [250, 250]): + # We authorize different timeframes for different sessions + # but they should be the same across subject + n_voxels = 100 + n_subjects = 2 + n_components = 3 # number of components used for SRM model + n_supervoxels = 10 # number of components of the atlas + n_sessions = len(n_timeframes) + + np.random.seed(0) + paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir) + + # Test if reduced data has the good shape + # probabilistic atlas + atlas = np.random.rand(n_supervoxels, n_voxels) + data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + low_ram=False, + temp_dir=None) + + for subject in range(n_subjects): + for session in range(n_sessions): + assert data[subject, + session].shape == (n_timeframes[session], + n_supervoxels) + + # deterministic atlas det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) n_unique = len(np.unique(det_atlas)[1:]) + while n_unique != n_supervoxels: + det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) + n_unique = len(np.unique(det_atlas)[1:]) - data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, - low_ram=True, temp_dir=datadir) - for subject in range(n_subjects): - for session in range(n_sessions): - assert (np.load(data[subject, session]).shape - == (n_timeframes[session], n_supervoxels)) + data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, + low_ram=True, temp_dir=datadir) - # test atlas that reduces nothing - atlas = np.arange(1, n_voxels + 1) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False) - for i in range(n_subjects): - for j in range(n_sessions): - assert_array_almost_equal(data[i, j], np.load(paths[i, j])) - - # test atlas that reduces everything - atlas = np.ones(n_voxels) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, low_ram=False) - for i in range(n_subjects): - for j in range(n_sessions): - assert_array_almost_equal(data[i, j].flatten(), - np.mean(np.load(paths[i, j]), - axis=1)) + for subject in range(n_subjects): + for session in range(n_sessions): + assert (np.load(data[subject, session]).shape + == (n_timeframes[session], n_supervoxels)) def test_reduced_data_srm(): + n_jobs = 1 with tempfile.TemporaryDirectory() as datadir: np.random.seed(0) @@ -178,10 +240,8 @@ def test_reduced_data_srm(): n_timeframes = [250, 245] n_subjects = 5 n_components = 3 # number of components used for SRM model - n_jobs = 1 n_sessions = len(n_timeframes) - np.random.seed(0) paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, 0) @@ -241,6 +301,13 @@ def test_reduced_data_srm(): S[session]) # Test fast_srm for reduced_data + # test first what happens with bad input + with pytest.raises(ValueError): + fast_srm([["path"]]) + + with pytest.raises(ValueError): + fast_srm(np.array([[{"bla": 5}]])) + shared_response_list = fast_srm(data, n_components=n_components) for i, sessions in enumerate(paths): @@ -251,7 +318,36 @@ def test_reduced_data_srm(): np.load(paths[i, j])) +def test_compute_and_save(): + with tempfile.TemporaryDirectory() as datadir: + np.random.seed(0) + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 5 + n_components = 3 # number of components used for SRM model + + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0) + + for m, subjects in enumerate(paths.T): + for subject in subjects: + _compute_and_save_corr_mat( + subject, + S[m], + datadir + ) + + for i, sessions in enumerate(paths): + basis = _compute_and_save_subject_basis(i, + sessions, + datadir + ) + + assert_array_almost_equal(np.load(basis), W[i]) + + def test_fastsrm_class(): + n_jobs = 1 with tempfile.TemporaryDirectory() as datadir: np.random.seed(0) @@ -261,7 +357,6 @@ def test_fastsrm_class(): n_timeframes = [250, 245] n_subjects = 5 n_components = 3 # number of components used for SRM model - n_jobs = 1 n_sessions = len(n_timeframes) np.random.seed(0) @@ -277,12 +372,181 @@ def test_fastsrm_class(): verbose=True, n_jobs=n_jobs) + # Raises an error because model is not fitted yet + with pytest.raises(NotFittedError): + srm.transform(paths) + srm.fit(paths) basis = srm.basis_list + print(basis) shared_response = srm.transform(paths) + shared_response_fittransform = srm.fit_transform(paths) + for j in range(n_sessions): + assert_array_almost_equal(shared_response_fittransform[j], + shared_response[j]) + for i in range(n_subjects): for j in range(n_sessions): basis_i = np.load(basis[i]) assert_array_almost_equal(shared_response[j].dot(basis_i), np.load(paths[i, j])) + + shared_response_partial = srm.transform( + paths[np.arange(1, 5)], + subjects_indexes=list(range(1, 5)) + ) + + for j in range(n_sessions): + assert_array_almost_equal(shared_response_partial[j], + shared_response[j]) + + reconstructed_data = srm.inverse_transform( + shared_response, + subjects_indexes=[0, 2], + sessions_indexes=[1]) + + for i, ii in enumerate([0, 2]): + for j, jj in enumerate([1]): + assert_array_almost_equal(reconstructed_data[i, j], + np.load(paths[ii, jj])) + + # Test bad input shapes n_timeframes varies across sessions + X = [[np.random.rand(n_timeframes[0], n_voxels), + np.random.rand(n_timeframes[1], n_voxels)], + [np.random.rand(n_timeframes[1], n_voxels), + np.random.rand(n_timeframes[0], n_voxels)]] + + with pytest.raises(ValueError): + srm.fit(to_path(X, datadir)) + + +# Test SRM class without temp_dir +def test_fastsrm_class_in_memory(): + with tempfile.TemporaryDirectory() as datadir: + np.random.seed(0) + + # We authorize different timeframes for different sessions + # but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 5 + n_components = 3 # number of components used for SRM model + + np.random.seed(0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0) + + atlas = np.arange(1, n_voxels + 1) + + for n_jobs in [1, 2]: + srm = FastSRM(atlas=atlas, + n_components=n_components, + n_iter=10, + temp_dir=None, + low_ram=True, + verbose=True, + n_jobs=n_jobs) + + srm.fit(paths) + basis = srm.basis_list + print(basis) + shared_response = srm.transform(paths) + + # Reconstruct data using all indexes + reconstructed_data = srm.inverse_transform( + shared_response, + subjects_indexes=None, + sessions_indexes=None) + + for i in range(len(paths)): + for j in range(len(paths[i])): + assert_array_almost_equal(reconstructed_data[i, j], + np.load(paths[i, j])) + + +def test_check_imgs(): + with tempfile.TemporaryDirectory() as datadir: + np.random.seed(0) + + # We authorize different timeframes for different sessions + # but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 5 + n_components = 3 # number of components used for SRM model + + np.random.seed(0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0) + + # Raises an error because of wrong type + with pytest.raises(ValueError): + imgs = ["sdfdf", "sdfsdf"] + check_imgs(imgs) + + # Raises an error because of wrong shape + with pytest.raises(ValueError): + imgs = np.random.rand(3, 3, 3) + check_imgs(imgs) + + # Raises an error because only one subject + with pytest.raises(ValueError): + check_imgs(paths[:1]) + + +def test_islow_ram(): + with pytest.raises(ValueError): + is_low_ram(["wrong type"]) + + assert is_low_ram(np.random.rand(10, 300)) is False + assert is_low_ram("path_to_data") is True + + +def test_shapes(): + with pytest.raises(ValueError): + check_shapes(n_supervoxels=10, n_components=50, n_timeframes=100) + + with pytest.raises(ValueError): + check_shapes(n_supervoxels=50, n_components=30, n_timeframes=20) + + +def test_multiple_fit(): + with tempfile.TemporaryDirectory() as datadir: + np.random.seed(0) + + # We authorize different timeframes for different sessions + # but they should be the same across subject + n_voxels = 100 + n_timeframes = [250, 245] + n_subjects = 5 + n_components = 3 # number of components used for SRM model + + np.random.seed(0) + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0) + + atlas = np.arange(1, n_voxels + 1) + + for n_jobs in [1, 2]: + srm = FastSRM(atlas=atlas, + n_components=n_components, + n_iter=10, + temp_dir=datadir, + verbose=True, + n_jobs=n_jobs) + srm.fit(paths) + basis = srm.basis_list + print(basis) + shared_response = srm.transform(paths) + + # Reconstruct data using all indexes + reconstructed_data = srm.inverse_transform( + shared_response, + subjects_indexes=None, + sessions_indexes=None) + + for i in range(len(paths)): + for j in range(len(paths[i])): + assert_array_almost_equal(reconstructed_data[i, j], + np.load(paths[i, j])) From 209fbb4a20c36d28b54ce2bc9f06b47fb5787f05 Mon Sep 17 00:00:00 2001 From: hrichard Date: Tue, 23 Apr 2019 17:40:14 +0200 Subject: [PATCH 10/24] correct docs --- brainiak/funcalign/fastsrm.py | 23 ++++------------------- requirements-dev.txt | 2 -- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 15c74a750..2d855a62d 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -1,19 +1,4 @@ """Fast Shared Response Model (FastSRM) - -The implementations are based on the following publications: - -.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", - P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge - Advances in Neural Information Processing Systems (NIPS), 2015. - http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model - -.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging - Datasets", - Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. - Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, - Kenneth A. Norman, - IEEE International Conference on Big Data, 2016. - https://doi.org/10.1109/BigData.2016.7840719 """ # Author: Hugo Richard (INRIA - Parietal) @@ -985,20 +970,20 @@ def inverse_transform(self, shared_response_list, subjects_indexes=None, shape=[n_timeframes, n_components] shared response, element i is the shared response during session i - subjects_indexes: list or None: + subjects_indexes : list or None if None reconstructs data of all subjects' used during train otherwise reconstructs data using subject's specified by subjects_indexes - sessions_indexes: list or None: + sessions_indexes : list or None if None reconstructs data using all sessions otherwise uses only specified sessions Returns ------- reconstructed_data: array - shape=[len(subjects_indexes), len(sessions_indexes), - n_timeframes, n_voxels] + shape=[len(subjects_indexes), len(sessions_indexes), + n_timeframes, n_voxels] Reconstructed data for chosen subjects and sessions """ n_subjects = len(self.basis_list) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9132617e1..07e8e3362 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,8 +9,6 @@ sphinx sphinx_rtd_theme towncrier joblib -os -glob # testing # Also add to .conda/meta.yaml From 30a8cfc68e9b0fe5f8d0e645618733b926add4e8 Mon Sep 17 00:00:00 2001 From: hrichard Date: Thu, 25 Apr 2019 14:31:33 +0200 Subject: [PATCH 11/24] correct value error message --- brainiak/funcalign/fastsrm.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 2d855a62d..462baec84 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -79,9 +79,9 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, data = np.load(img) if data.shape[1] != n_voxels: - raise ValueError("%s have %i number of voxels and" - " the atlas has %i number of voxels." - "This is not compatible." % (img, data.shape[1], + raise ValueError("%s have %i voxels and" + " the atlas has %i voxels." + "This is incompatible." % (img, data.shape[1], n_voxels)) reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) @@ -94,9 +94,9 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, data = np.load(img) if data.shape[1] != n_voxels: - raise ValueError("%s have %i number of voxels and the atlas" - " has %i number of voxels." - "This is not compatible." % (img, data.shape[1], + raise ValueError("%s have %i voxels and the atlas" + " has %i voxels." + "This is incompatible." % (img, data.shape[1], n_voxels)) reduced_data = data.dot(inv_atlas) From 50b4efb4ee7fee7c8344afd196d5498301f8a2cc Mon Sep 17 00:00:00 2001 From: hrichard Date: Mon, 29 Apr 2019 16:09:19 +0200 Subject: [PATCH 12/24] formatting enhancement --- brainiak/funcalign/fastsrm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 462baec84..6d77a391e 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -82,7 +82,7 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, raise ValueError("%s have %i voxels and" " the atlas has %i voxels." "This is incompatible." % (img, data.shape[1], - n_voxels)) + n_voxels)) reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T @@ -97,7 +97,7 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, raise ValueError("%s have %i voxels and the atlas" " has %i voxels." "This is incompatible." % (img, data.shape[1], - n_voxels)) + n_voxels)) reduced_data = data.dot(inv_atlas) From 76b29fffba612e1718e986e033fd6f5f21c4874e Mon Sep 17 00:00:00 2001 From: hrichard Date: Mon, 29 Apr 2019 16:10:45 +0200 Subject: [PATCH 13/24] add example --- .../fastsrm_image_prediction_example.ipynb | 612 ++++++++++++++++++ 1 file changed, 612 insertions(+) create mode 100644 examples/funcalign/fastsrm_image_prediction_example.ipynb diff --git a/examples/funcalign/fastsrm_image_prediction_example.ipynb b/examples/funcalign/fastsrm_image_prediction_example.ipynb new file mode 100644 index 000000000..5930f7c2e --- /dev/null +++ b/examples/funcalign/fastsrm_image_prediction_example.ipynb @@ -0,0 +1,612 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import some libraries that we will need" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import scipy.io\n", + "from scipy.stats import stats\n", + "from sklearn.metrics import confusion_matrix\n", + "from sklearn.svm import NuSVC\n", + "import numpy as np\n", + "from nilearn.datasets import fetch_atlas_basc_multiscale_2015\n", + "from nilearn.input_data import MultiNiftiMasker\n", + "import nibabel\n", + "from nilearn.image import new_img_like" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run SRM with the movie data" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on class FastSRM in module brainiak.funcalign.fastsrm:\n", + "\n", + "class FastSRM(sklearn.base.BaseEstimator, sklearn.base.TransformerMixin)\n", + " | FastSRM(atlas, n_components=20, n_iter=100, temp_dir=None, low_ram=False, random_state=None, n_jobs=1, verbose='warn')\n", + " | \n", + " | SRM decomposition using a very low amount of memory and\n", + " | computational power\n", + " | \n", + " | Given multi-subject data, factorize it as a shared response S among all\n", + " | subjects and an orthogonal transform (basis) W per subject:\n", + " | \n", + " | .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | \n", + " | atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels]\n", + " | Probabilistic or deterministic atlas on which to project the data\n", + " | Deterministic atlas is an array of shape [n_voxels,] where values\n", + " | range from 1 to n_supervoxels. Voxels labelled 0 will be ignored.\n", + " | \n", + " | n_components : int\n", + " | Number of timecourses of the shared coordinates\n", + " | \n", + " | n_iter : int\n", + " | Number of iterations to perform\n", + " | \n", + " | temp_dir : str or None\n", + " | path to dir where temporary results are stored\n", + " | if None temporary results will be stored in memory. This\n", + " | can results in memory errors when the number of subjects\n", + " | and / or sessions is large\n", + " | \n", + " | low_ram : bool\n", + " | if True and temp_dir is not None, reduced_data will be saved on disk\n", + " | this increases the number of IO but reduces memory complexity when\n", + " | the number of subject and / or sessions is large\n", + " | \n", + " | random_state : int or RandomState\n", + " | Pseudo number generator state used for random sampling.\n", + " | \n", + " | n_jobs : int, optional, default=1\n", + " | The number of CPUs to use to do the computation.\n", + " | -1 means all CPUs, -2 all CPUs but one, and so on.\n", + " | \n", + " | verbose : bool or \"warn\"\n", + " | if True, logs are enabled.\n", + " | if False, logs are disabled.\n", + " | if \"warn\" only warnings are printed.\n", + " | \n", + " | Attributes\n", + " | ----------\n", + " | \n", + " | `basis_list`: list of array, element i has shape=[n_components, n_voxels]\n", + " | or list of str\n", + " | basis of all subjects, element i is the basis of subject i\n", + " | or path to basis of all subjects, element i is the path to the\n", + " | basis of subject i\n", + " | \n", + " | Method resolution order:\n", + " | FastSRM\n", + " | sklearn.base.BaseEstimator\n", + " | sklearn.base.TransformerMixin\n", + " | builtins.object\n", + " | \n", + " | Methods defined here:\n", + " | \n", + " | __init__(self, atlas, n_components=20, n_iter=100, temp_dir=None, low_ram=False, random_state=None, n_jobs=1, verbose='warn')\n", + " | Initialize self. See help(type(self)) for accurate signature.\n", + " | \n", + " | fit(self, imgs)\n", + " | Computes basis across subjects from input imgs\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | \n", + " | imgs : array of str, shape=[n_subjects, n_sessions]\n", + " | Element i, j of the array is a path to the data of subject i\n", + " | collected during session j.\n", + " | Data are loaded with numpy.load and expected\n", + " | shape is [n_timeframes, n_voxels]\n", + " | n_timeframes and n_voxels are assumed to be the same across\n", + " | subjects\n", + " | n_timeframes can vary across sessions\n", + " | Each voxel's timecourse is assumed to have mean 0 and variance 1\n", + " | \n", + " | Returns\n", + " | -------\n", + " | self : object\n", + " | Returns the instance itself. Contains attributes listed\n", + " | at the object level.\n", + " | \n", + " | fit_transform(self, imgs, **fit_params)\n", + " | Computes basis across subjects and shared response from input imgs\n", + " | return shared response.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | imgs : array of str, shape=[n_subjects, n_sessions]\n", + " | Element i, j of the array is a path to the data of subject i\n", + " | collected during session j.\n", + " | Data are loaded with numpy.load and expected\n", + " | shape is [n_timeframes, n_voxels]\n", + " | n_timeframes and n_voxels are assumed to be the same across\n", + " | subjects\n", + " | n_timeframes can vary across sessions\n", + " | Each voxel's timecourse is assumed to have mean 0 and variance 1\n", + " | \n", + " | Returns\n", + " | --------\n", + " | shared_response_list : list of array, element i has\n", + " | shape=[n_timeframes, n_components]\n", + " | shared response, element i is the shared response during session i\n", + " | \n", + " | inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None)\n", + " | From shared response and basis from training data\n", + " | reconstruct subject's data\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | \n", + " | shared_response_list : list of array, element i has\n", + " | shape=[n_timeframes, n_components]\n", + " | shared response, element i is the shared response during session i\n", + " | \n", + " | subjects_indexes : list or None\n", + " | if None reconstructs data of all subjects' used during train\n", + " | otherwise reconstructs data using subject's specified by\n", + " | subjects_indexes\n", + " | \n", + " | sessions_indexes : list or None\n", + " | if None reconstructs data using all sessions\n", + " | otherwise uses only specified sessions\n", + " | \n", + " | Returns\n", + " | -------\n", + " | reconstructed_data: array\n", + " | shape=[len(subjects_indexes), len(sessions_indexes),\n", + " | n_timeframes, n_voxels]\n", + " | Reconstructed data for chosen subjects and sessions\n", + " | \n", + " | transform(self, imgs, subjects_indexes=None)\n", + " | From data in imgs and basis from training data,\n", + " | computes shared response.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | \n", + " | imgs : array of str, shape=[n_subjects, n_sessions]\n", + " | Element i, j of the array is a path to the data of subject i\n", + " | collected during session j.\n", + " | Data are loaded with numpy.load and expected\n", + " | shape is [n_timeframes, n_voxels]\n", + " | n_timeframes and n_voxels are assumed to be the same across\n", + " | subjects\n", + " | n_timeframes can vary across sessions\n", + " | Each voxel's timecourse is assumed to have mean 0 and variance 1\n", + " | \n", + " | \n", + " | subjects_indexes : list or None:\n", + " | if None imgs[i] will be transformed using basis[i]\n", + " | otherwise imgs[i] will be transformed using\n", + " | basis[subjects_index[i]]\n", + " | \n", + " | Returns\n", + " | -------\n", + " | shared_response_list : list of array, element i has\n", + " | shape=[n_timeframes, n_components]\n", + " | shared response, element i is the shared response during session i\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Methods inherited from sklearn.base.BaseEstimator:\n", + " | \n", + " | __getstate__(self)\n", + " | \n", + " | __repr__(self)\n", + " | Return repr(self).\n", + " | \n", + " | __setstate__(self, state)\n", + " | \n", + " | get_params(self, deep=True)\n", + " | Get parameters for this estimator.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | deep : boolean, optional\n", + " | If True, will return the parameters for this estimator and\n", + " | contained subobjects that are estimators.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | params : mapping of string to any\n", + " | Parameter names mapped to their values.\n", + " | \n", + " | set_params(self, **params)\n", + " | Set the parameters of this estimator.\n", + " | \n", + " | The method works on simple estimators as well as on nested objects\n", + " | (such as pipelines). The latter have parameters of the form\n", + " | ``__`` so that it's possible to update each\n", + " | component of a nested object.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | self\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Data descriptors inherited from sklearn.base.BaseEstimator:\n", + " | \n", + " | __dict__\n", + " | dictionary for instance variables (if defined)\n", + " | \n", + " | __weakref__\n", + " | list of weak references to the object (if defined)\n", + "\n" + ] + } + ], + "source": [ + "import brainiak.funcalign.fastsrm\n", + "help(brainiak.funcalign.fastsrm.FastSRM)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load the input data that contains the movie stimuli for unsupervised training with SRM" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "movie_file = scipy.io.loadmat('data/movie_data.mat')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Convert data to a arrays matching FastSRM input.\n", + "FastSRM is designed to work on large datasets where all data are not assumed to hold in memory. Therefore it takes as input npy files and not matrices.\n", + "Files should be loaded with np.load and represent matrix of TRs by voxels. " + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(10, 1)" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "movie_data_left = movie_file['movie_data_lh']\n", + "movie_data_right = movie_file['movie_data_rh']\n", + "subjects = movie_data_left.shape[2]\n", + "movie_data = []\n", + "\n", + "# We treat these data as having only one session\n", + "paths = []\n", + "for s in range(subjects):\n", + " # concatenate data from both hemisphere\n", + " data_s = np.concatenate([movie_data_left[:, :, s], movie_data_right[:, :, s]], axis=0)\n", + " # zscore the data\n", + " data_s = stats.zscore(data_s,axis=1,ddof=1)\n", + " # input should be n_timeframes x n_voxels to match with \n", + " # n_samples, n_features framework of sklearn\n", + " data_s = data_s.T\n", + " np.save(\n", + " \"data/movie_subject_%i\" % s,\n", + " data_s\n", + " )\n", + " _, n_voxels = data_s.shape\n", + " paths.append([\"data/movie_subject_%i.npy\" % s])\n", + "paths = np.array(paths)\n", + "\n", + "# paths is now an array of shape (number_of_subjects, number_of_sessions)\n", + "paths.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run FastSRM " + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's use a naive probabilistic atlas (all voxels are used)\n", + "# In practice one would choose an off-the-shelf atlas like\n", + "# nilearn.datasets.fetch_atlas_basc_multiscale_2015\n", + "A = np.eye(n_voxels)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "temp_dir has value None. All basis (spatial maps) and reconstructed data will therefore be kept in memory.This can lead to memory errors when the number of subjects and/or sessions is large.\n" + ] + }, + { + "data": { + "text/plain": [ + "FastSRM(atlas=array([[1., 0., ..., 0., 0.],\n", + " [0., 1., ..., 0., 0.],\n", + " ...,\n", + " [0., 0., ..., 1., 0.],\n", + " [0., 0., ..., 0., 1.]]),\n", + " low_ram=False, n_components=50, n_iter=100, n_jobs=1,\n", + " random_state=None, temp_dir=None, verbose='warn')" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "srm = brainiak.funcalign.fastsrm.FastSRM(n_components=50, atlas=A)\n", + "srm.fit(paths)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load the input data that contains the image stimuli and its labels for training a classifier" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [], + "source": [ + "image_file = scipy.io.loadmat('data/image_data.mat')\n", + "image_data_left = image_file['image_data_lh']\n", + "image_data_right = image_file['image_data_rh']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Convert data to a list of arrays matching FastSRM input. Each element is a path to a matrix of TRs by voxels. Also, concatenate data from both hemispheres in the brain." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [], + "source": [ + "image_data = []\n", + "for s in range(subjects):\n", + " im_data = np.concatenate([image_data_left[:, :, s], image_data_right[:, :, s]], axis=0)\n", + " im_data = stats.zscore(im_data,axis=1,ddof=1)\n", + " im_data = im_data.T\n", + " np.save(\n", + " \"data/image_subject_%i\" % s,\n", + " im_data\n", + " )\n", + " image_data.append([\"data/image_subject_%i.npy\" % s])\n", + "image_data = np.array(image_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Z-score the Shared Response data" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [], + "source": [ + "image_data_shared = [None] * subjects\n", + "for subject in range(subjects):\n", + " image_data_shared[subject] = srm.transform(\n", + " np.array([image_data[subject]]),\n", + " subjects_indexes=[subject]\n", + " )[0]\n", + " image_data_shared[subject] = stats.zscore(image_data_shared[subject].T, axis=1, ddof=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Read the labels of the image data" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "labels = scipy.io.loadmat('data/label.mat')\n", + "labels = np.squeeze(labels['label'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run a leave-one-out cross validation with the subjects. We use a $\\nu$-SVM classifier." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [], + "source": [ + "train_labels = np.tile(labels, subjects-1)\n", + "test_labels = labels\n", + "accuracy = np.zeros((subjects))\n", + "cm = [None] * subjects\n", + "for subject in range(subjects):\n", + " # Concatenate the subjects' data for training into one matrix\n", + " train_subjects = list(range(subjects))\n", + " train_subjects.remove(subject)\n", + " TRs = image_data_shared[0].shape[1]\n", + " train_data = np.zeros((image_data_shared[0].shape[0], len(train_labels)))\n", + " for train_subject in range(len(train_subjects)):\n", + " start_index = train_subject*TRs\n", + " end_index = start_index+TRs\n", + " train_data[:, start_index:end_index] = image_data_shared[train_subjects[train_subject]]\n", + "\n", + " # Train a Nu-SVM classifier using scikit learn\n", + " classifier = NuSVC(nu=0.5, kernel='linear')\n", + " classifier = classifier.fit(train_data.T, train_labels)\n", + "\n", + " # Predict on the test data\n", + " predicted_labels = classifier.predict(image_data_shared[subject].T)\n", + " accuracy[subject] = sum(predicted_labels == test_labels)/float(len(predicted_labels))\n", + "\n", + " # Create a confusion matrix to see the accuracy of each class\n", + " cm[subject] = confusion_matrix(test_labels, predicted_labels)\n", + "\n", + " # Normalize the confusion matrix\n", + " cm[subject] = cm[subject].astype('float') / cm[subject].sum(axis=1)[:, np.newaxis]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a function that presents the output of the experiment in a plot" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_confusion_matrix(cm, title=\"Confusion Matrix\"):\n", + " \"\"\"Plots a confusion matrix for each subject\"\"\"\n", + " import matplotlib.pyplot as plt\n", + " import math\n", + " plt.figure()\n", + " subjects = len(cm)\n", + " root_subjects = math.sqrt(subjects)\n", + " cols = math.ceil(root_subjects)\n", + " rows = math.ceil(subjects/cols)\n", + " classes = cm[0].shape[0]\n", + " for subject in range(subjects):\n", + " plt.subplot(rows, cols, subject+1)\n", + " plt.imshow(cm[subject], interpolation='nearest', cmap=plt.cm.bone)\n", + " plt.xticks(np.arange(classes), range(1,classes+1))\n", + " plt.yticks(np.arange(classes), range(1,classes+1))\n", + " cbar = plt.colorbar(ticks=[0.0,1.0], shrink=0.6)\n", + " cbar.set_clim(0.0, 1.0)\n", + " plt.xlabel(\"Predicted\")\n", + " plt.ylabel(\"True label\")\n", + " plt.title(\"{0:d}\".format(subject + 1))\n", + " plt.suptitle(title)\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " Plot the confusion matrices and print the accuracy results" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The average accuracy among all subjects is 0.671429 +/- 0.065465\n" + ] + } + ], + "source": [ + "plot_confusion_matrix(cm, title=\"Confusion matrices for different test subjects\")\n", + "print(\"The average accuracy among all subjects is {0:f} +/- {1:f}\".format(np.mean(accuracy), np.std(accuracy)))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} From fbf992898c053f3ad55bac0f66049fdd50ebcd6b Mon Sep 17 00:00:00 2001 From: hrichard Date: Thu, 2 May 2019 10:29:39 +0200 Subject: [PATCH 14/24] correct dependencies issues --- requirements-dev.txt | 1 - setup.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 07e8e3362..bf2baa3eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,6 @@ setuptools_scm sphinx sphinx_rtd_theme towncrier -joblib # testing # Also add to .conda/meta.yaml diff --git a/setup.py b/setup.py index 87fc4c244..0090b770a 100644 --- a/setup.py +++ b/setup.py @@ -133,7 +133,8 @@ def finalize_options(self): 'pybind11>=1.7', 'psutil', 'nibabel', - 'typing' + 'typing', + 'joblib' ], author='Princeton Neuroscience Institute and Intel Corporation', author_email='mihai.capota@intel.com', From 14c73d406d099bb9d0005e9efb4c6c95a7dae812 Mon Sep 17 00:00:00 2001 From: hrichard Date: Mon, 3 Jun 2019 17:09:09 +0200 Subject: [PATCH 15/24] New example: encoding experiment (predict data from one subject given data of others) --- .../FastSRM_encoding_experiment.ipynb | 499 ++++++++++++++ .../fastsrm_image_prediction_example.ipynb | 612 ------------------ 2 files changed, 499 insertions(+), 612 deletions(-) create mode 100644 examples/funcalign/FastSRM_encoding_experiment.ipynb delete mode 100644 examples/funcalign/fastsrm_image_prediction_example.ipynb diff --git a/examples/funcalign/FastSRM_encoding_experiment.ipynb b/examples/funcalign/FastSRM_encoding_experiment.ipynb new file mode 100644 index 000000000..d85128733 --- /dev/null +++ b/examples/funcalign/FastSRM_encoding_experiment.ipynb @@ -0,0 +1,499 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Encoding experiment using FastSRM" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We train the model on a train session learning how to transfer data between subjects' space and shared space.\n", + "On a test session we use all subjects but one to compute the shared response and use this shared response to predict data from the test subject. We use R2 score as a measure of performance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Real fMRI data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll download a publicly available fMRI dataset and run SRM on these data. This dataset comprises fMRI data for 20 subjects listening to the spoken story Pie Man by Jim O'Grady (archived on the Princeton DataSpace). Note that we use 20 subjects to minimize computational demands for this tutorial and recommend larger sample sizes for publication. The gzipped data archive file is ~1.5 GB in size, and may take a couple minutes to download and unzip. The functional data were acquired with 3 x 3 x 4 mm voxels and 1.5 s TRs. Data were preprocessed using fMRIPrep (Esteban et al., 2018), including spatial normalization to MNI space (the T1-weighted ICBM 2009c Nonlinear Asymmetric template). The data were then smoothed to 6 mm FWHM using AFNI's 3dBlurToFWHM (Cox, 1996). The following confound variables were regressed out using 3dTproject: six head motion parameters (and their first derivatives), framewise displacement, six prinicipal components from an anatomical mask of cerebrospinal fluid (CSF) and white matter, sine/cosine bases for high-pass filtering (cutoff: 0.00714 Hz; 140 s), as well as a linear and quadratic trends. The anatomical template and a brain mask (i.e., excluding skull) are supplied as well. These have been resampled to match resolution of the functional images." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from urllib.request import urlretrieve\n", + "from time import time\n", + "from glob import glob\n", + "from os.path import join\n", + "import nibabel\n", + "from nilearn.image import new_img_like\n", + "from nilearn.input_data import NiftiMasker, MultiNiftiMasker\n", + "import numpy as np\n", + "from joblib import Parallel, delayed\n", + "from nilearn.plotting import plot_stat_map\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done in 643.28 seconds\n" + ] + } + ], + "source": [ + "# Download data tarball from Princeton DataSpace\n", + "t0 = time()\n", + "urlretrieve('https://dataspace.princeton.edu/jspui/bitstream/'\n", + " '88435/dsp01dz010s83s/6/pieman-isc-tutorial.tgz',\n", + " 'data_fastsrm.tgz');\n", + "!tar -xzf data_fastsrm.tgz\n", + "print(\"Done in %.2f seconds\" % (time() - t0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Step 1: Mask and save the data\n", + "- We split our data into two sessions (in order to be able to perform our encoding experiment)\n", + "- We mask the data and save them into .npy file" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Parallel(n_jobs=4)]: Using backend LokyBackend with 4 concurrent workers.\n", + "[Parallel(n_jobs=4)]: Done 5 tasks | elapsed: 16.7s\n", + "[Parallel(n_jobs=4)]: Done 10 tasks | elapsed: 24.7s\n", + "[Parallel(n_jobs=4)]: Done 16 out of 20 | elapsed: 32.7s remaining: 8.2s\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done in 40.33 seconds\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Parallel(n_jobs=4)]: Done 20 out of 20 | elapsed: 40.3s finished\n" + ] + } + ], + "source": [ + "t0 = time()\n", + "# the directory where our data are located\n", + "data_dir = 'pieman-isc-tutorial'\n", + "# Filenames for MRI data; gzipped NIfTI images (.nii.gz)\n", + "func_fns = glob(join(data_dir, ('sub-*_task-pieman_space-MNI152NLin2009cAsym'\n", + " '_desc-tproject_bold.nii.gz')))\n", + "# The mask for our data\n", + "mask_fn = join(data_dir, 'MNI152NLin2009cAsym_desc-brain_mask.nii.gz')\n", + "\n", + "# Let us mask these data and separate them into two sessions\n", + "def separate_and_mask(func):\n", + " # Load data\n", + " N = nibabel.load(func).get_data()\n", + " # Separate them into two sessions\n", + " N_1 = N[:, :, :, :250]\n", + " N_2 = N[:, :, :, 250:]\n", + " I_1 = new_img_like(func, N_1)\n", + " I_2 = new_img_like(func, N_2)\n", + " # Mask data\n", + " masker = NiftiMasker(\n", + " mask_img=mask_fn, \n", + " detrend=True,\n", + " standardize=True,\n", + " smoothing_fwhm=6\n", + " ).fit()\n", + " X_1 = masker.transform(I_1)\n", + " X_2 = masker.transform(I_2)\n", + " # Save data\n", + " np.save(func[:-7] + \"_session_1\", X_1)\n", + " np.save(func[:-7] + \"_session_2\", X_2)\n", + "\n", + "# I have 4 cores in my computer, it you have more increase n_jobs\n", + "Parallel(n_jobs=4, verbose=10)(\n", + " delayed(separate_and_mask)(\n", + " func\n", + " ) for func in func_fns)\n", + "print(\"Done in %.2f seconds\" % (time() - t0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Step 2: mask and save an atlas\n", + "- Atlases are used in FastSRM to make computation faster\n", + "- Any off-the-shelf big atlas should work (number of regions of the atlas should be larger than number of components used in SRM) we use Basc 444 for our example" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Dataset created in pieman-isc-tutorial/basc_multiscale_2015\n", + "\n", + "Downloading data from https://ndownloader.figshare.com/files/1861819 ...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r", + "Downloaded 193985 of 193985 bytes (100.0%, 0.0s remaining) ...done. (0 seconds, 0 min)\n", + "Extracting data from pieman-isc-tutorial/basc_multiscale_2015/3cbcf0eeb3f666f55070aba1db9a758f/1861819..... done.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pieman-isc-tutorial/basc_multiscale_2015/template_cambridge_basc_multiscale_nii_sym/template_cambridge_basc_multiscale_sym_scale444.nii.gz\n", + "Done in 26.51\n" + ] + } + ], + "source": [ + "def load_atlas(atlas, mask_img):\n", + " # Load masker\n", + " atlas_masker = MultiNiftiMasker(\n", + " mask_img=mask_img).fit()\n", + " X = nibabel.load(atlas).get_data()\n", + " # If the atlas is a deterministic atlas\n", + " # (each region is identified by a number starting from 1)\n", + " if len(X.shape) == 3:\n", + " n_components = len(np.unique(X)) - 1\n", + " xa, ya, za = X.shape\n", + " A = np.zeros((xa, ya, za, n_components + 1))\n", + " for c in np.unique(X)[1:].astype(int):\n", + " X_ = np.copy(X)\n", + " X_[X_ != c] = 0.\n", + " X_[X_ == c] = 1.\n", + " A[:, :, :, c] = X_\n", + " A = atlas_masker.transform(new_img_like(atlas, A))\n", + " A = np.argmax(A, axis=0)\n", + " # If the atlas is a probabilistic atlas\n", + " # (each region is assigned to a component)\n", + " else:\n", + " A = atlas_masker.transform(atlas)\n", + " return A\n", + "\n", + "t0 = time()\n", + "from nilearn.datasets import fetch_atlas_basc_multiscale_2015\n", + "atlas = fetch_atlas_basc_multiscale_2015(data_dir=data_dir)['scale444']\n", + "print(atlas)\n", + "A = load_atlas(atlas, mask_fn)\n", + "np.save(atlas[:-7], A)\n", + "atlas_path = atlas[:-7] + \".npy\"\n", + "print(\"Done in %.2f\" % (time() - t0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Note about input data\n", + "\n", + "- ProbSRM takes as input input images: a list of n_subjects np array of size (n_voxels, n_timeframes)\n", + "- FastSRM takes as input (1) input images: a matrix of npy files of size (number of subjects, number of sessions) each file should be loadable with np.load and represents a matrix of size (n_timeframes, n_voxels) (2) an atlas: np array of shape (n_components, n_voxels) in case of probabilistic atlas and simply (n_voxels) for a deterministic atlas" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Step 3: Fit of the model\n", + "- Load data\n", + "- Train model on first session" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Input shape\n", + "(20, 2)\n" + ] + } + ], + "source": [ + "subjects = [18, 19, 20, 21, 24, 25, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 42]\n", + "sessions = [1, 2]\n", + "\n", + "files = np.array([\n", + " [glob(join(data_dir, \"sub-%.3i*_session_%i*\" %(sub, sess)))[0] \n", + " for sess in sessions]\n", + " for sub in subjects])\n", + "\n", + "# 20 subjects x 2 sessions file matrix\n", + "print(\"Input shape\")\n", + "print(files.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def load_and_concat(paths):\n", + " \"\"\"\n", + " Take list of path and yields input data for ProbSRM\n", + " Parameters\n", + " ----------\n", + " paths\n", + " Returns\n", + " -------\n", + " X\n", + " \"\"\"\n", + " X = []\n", + " for i in range(len(paths)):\n", + " X_i = np.concatenate([np.load(paths[i, j])\n", + " for j in range(len(paths[i]))], axis=0).T\n", + " X.append(X_i)\n", + " return X" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fitting FastSRM\n", + "Done in 5.49\n", + "Fitting ProbSRM\n", + "Done in 38.92\n" + ] + } + ], + "source": [ + "from brainiak.funcalign.fastsrm import FastSRM\n", + "from brainiak.funcalign.srm import SRM\n", + "# We specify\n", + "# temp_dir: a temporary directory used to store spatial maps (this saves memory when number of subjects is big)\n", + "# (not really needed here but I'll use it anyway)\n", + "# n_jobs: the number of cores to use\n", + "# low_ram: in case you still have memory issues even when temp_dir is not None set this to True\n", + "# n_iter: can easily be set to 100 with no difference in fitting time \n", + "# but we set it to 10 to be fair with ProbSRM\n", + "\n", + "fastsrm = FastSRM(\n", + " atlas=np.load(atlas_path),\n", + " n_components=20,\n", + " n_jobs=4,\n", + " n_iter=10,\n", + " temp_dir=data_dir,\n", + " low_ram=False\n", + ")\n", + "probsrm = SRM(\n", + " n_iter=10,\n", + " features=20\n", + ")\n", + "\n", + "print(\"Fitting FastSRM\")\n", + "t0 = time()\n", + "fastsrm.fit(files[:, :1])\n", + "print(\"Done in %.2f\" % (time() - t0))\n", + "\n", + "print(\"Fitting ProbSRM\")\n", + "train_files = load_and_concat(files[:, :1])\n", + "t0 = time()\n", + "probsrm.fit(train_files)\n", + "print(\"Done in %.2f\" % (time() - t0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Step 4: Use the model to reconstruct data from a leftout subjects using others" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.model_selection import KFold\n", + "# List where we record test R2 scores per voxels of all subjects\n", + "var_e_fastsrm = []\n", + "var_e_probsrm = []\n", + "for subjects_train, subjects_test in KFold(n_splits=5,\n", + " shuffle=True\n", + " ).split(np.arange(len(subjects))):\n", + " shared_response_fastsrm = fastsrm.transform(\n", + " files[subjects_train, :][:, 1:],\n", + " subjects_indexes=subjects_train\n", + " )\n", + "\n", + " shared_response_probsrm = np.mean(\n", + " [load_and_concat(files[subjects_train, :][:, 1:])[i].T.dot(\n", + " probsrm.w_[subjects_train[i]]\n", + " ) for i in range(len(subjects_train))], axis=0)\n", + " \n", + " # In ProbSRM sessions are concatenated\n", + " Y_pred_probsrm = np.array([shared_response_probsrm.dot(probsrm.w_[s_test].T)\n", + " for s_test in subjects_test])\n", + "\n", + " # The reconstructed data with FastSRM is a matrix of size (n_test_subjects, n_test_sessions)\n", + " Y_pred_fastsrm = fastsrm.inverse_transform(\n", + " shared_response_fastsrm,\n", + " subjects_indexes=subjects_test\n", + " )\n", + " # Let us make fastSRM match the shape of ProbSRM\n", + " Y_pred_fastsrm = Y_pred_fastsrm[:, 0, :, :]\n", + "\n", + " # This is the true data we are trying to reconstruct\n", + " Y_true = np.array([np.load(file) for file in files[subjects_test, :][:, 1]])\n", + " \n", + " \n", + " for i in range(len(subjects_test)):\n", + " var_e = 1 - (Y_pred_fastsrm[i] - Y_true[i]).var(axis=0)\n", + " var_e_fastsrm.append(var_e)\n", + "\n", + " \n", + " for i in range(len(subjects_test)):\n", + " var_e = 1 - (Y_pred_probsrm[i] - Y_true[i]).var(axis=0)\n", + " var_e_probsrm.append(var_e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Step 5: Plot results" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R2 score FastSRM: 0.088\n", + "R2 score ProbSRM: 0.081\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Average test R2 scores\n", + "var_e_fast = np.mean(var_e_fastsrm, axis=0)\n", + "var_e_prob = np.mean(var_e_probsrm, axis=0)\n", + "\n", + "masker = NiftiMasker(\n", + " mask_img=mask_fn).fit()\n", + "\n", + "plot_stat_map(\n", + " masker.inverse_transform(var_e_fast),\n", + " display_mode=\"z\",\n", + " cut_coords=[0, 5, 10, 15, 20],\n", + " vmax=0.3\n", + ")\n", + "\n", + "plot_stat_map(\n", + " masker.inverse_transform(var_e_prob),\n", + " display_mode=\"z\",\n", + " cut_coords=[0, 5, 10, 15, 20],\n", + " vmax=0.3\n", + ")\n", + "\n", + "# R2 score in a ROI given by areas where ProbSRM performs the best\n", + "print(\"R2 score FastSRM: %.3f\" % np.mean(var_e_fast[var_e_prob > 0.05]))\n", + "print(\"R2 score ProbSRM: %.3f\" % np.mean(var_e_prob[var_e_prob > 0.05]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/funcalign/fastsrm_image_prediction_example.ipynb b/examples/funcalign/fastsrm_image_prediction_example.ipynb deleted file mode 100644 index 5930f7c2e..000000000 --- a/examples/funcalign/fastsrm_image_prediction_example.ipynb +++ /dev/null @@ -1,612 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Import some libraries that we will need" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import scipy.io\n", - "from scipy.stats import stats\n", - "from sklearn.metrics import confusion_matrix\n", - "from sklearn.svm import NuSVC\n", - "import numpy as np\n", - "from nilearn.datasets import fetch_atlas_basc_multiscale_2015\n", - "from nilearn.input_data import MultiNiftiMasker\n", - "import nibabel\n", - "from nilearn.image import new_img_like" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run SRM with the movie data" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on class FastSRM in module brainiak.funcalign.fastsrm:\n", - "\n", - "class FastSRM(sklearn.base.BaseEstimator, sklearn.base.TransformerMixin)\n", - " | FastSRM(atlas, n_components=20, n_iter=100, temp_dir=None, low_ram=False, random_state=None, n_jobs=1, verbose='warn')\n", - " | \n", - " | SRM decomposition using a very low amount of memory and\n", - " | computational power\n", - " | \n", - " | Given multi-subject data, factorize it as a shared response S among all\n", - " | subjects and an orthogonal transform (basis) W per subject:\n", - " | \n", - " | .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N\n", - " | \n", - " | Parameters\n", - " | ----------\n", - " | \n", - " | atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels]\n", - " | Probabilistic or deterministic atlas on which to project the data\n", - " | Deterministic atlas is an array of shape [n_voxels,] where values\n", - " | range from 1 to n_supervoxels. Voxels labelled 0 will be ignored.\n", - " | \n", - " | n_components : int\n", - " | Number of timecourses of the shared coordinates\n", - " | \n", - " | n_iter : int\n", - " | Number of iterations to perform\n", - " | \n", - " | temp_dir : str or None\n", - " | path to dir where temporary results are stored\n", - " | if None temporary results will be stored in memory. This\n", - " | can results in memory errors when the number of subjects\n", - " | and / or sessions is large\n", - " | \n", - " | low_ram : bool\n", - " | if True and temp_dir is not None, reduced_data will be saved on disk\n", - " | this increases the number of IO but reduces memory complexity when\n", - " | the number of subject and / or sessions is large\n", - " | \n", - " | random_state : int or RandomState\n", - " | Pseudo number generator state used for random sampling.\n", - " | \n", - " | n_jobs : int, optional, default=1\n", - " | The number of CPUs to use to do the computation.\n", - " | -1 means all CPUs, -2 all CPUs but one, and so on.\n", - " | \n", - " | verbose : bool or \"warn\"\n", - " | if True, logs are enabled.\n", - " | if False, logs are disabled.\n", - " | if \"warn\" only warnings are printed.\n", - " | \n", - " | Attributes\n", - " | ----------\n", - " | \n", - " | `basis_list`: list of array, element i has shape=[n_components, n_voxels]\n", - " | or list of str\n", - " | basis of all subjects, element i is the basis of subject i\n", - " | or path to basis of all subjects, element i is the path to the\n", - " | basis of subject i\n", - " | \n", - " | Method resolution order:\n", - " | FastSRM\n", - " | sklearn.base.BaseEstimator\n", - " | sklearn.base.TransformerMixin\n", - " | builtins.object\n", - " | \n", - " | Methods defined here:\n", - " | \n", - " | __init__(self, atlas, n_components=20, n_iter=100, temp_dir=None, low_ram=False, random_state=None, n_jobs=1, verbose='warn')\n", - " | Initialize self. See help(type(self)) for accurate signature.\n", - " | \n", - " | fit(self, imgs)\n", - " | Computes basis across subjects from input imgs\n", - " | \n", - " | Parameters\n", - " | ----------\n", - " | \n", - " | imgs : array of str, shape=[n_subjects, n_sessions]\n", - " | Element i, j of the array is a path to the data of subject i\n", - " | collected during session j.\n", - " | Data are loaded with numpy.load and expected\n", - " | shape is [n_timeframes, n_voxels]\n", - " | n_timeframes and n_voxels are assumed to be the same across\n", - " | subjects\n", - " | n_timeframes can vary across sessions\n", - " | Each voxel's timecourse is assumed to have mean 0 and variance 1\n", - " | \n", - " | Returns\n", - " | -------\n", - " | self : object\n", - " | Returns the instance itself. Contains attributes listed\n", - " | at the object level.\n", - " | \n", - " | fit_transform(self, imgs, **fit_params)\n", - " | Computes basis across subjects and shared response from input imgs\n", - " | return shared response.\n", - " | \n", - " | Parameters\n", - " | ----------\n", - " | imgs : array of str, shape=[n_subjects, n_sessions]\n", - " | Element i, j of the array is a path to the data of subject i\n", - " | collected during session j.\n", - " | Data are loaded with numpy.load and expected\n", - " | shape is [n_timeframes, n_voxels]\n", - " | n_timeframes and n_voxels are assumed to be the same across\n", - " | subjects\n", - " | n_timeframes can vary across sessions\n", - " | Each voxel's timecourse is assumed to have mean 0 and variance 1\n", - " | \n", - " | Returns\n", - " | --------\n", - " | shared_response_list : list of array, element i has\n", - " | shape=[n_timeframes, n_components]\n", - " | shared response, element i is the shared response during session i\n", - " | \n", - " | inverse_transform(self, shared_response_list, subjects_indexes=None, sessions_indexes=None)\n", - " | From shared response and basis from training data\n", - " | reconstruct subject's data\n", - " | \n", - " | Parameters\n", - " | ----------\n", - " | \n", - " | shared_response_list : list of array, element i has\n", - " | shape=[n_timeframes, n_components]\n", - " | shared response, element i is the shared response during session i\n", - " | \n", - " | subjects_indexes : list or None\n", - " | if None reconstructs data of all subjects' used during train\n", - " | otherwise reconstructs data using subject's specified by\n", - " | subjects_indexes\n", - " | \n", - " | sessions_indexes : list or None\n", - " | if None reconstructs data using all sessions\n", - " | otherwise uses only specified sessions\n", - " | \n", - " | Returns\n", - " | -------\n", - " | reconstructed_data: array\n", - " | shape=[len(subjects_indexes), len(sessions_indexes),\n", - " | n_timeframes, n_voxels]\n", - " | Reconstructed data for chosen subjects and sessions\n", - " | \n", - " | transform(self, imgs, subjects_indexes=None)\n", - " | From data in imgs and basis from training data,\n", - " | computes shared response.\n", - " | \n", - " | Parameters\n", - " | ----------\n", - " | \n", - " | imgs : array of str, shape=[n_subjects, n_sessions]\n", - " | Element i, j of the array is a path to the data of subject i\n", - " | collected during session j.\n", - " | Data are loaded with numpy.load and expected\n", - " | shape is [n_timeframes, n_voxels]\n", - " | n_timeframes and n_voxels are assumed to be the same across\n", - " | subjects\n", - " | n_timeframes can vary across sessions\n", - " | Each voxel's timecourse is assumed to have mean 0 and variance 1\n", - " | \n", - " | \n", - " | subjects_indexes : list or None:\n", - " | if None imgs[i] will be transformed using basis[i]\n", - " | otherwise imgs[i] will be transformed using\n", - " | basis[subjects_index[i]]\n", - " | \n", - " | Returns\n", - " | -------\n", - " | shared_response_list : list of array, element i has\n", - " | shape=[n_timeframes, n_components]\n", - " | shared response, element i is the shared response during session i\n", - " | \n", - " | ----------------------------------------------------------------------\n", - " | Methods inherited from sklearn.base.BaseEstimator:\n", - " | \n", - " | __getstate__(self)\n", - " | \n", - " | __repr__(self)\n", - " | Return repr(self).\n", - " | \n", - " | __setstate__(self, state)\n", - " | \n", - " | get_params(self, deep=True)\n", - " | Get parameters for this estimator.\n", - " | \n", - " | Parameters\n", - " | ----------\n", - " | deep : boolean, optional\n", - " | If True, will return the parameters for this estimator and\n", - " | contained subobjects that are estimators.\n", - " | \n", - " | Returns\n", - " | -------\n", - " | params : mapping of string to any\n", - " | Parameter names mapped to their values.\n", - " | \n", - " | set_params(self, **params)\n", - " | Set the parameters of this estimator.\n", - " | \n", - " | The method works on simple estimators as well as on nested objects\n", - " | (such as pipelines). The latter have parameters of the form\n", - " | ``__`` so that it's possible to update each\n", - " | component of a nested object.\n", - " | \n", - " | Returns\n", - " | -------\n", - " | self\n", - " | \n", - " | ----------------------------------------------------------------------\n", - " | Data descriptors inherited from sklearn.base.BaseEstimator:\n", - " | \n", - " | __dict__\n", - " | dictionary for instance variables (if defined)\n", - " | \n", - " | __weakref__\n", - " | list of weak references to the object (if defined)\n", - "\n" - ] - } - ], - "source": [ - "import brainiak.funcalign.fastsrm\n", - "help(brainiak.funcalign.fastsrm.FastSRM)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Load the input data that contains the movie stimuli for unsupervised training with SRM" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": {}, - "outputs": [], - "source": [ - "movie_file = scipy.io.loadmat('data/movie_data.mat')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Convert data to a arrays matching FastSRM input.\n", - "FastSRM is designed to work on large datasets where all data are not assumed to hold in memory. Therefore it takes as input npy files and not matrices.\n", - "Files should be loaded with np.load and represent matrix of TRs by voxels. " - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(10, 1)" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "movie_data_left = movie_file['movie_data_lh']\n", - "movie_data_right = movie_file['movie_data_rh']\n", - "subjects = movie_data_left.shape[2]\n", - "movie_data = []\n", - "\n", - "# We treat these data as having only one session\n", - "paths = []\n", - "for s in range(subjects):\n", - " # concatenate data from both hemisphere\n", - " data_s = np.concatenate([movie_data_left[:, :, s], movie_data_right[:, :, s]], axis=0)\n", - " # zscore the data\n", - " data_s = stats.zscore(data_s,axis=1,ddof=1)\n", - " # input should be n_timeframes x n_voxels to match with \n", - " # n_samples, n_features framework of sklearn\n", - " data_s = data_s.T\n", - " np.save(\n", - " \"data/movie_subject_%i\" % s,\n", - " data_s\n", - " )\n", - " _, n_voxels = data_s.shape\n", - " paths.append([\"data/movie_subject_%i.npy\" % s])\n", - "paths = np.array(paths)\n", - "\n", - "# paths is now an array of shape (number_of_subjects, number_of_sessions)\n", - "paths.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run FastSRM " - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": {}, - "outputs": [], - "source": [ - "# Let's use a naive probabilistic atlas (all voxels are used)\n", - "# In practice one would choose an off-the-shelf atlas like\n", - "# nilearn.datasets.fetch_atlas_basc_multiscale_2015\n", - "A = np.eye(n_voxels)" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "temp_dir has value None. All basis (spatial maps) and reconstructed data will therefore be kept in memory.This can lead to memory errors when the number of subjects and/or sessions is large.\n" - ] - }, - { - "data": { - "text/plain": [ - "FastSRM(atlas=array([[1., 0., ..., 0., 0.],\n", - " [0., 1., ..., 0., 0.],\n", - " ...,\n", - " [0., 0., ..., 1., 0.],\n", - " [0., 0., ..., 0., 1.]]),\n", - " low_ram=False, n_components=50, n_iter=100, n_jobs=1,\n", - " random_state=None, temp_dir=None, verbose='warn')" - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "srm = brainiak.funcalign.fastsrm.FastSRM(n_components=50, atlas=A)\n", - "srm.fit(paths)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Load the input data that contains the image stimuli and its labels for training a classifier" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "metadata": {}, - "outputs": [], - "source": [ - "image_file = scipy.io.loadmat('data/image_data.mat')\n", - "image_data_left = image_file['image_data_lh']\n", - "image_data_right = image_file['image_data_rh']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Convert data to a list of arrays matching FastSRM input. Each element is a path to a matrix of TRs by voxels. Also, concatenate data from both hemispheres in the brain." - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "metadata": {}, - "outputs": [], - "source": [ - "image_data = []\n", - "for s in range(subjects):\n", - " im_data = np.concatenate([image_data_left[:, :, s], image_data_right[:, :, s]], axis=0)\n", - " im_data = stats.zscore(im_data,axis=1,ddof=1)\n", - " im_data = im_data.T\n", - " np.save(\n", - " \"data/image_subject_%i\" % s,\n", - " im_data\n", - " )\n", - " image_data.append([\"data/image_subject_%i.npy\" % s])\n", - "image_data = np.array(image_data)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Z-score the Shared Response data" - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "metadata": {}, - "outputs": [], - "source": [ - "image_data_shared = [None] * subjects\n", - "for subject in range(subjects):\n", - " image_data_shared[subject] = srm.transform(\n", - " np.array([image_data[subject]]),\n", - " subjects_indexes=[subject]\n", - " )[0]\n", - " image_data_shared[subject] = stats.zscore(image_data_shared[subject].T, axis=1, ddof=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Read the labels of the image data" - ] - }, - { - "cell_type": "code", - "execution_count": 57, - "metadata": {}, - "outputs": [], - "source": [ - "labels = scipy.io.loadmat('data/label.mat')\n", - "labels = np.squeeze(labels['label'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run a leave-one-out cross validation with the subjects. We use a $\\nu$-SVM classifier." - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "metadata": {}, - "outputs": [], - "source": [ - "train_labels = np.tile(labels, subjects-1)\n", - "test_labels = labels\n", - "accuracy = np.zeros((subjects))\n", - "cm = [None] * subjects\n", - "for subject in range(subjects):\n", - " # Concatenate the subjects' data for training into one matrix\n", - " train_subjects = list(range(subjects))\n", - " train_subjects.remove(subject)\n", - " TRs = image_data_shared[0].shape[1]\n", - " train_data = np.zeros((image_data_shared[0].shape[0], len(train_labels)))\n", - " for train_subject in range(len(train_subjects)):\n", - " start_index = train_subject*TRs\n", - " end_index = start_index+TRs\n", - " train_data[:, start_index:end_index] = image_data_shared[train_subjects[train_subject]]\n", - "\n", - " # Train a Nu-SVM classifier using scikit learn\n", - " classifier = NuSVC(nu=0.5, kernel='linear')\n", - " classifier = classifier.fit(train_data.T, train_labels)\n", - "\n", - " # Predict on the test data\n", - " predicted_labels = classifier.predict(image_data_shared[subject].T)\n", - " accuracy[subject] = sum(predicted_labels == test_labels)/float(len(predicted_labels))\n", - "\n", - " # Create a confusion matrix to see the accuracy of each class\n", - " cm[subject] = confusion_matrix(test_labels, predicted_labels)\n", - "\n", - " # Normalize the confusion matrix\n", - " cm[subject] = cm[subject].astype('float') / cm[subject].sum(axis=1)[:, np.newaxis]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define a function that presents the output of the experiment in a plot" - ] - }, - { - "cell_type": "code", - "execution_count": 59, - "metadata": {}, - "outputs": [], - "source": [ - "def plot_confusion_matrix(cm, title=\"Confusion Matrix\"):\n", - " \"\"\"Plots a confusion matrix for each subject\"\"\"\n", - " import matplotlib.pyplot as plt\n", - " import math\n", - " plt.figure()\n", - " subjects = len(cm)\n", - " root_subjects = math.sqrt(subjects)\n", - " cols = math.ceil(root_subjects)\n", - " rows = math.ceil(subjects/cols)\n", - " classes = cm[0].shape[0]\n", - " for subject in range(subjects):\n", - " plt.subplot(rows, cols, subject+1)\n", - " plt.imshow(cm[subject], interpolation='nearest', cmap=plt.cm.bone)\n", - " plt.xticks(np.arange(classes), range(1,classes+1))\n", - " plt.yticks(np.arange(classes), range(1,classes+1))\n", - " cbar = plt.colorbar(ticks=[0.0,1.0], shrink=0.6)\n", - " cbar.set_clim(0.0, 1.0)\n", - " plt.xlabel(\"Predicted\")\n", - " plt.ylabel(\"True label\")\n", - " plt.title(\"{0:d}\".format(subject + 1))\n", - " plt.suptitle(title)\n", - " plt.tight_layout()\n", - " plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " Plot the confusion matrices and print the accuracy results" - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZ8AAAEgCAYAAABrfn40AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJztnXmYHVWZ/z/fJB2SdEICJEQkYVcU44/FGEEYRBAIDuAGMzCjgMvgMiqocUEYEUaQGXFAB8cZZBcBNYKDyExAxGExQQhrWAWFSTIhCUtC0lnp+/7+OKfJTXfde+tudau638/z1NN9T51z6r31vafeqnNOvUdmhuM4juNkybBOG+A4juMMPdz5OI7jOJnjzsdxHMfJHHc+juM4Tua483Ecx3Eyx52P4ziOkznufDJC0mhJv5K0UtLPm6jnbyXd0krbOoWkv5D0ZIbHmyzpDkmrJH23DfXvJMkkjYif/0vSiWX7vyXpBUnPx88fkLRQ0mpJe7fanqGApN9J+kSFfTvEczs8a7uc2rjz6Yekv5F0X/zRLokXkANaUPUxwGRgGzM7ttFKzOwnZnZYC+xpK/EivFu1PGZ2p5ntnpVNwMnAC8CWZvaldh/MzI4wsyshXAiBLwF7mNnrYpbzgc+a2Vgze6Dd9pQj6ZuSrq6R51lJ72nBsU6SdFez9dSLmf1vPLe9zdTTqvPgbI47nzIkfRG4EDiX4Ch2AP4NeF8Lqt8ReMrMXm1BXYWn7+kgY3YEHrMG3qxugb07AC+a2bJ+9jzaSGUdOn+O0zrMbFBtwGeB+4D1wBV1lBsPrAaOrZJnC4Jz+r+4XQhsEfcdBCwi3N0uA5YAH437zgI2ABvjMT4OfBO4uqzunQADRsTPJwF/AlYBfwb+tiz9rrJy7wTuBVbGv+8s2/c74B+Bu2M9twATK3y3Pvu/Umb/+4H3Ak8BLwFfL8s/A5gLrIh5LwJGxn13xO/SE7/vX5fV/1XgeeDp+LcEPAgcAUwFrgeWAy8CF5Ud72PA48DLwBxgx5gu4IJo8yvAI8C0hO93RTz/G6JN70mpZ5+9P06oczjh6eWFqNXf99Pwd8An4rHWxu+6Grg2/u07R8/E/K8HfhG//5+Bz5cd65vAbODq+D0/Qbh5/BrwTDxfPwO27vd7OhH432jj6XHfTDb/PT6U8N1+HMv3bcvjMfcFfh91fwg4qKzMSfT7zQJvBtYBvfFYKyr8/gaULfve1drJ74BvA3+I5+U/E85BX97xwKWE3+ti4FvA8LK6/47wG1sFPAbsE89DKeq3mtA+RkUdXozn4V5gcoeve2+I5/nqTtpRl82dNqANInyQcNH8IfU5n5nAq30/1Ap5zgbmAdsCk2Ij/Me476BY/mygi3DRXgNsFff3b0QVGxXQHRvS7nHfdsBb4v8nEZ0PsDXhYvyRWO74+HmbuP93hAvTG4HR8fN5Fb5bn/3fiPb/HeGCcw0wDnhLbIA7x/xvI1yIRkTbHwdOLavPgN0S6v8nYKvY8I8jXOCPLGvwF8TvPwo4IJZ9H8FZvTke7wzg93Hf4cB8YALBEb0Z2K7Cd7wC+Fadev4TwUmNTqjvU8ATBKe5NXA7Cc6nrL5F/cq/do4IjmR+PP8jgV0IF+PDy34vGwm/7WFRz1Oi/VOijf8BXNvv9/SjmHdPwg3Zm5N+fxXO12LgiPj/mwgOfgXhtz0MOJRwAZ5Eyt9sheNUK7uZnSQ7n8XAtFjPL/ryJ+S9IZ6j7qj5H4BPxn3HxnreTvgd7camG5xngfeU2fBJ4FfAGMINyNsIXbmdvO7dAtxZS9M8bYOu283MrjezXxIaRT1sA7xg1bvF/hY428yWmdlywhPNR8r2b4z7N5rZzYQ7pUbHNErANEmjzWyJmSV1z/wl8Ecz+7GZvWpm1xIuhkeV5bnczJ4ys7WEO+O9qhxzI3COmW0ErgMmAt8zs1Xx+I8RLmKY2XwzmxeP+yyhUb8rxXc608xeNrMzCE8UmNlNwFJge+DLZtZjZuvMrG+c4FPAt83s8ajPucBeknaMNo8jXBwV8yypYUcftfTss3d9PH/9+SvgQjNbaGYvEe7AG+XtwCQzO9vMNpjZnwiO47iyPHPN7JdmVor2fIrwNLPIzNYTLtTH9OuSO8vM1prZQ4QnlT3rsGlj3CBcxEcBD5vZzdGGWwm9DO+NedL8ZivRTNkfm9kCM+sB/gH4q/6TDCRNjnaeGn9fywg3On3n9xPAP5vZvRZ42syeq3C8jYTrxW5m1hvbwit12NtSJB1HuCm4rVM2NMKgcz5N8CIwsUZf+uuB8h/kczHttTr6Oa81wNh6DYmN6K8JF5clkn4t6U0p7Omzafuyz8/XYc+Ltmlwtu9iu7Rs/9q+8pLeKOkmSc9LeoXgECZWqRtguZmt658YLww7AAsrOP8dge9JWiFpBaELUMD2ZvZbQpffD4Blki6WtGUNO/qopWeivf3KL+xXvlF2BF7f9x3j9/w6Yeyxj4UJZW4oy/84oXurvEw9+ifxeUlrCDc164EZ/Ww8gPCkmfY3O4Bmykb6a9DFwN/ijjF9SZnt/0F4AoLw9PpMyuP9mND1e52k/5P0z5K66rC3ZcTf+tnAFztx/GZw57OJuYTG9f4qef6P8CPuY4eY1gg9hMf2Pl5XvtPM5pjZoYQuiCcId8G17OmzaXGDNtXDDwl2vcHMtiRcKFWjTKWB/p8Avwa2reD8FxK6RyaUbaPN7PcAZvZ9M3sbsAehi/HLKb9DLT1rTUxYQrholZdvlIXAn/t9x3Fm9t6yPP3tWUjoFisvM8rM0uifZtKFAd8nPFn+BfAAoVun/HjdZnYeVP3N1jxWlbJV20mkvwYbCWNc5SwktO+JZbZvaWZvKdu/ayXz+tm60czOMrM9CGOuRwInVP2C7eMfgUvNbFGHjt8w7nwiZraS0N/+A0nvlzRGUpekIyT9c8x2LXCGpEmSJsb8VaerVuFB4MD4LsJ44LS+HfF9lPdJ6iY0mNWEbon+3Ay8MU4PHyHprwkX4JsatKkexhH66VfHu9RP99u/lDBuUQ0Rxko2ELo/lgDnSeqWNErS/jHfvwOnSXoLgKTxko6N/79d0jvinWcPYdA16Vwl0ayePyM8GUyRtBVh8L9R/gCskvTV+E7YcEnTJL29Spl/B86J3Y/E75F2ZuZSYCdJ1a4BS4FdYtfSXfHzX0k6PNo3StJB8ftX+80uBaZIGpl0kBplK7aTMj4saQ9JYwhPAbOt3/Tq2BV7C/BdSVtKGiZpV0l9XcWXALMkvU2B3frOK/1+y5LeLemtsWvvFYKzS/ubaxmS9iJMZrkg62O3Anc+ZZjZdwmPr2cQBtsXEmbP/TJm+Rahj/thwqyq+2NaI8e6FfhprGs+mzuMYdGO/yN0Mb2LgRd3zOxFwl3Xlwjdhl8BjjSz/nd97WAW8DeEiQI/InyXcr4JXBm7OP6qf2FJItg7DPhQHLM4ijDQ+7+EiQh/DWBmNxAG/q+LXXwLCLPjALaMx3+Z0OXyIvCdlN+hWT1/ROh+eSiWvb6OspsRL5ZHEsbk/ky4c7+EMEOrEt8DbgRukbSKMPngHSkP2fei84uS7q+Q59sE57xC0iyCY7iZ8JTb1z6+TNCw2m/2t4Qp5c9LSvptVixbo5308WPCZJLnCeNSn6/wfU4gTOZ4jPB7mU140sLMfg6cQ5hgs4rQ5reucB5eF8u+Qujq/J9oQ9YcRJhU8b8KLy7PAj5URc9cIbO6X3nINbHbZgRwJmEW0N8Br1YYS3A6hKR/J1xo32Nmqzttj7M5krYFDiZc7NcS7rCvB443sxs7aVtaJO1CeE2gywbbhQ6IT3rl45uzCM7o03ECTa4ZjE8+ZxAay9eAD8f/z+ioRc5mxO6MTxKcz/MK0SRWS/rbDpvmbMIITx+LCE8J5xNmihXC8USmAc8NRscDYGZrzOz5vo3QXbmuCI4HBuGTj+M4jkK0kq8An4tdak7OcOfjOI7jZM5g7HZzHMdxck7FFyprvajXyTd6nYBrVAxcp/zjGmVPxW43SQsJg47lLw72fTYza+aFOqcFuEbFwHXKP65R9rRtzEfSZYT3FpaZ2bS2HMRpGtcp/7hG+cc1qp9UzkchcN0uZnaupCmE8OHza5Q5kDD176q0YnSP29K2njhpQPrLy5NjhPb0rExTbUN0dW0xIO3VVzdSKvXWCiHTERrRKJarS6ctthht3d3V3nvcnNWrVySmb9y4PnUdlSiaRpBNWxo2bLiNGDEw1NjUXXdOzP+nJ55IY3pDJGkEsHHj+hfMbGBjzwFZaJT3dhTrbqtGNRekknQRISDfgYTgkWsIYT2qhf3AzO6QtFM9xmw9cRKnnvVPA9Jn/9tVifnn3fOreqqvi8nb9g+ZBkuXNRM3sn00qhHUr1N393gOOeTDqW2bNzdZo0WLn0pdRyWKpBFk15ZGjOhKPDfnXn55Yv7j9tsvbdV1k2QHwKLFT+VSqKw0yns7inW3VaM0qyG+08z2kfQAgJm9VClGk9MxXKNi4DrlH9coI9JMtd4Ygw8agKRtaGEQPUknS7pP0n09q3xCSYNkptH69WtaVe1QpG06lWtUKvXWLuBUIhONvB2lcz4/IKwOOEnSWcBdhCCPLcHMLjaz6WY2vXtc2mVYioGkyyQtk7SgzYfKTKMtthhTu0DBGAw6lWs0bNjw2gUKxmDTyNtRim43M7tK0nxCYEGAY82sZuWSphJC1u8m6VHgYjP7XrUyPSt7mPereSnMbj/77nfUgLTbbqt79YQrCAudJQ9atYgmNbqKEIB1qqRTamm0evWKxP7nY04aEHQbgCkLkxdybUVfdQu5gvzrlLotjR07IfH3u+ip7Jd8aaHOVzCINNqwYR2LFg48N8d8JnlZoNmzv5vC+sCU7d+YmF5Ji05plDbCwXDCmhUb6ijzfUJ4/OHABMJ6LHukLDsoMLM7CCHis6ARjV4lhMkfG8ufL2nIBWEtgE7ellyj3FOvRjVPrKTTCR799YQ75GskJS3o1N+QD5jZJDPrMrPtgXvYfHlnp0U0odESM5tpZtuZWRdhrZZ72mvt0MXbUv5xjbIjjVc/AXi7mZ1hZqcDM4CT6jlInIK4NwkXtqIOwh1++OEmaUGf7XE7uUPmZKZRkQazDz/8cJs+ffqQ0Kmo7QiGTlsq12jjxg2tsTYj2qFRmqnWS/rlGxHTUiFpLGEA79Sk+EhmdjFwMcDWW7+uMCG2ly5fDmHtjOmdtoUMNRo5clShNPrtnXeyzdixg16norYjGDptqVyjsWMnDHmNqgUWvYAw3fAl4FFJc+Lnw4B701QuqYsgxE/MrOElhvNIqZT5ku0DcI2qUyqVWLV2bafNcJ1q4G0p/7RDo2pPPn0zPB4Ffl2Wnmo6mqRRhFUQDZgiabyZnVmtTKUZIJUiGVw3d25ieive2K41u6Q3hRiSriWssz5R0iLgTDO7tGnjNpG5Rhs3rk+cHXPhOV9IzF9Jo1nHPJnGRKDxWTq9pRIr19TughpsOr388tLE3+/s2cn562lH9c6kSpN/KLalnp6Vide1Ste6KgGgB6S1YyZpOzSq6HxaIOx0YBvgEcJLWrMkrTCzC5qsNxekEcPMjm+nDa5RdXpLJVakePJxnTqLt6X80w6N0sR22xU4B9gDGFV2oORbmk377yKGJ5c0hvCy1u/rMS7PpBEjK1yjZHpz0u3Wh+uUjLel/NMOjdLMdrsCuJxwYo8Afgb8NE3lkoZLehBYBtxqZlVnUhVpBkieGgwZatQ6k9tP2m63DLmCNulUVI1g6LQl12hz0jifMWY2B8DMnjGzMwii1MTMes1sL8J8+RmSBoQaLw850dVVnPh9pVKuJqtkplFLrW4zpZKxbnV+nnxoo05F1QiGTltyjTYnzVTr9QqB9p6R9ClgMTCunoOY2QpJtwMz2TSwV2hKvbm6W3ONEij1llizKlfOx3VKwNtS/mmHRmmczxeAbuDzhL7Q8cDHahWSNIkQomIVMB+YCpxYrUylGSCVqDSrbcHChQPSZu57SGLeRmeG5KzBZKZRvVTSKGn2TtLMnWYo9ZZY80quut2a0akXuI3wzsk4Whg4tp0apWlf3pZqU+m8Z9GOoEPOp6zfchXwkTrq3g64EpgMjAGWmtlNdVuYUyxH/dSuUTJWKuWq261JnW4mXBR3Bc4fbDrlBW9LybRDo2ovmd5AXNMi0RizD1ar2MwelnQUQZBzgC82amQeycPdmmtUnbx0uzWrE+HFxyeJGpnZ2S00r+N4W8o/WT/5XNSC+i8EvkKVPtMYH6hTcZwaJg9vZeMaVaVUKrE2H08+zeo0aDWCodOWXKPNqfaS6W3NVCzpSGCZmc2XdFCV47wW70hSrqa9VCMPd2uuUXVKvSXWrur8mE8zOg12jWDotCXXaHPSTDholP2BkyV9gjBnfpikq83sw208JgDTpk4dkFZPeIo05Gx6aKN0TKOk814pzMuFp56bmF5rckqpZKxdva5+4/LF/sDR8a7ZgOGSXjSzbdp94CSNWt2OwNtSMxRZo7QLJdWNmZ1GiHX0euBQ4L+yuKhlRR7u1pplKGi0JgdPPs1gZqeZ2RSCTscAN2fheLLE21L+aYdGqZ2PpC1afvQCk8d1bVyjzSmVelm7ZlWnzRiA67Q53pbyTzs0SrOS6QxJjwB/jJ/3lPSvKes34Bbgu8CNDVuZQ3IySAq4RpUolUqsXbu602a8Rgt0OhPYTp1baK0teFvKP1kvqdDH94EjgV8CmNlDkt6dsv4DzGyxpG2BWyU9YWGd79co6gyQnN2tuUYJlEq9rFkzYG28TtI2nYqqEQydtuQabU6abrdhZvZcv7RUlpjZ4vh3GXADYUna/nkKGe/ILFcNxjVKwKyXdTl68qGNOhVVIxg6bck12pw0Tz4LJc0ATNJw4HNAzZgZkrqBrQhz399KCDfx9SZsHcC+7zgqMT1pFtTUKbsn5j3/yp8nps868diqx85TVwE51qiexcdmHZMcjeT82Vcmph+3X63ZbiXWrM3VmE9udUoiSbv99j06Me+ppycvWzP7ih8mppfr722pNvWc30rXunqul/1ph0Zpnnw+TXhbdwdgKbBvTKvFZOBh4B3Aq8B5hHDlg4K0j6GSZkp6UtLTkr7WJnNcowRKpd5UYz4ZaQSuUyJp2pJr1Fnacb1LE9ttGXBcOhM340VgJbCLVZp4XmBSNpjhwA8IUy8XAfdKutHMHmulLa5RMmG2W/Uxn6w0AtepErXakmvUedpxvUuzkumPSIh5ZGa1Bs52BpYDl0vakxDp9RQz6+lXfyEH4VI+hs4AnjazPwFIug54H9DSRuMaJZOy2y0TjWLdbdOpqBpBqrbkGnWYdlzv0nS7/YYQyv024G5gW2B9inIjgH2AH5rZ3kAPMOAxrKiDcGalOcAoxZUJ49b/h7U9UL6+w6KY1mpcowTMSnPWreuZT3WdstII2qhTUTWCVG3JNeow7bjepel222wJWUk/JqxPXotFwKKyEOWzSbiwFRUzm9lpG/pwjZLJk0bgOlUiTzq5Rsm0Q6NGYrvtTBhcq8V4YKKkxwl3Dm8m3EnUTaUZU/UsPFdpUatKs9qSFqT7q/e+N/XxIosJs176mBLT2k1uNKpnsb5KeetZNLBAGkGbdOrq2oLJ2+44IL3S+a1Hu0p1VGqLleL1VdK0AkNeo3m/+W1ieivaV6MzfhOoS6c0EQ5elvRS3FYAtwKn1SpnZk8CBxAeP4cR+lFPqVVukHEv8AZJO0saSRjIbPmbz65RU2SiEbhOTeAaFYO6dKr65CNJwJ5s8l6lemZymNmDwHRJhwFnmtkjacsOBszsVUmfBeYAw4HLzOzRVh7DNWqOLDQC16kZXKNiUK9OVZ2PmZmkm81sWpN2HQdcm7SjyDNA0mBmNxOWQG5X/a5Rk7Rbo3iMtupUrtHw4e1cKaUzuEbFoB6d0sx2e1DS3o0aEx+/jgYSOxaLPAMkR7hGxaBtOpVrNGzY8GZsHOq4RhlR0f1KGmFmrwJ7E14WeobQnynCTcI+KY9xBHC/mS1t2lpnM1yjYuA65R/XKHtUqUtT0v1mto+kXZP2m9kzqQ4QXjSaY2aXp8i7HHgOmAi8kJClFenN1LGjmU1KyNMROqwRZHfe66k7VxpB9jo1oVGl9HZolyudBoFGldLzq5GZJW7AA5X2pd2AbkLYifF1lruvXemtqjsPWyc16tR5r6fuvGxFb0vt1C4vW9E1ard27diqjXpNkvTFKk7rX6qU7cvTAwyqJX9zhmtUDFyn/OMaZUw15zMcGEvo83TyiWtUDFyn/OMaZUw157PEzM7OzJLNubiN6a2qOw90UiPozHmvp+68UPS21E7t8kLRNaqUnluNqk04eMBCgDwnp7hGxcB1yj+uUfZUcz5bm9lLGdvj1IFrVAxcp/zjGmVPRefjOI7jOO0iTYSDzJB0maRlkhb0S58q6XZJj0l6VNIpMX2UpD9Ieiimn1VWZrikByTdVJb2rKRHJD0o6b6y9AmSZkt6QtLjkvaTtHvM17e9IunULM5D3knSyTXKF63SKO5PpZNrVB/1XO9apVFMz4dOWc3pTjnf/UDCgkwL+qVvB+wT/x8HPAXsQZiZMjamdwH3APvGz18ErgFuKqvnWWBiwnGvBD4R/x8JTOi3fzjwPOGlq46fp05vSTq5RvnaWqVRPTq5Rs1rVEOnpjXKk065evIxszuAAf2uZrbEzO6P/68CHge2t8DqmK0rbiZpCvCXwCW1jilpPOFHcGmsf4OZreiX7RDgGTN7rn/5oUiSTq5RvmiFRgBpdXKN6qeB611TGsW8udEpV84nDZJ2IsRfuid+Hi7pQWAZcKuFlQQvBL4C9F943IBbJM3XpiVgy9def0DSJZK6+5WrGPHZGYhrlH9SagTpdXKN2kC5Ti3QCHKkU6Gcj6SxwC+AU83sFQAz6zWzvQir5s2Q9BlgmZnNT6jiAAsBAo8A/l7SgdRYe101Ij47m+Ma5Z+UGk2TdCQpdQLejmvUUvrr1KxGeWtLhXE+kroIQvzEzK7vvz8+Ot4OfAg4WtKzwHXAwZKujnkWx7/LgBuAGSSvvV4ewdaj1KbENco/dWg0E9if9DpNwTVqGdV0akKjXLWlQjgfSSL0UT5uZTGWJE2SNCH+Pxo4FLjAzKaY2U6Ex8ffmtmHJXVLGhfzdgOHEQb6ngcWSto9VnsI8FjZ4Y/Huwpq4hrlnzo1esLMTqtDp7m4Ri0hSacWaZSvtlTP7IR2b/FLLwE2Ejz0x2P6AYT+y4eBB+P2XuD/AQ/E9AXAN/rVdxBx9gewC/BQ3B4FTi/LtxdwX6znl8BWMb3hiM+DeUvSyTXK19ZKjdLq5Bo1r1FMT9LpM63QKE86+UumjuM4TuYUotvNcRzHGVy483Ecx3Eyx52P4ziOkznufBzHcZzMcefjOI7jZE7HnY+k3hhFdYGkn0sa00RdB/VFdZV0tKSvVck7Ib5pX+8xvilpVqM2FhHXqBi4TvnHNdpEx50PsNbM9jKzacAG4FPlOxWo204zu9HMzquSZQJh7rxTG9eoGLhO+cc1iuTB+ZRzJ7CbpJ0kPSnpKsILVVMlHSZprqT74x3DWABJMxXWpbgf+GBfRZJOknRR/H+ypBsU1sJ4SNI7gfOAXeNdyHdivi9LulfSw9p83ZnTJT0l6S5gd4Y2rlExcJ3yz9DWKAdv+a6Of0cA/wl8GtiJEKG1b92XicAdQHf8/FXgG8AoYCHwBsKaJD9j0xu+JwEXxf9/SgjOB2GtivHxGOVrnRwGXBzrGQbcRAg9/jbgEWAMsCXwNDCr0+fNNXKNXKfiba7Rpm0EnWe0QphwCHcClwKvB54zs3kxfV/CYkp3S4KwANJc4E3An83sjwAKAfVOZiAHAydAiN4LrJS0Vb88h8Xtgfh5LEHkccANZrYmHuPGpr5tMXGNioHrlH9co0genM9aC2HCXyOe8J7yJML6Fcf3y7dZuSYR8G0z+49+x/Alf12jouA65R/XKJK3MZ9KzAP2l7QbhCitkt4IPAHsJGnXmO/4CuVvIzze9i2aNR5YRfDyfcwBPlbWt7q9pG0Jj7/vlzRaIUrsUS3+boMF16gYuE75Z0hoVAjnY2bLCX2a10p6mPgIambrCI+dv44DcMsqVHEK8G5JjwDzgT3M7EXCY+0CSd8xs1sIa6DPjflmA+MsLGf7U0J02P8C7m3bFy0wrlExcJ3yz1DRyKNaO47jOJlTiCcfx3EcZ3DhzsdxHMfJHHc+juM4Tua483Ecx3Eyx52P4ziOkznufBzHcZzMcefjOI7jZM6gdD6SfidpnaTVcXuy0zY5A5F0nKTHJfVIekbSX3TaJmcTZe2nb+uV9K+dtsvZHIWo2DdLelnS85IukpSH0GlVGZTOJ/JZMxsbt6Eeuj13SDoU+Cfgo4SwHwcCf+qoUc5mlLWfscDrgLXAzztsljOQfyNEO9gO2At4FzlbuyeJ3HtHZ9ByFnB2WSTfxZ00xqnJhwgXuDs7bYgzgJ0JyymsA56X9N/AWzpsU00G85PPtyW9IOluSQd12hhnE5KGA9OBSZKelrQodhWM7rRtTkVOBK4yj8eVRy4EjpM0RtL2wBHAf3fYppoMVufzVWAXYHvCgkm/KosE63SeyUAXcAzwF4Sugr2BMzpplJOMpB0JXTlXdtoWJ5E7CE86rwCLgPuAX3bUohQMSudjZveY2SozW29mVwJ3A+/ttF3Oa6yNf//VzJaY2QvAv+Aa5ZWPAHeZ2Z87bYizOZKGEZ5yrge6CaugbkUYT801g9L5JGCExZOcHGBmLxPu0Mq7cLw7J7+cgD/15JWtgR0IYz7r49IJl1OAG7lB53wkTZB0uKRRkkZI+lvCTKrc94EOMS4HPidpW4Ulfr9AWEfeyRGS3knovvZZbjkk9hr8Gfh0vN5NIIzPPdxZy2oz6JwPYSzhW8By4AXgc8D7zeypjlrl9OcfCQtVPQU8TlhL/pyOWuQkcSJwvZmt6rQhTkU+CMwkXPOeBjYSbuZyjS8m5ziO42TOYHzycRzHcXJOxZdMJW1ZraCZvdJ6c5x6cI2KgeuUf1yj7KnY7SZpIQNnifV9NjPbof3mOdVwjYqB65R/XKPs8TEfx3EjYK6GAAAgAElEQVQcJ3NSxXaTdBywi5mdK2kKMNnM5tcocxlwJLDMzKalOU5X10jbYosxA9K3mrRNYv6li5PDgW3cuD7N4RrCzHL5vlAjGsVydelUSaPuceMS87/84vLE9FZo1NW1xYC0V1/dSKnUm0uNIJu21D1uS9t64qQB6T0rexLzv/zy0jSmt5oXzGygkTkgC41GjxlrW47fakD6sucXJebv7h6fmN7Ts7LWoWpSpe62alTzyUfSRYTpywea2ZslbQ3MMbO31yh3ILCaEA8qlfMZO3aCvXXagQPSj/nMCYn5L/z66Ynpixa3b1Z1Hp1PoxrFsnXpVEmjfd9zcGL+2Vf8MDG9FRpN2f6NA9KWLnuODRvW5U4jyK4tTd15Vzv1rIEvuM/71byE3DB79ndTWN9y5pvZ9E4cuBpZaTR5u6n2Nx//0oD0C89JniG97zuOSkyfd8+vah2qJlXqbqtGaWa7vdPMPgmsAzCzl4CRtQqZ2R3AS82Z56SkIY1iXtcpO7wt5R/XKCPSOJ+NMX6QAUjaBii1ygBJJ0u6T9J9GzduaFW1Qw3XqBi0TadyjXpW+cSsJshEo7VrkrtAhxJpnM8PgF8Qwt+fBdxFC4PWmdnFZjbdzKZ3daW6WXcG4hoVg7bpVK5R97iqs4ad6mSi0egx3a2ostDUnHBgZldJmg+8JyYda2YLapWTNBW4FthN0qPAxWb2vWplNmxYx6JFA1e8nnnwvon5Z7VxbKcVNDLpohGa1OgqYAowVdIptTTaatI2iWNws//tqsT87Rx/axUF0Sl1W+pZ2ZM4vjNl9ymJ+ZPGziBf2g02jSpRSYtWjO1UPObU5GNyT9sOCaSPcDCcEC9oQx1lXiXEWHsa2Bf4e0l71G1hsbmCEHMpCxrV6EvAoYQlrIeiRlAMnbwtuUaDiponVtLpBI/+esId8jWSTktR978AlwC7EwJH9hCi4w4ZshqEbFQjM1sCfBmYC7wBmAp8vI2m5pK864S3JddoEJLGq58AvN3MzjCz04EZwEm1CpnZ8Wa2nZl1AQcA29D2B7nsmDlzpkla0DeAGLeTO2ROQxrBJp0Izmc5cFbbrMyYmTNn2vTp0wuvk7elTHGNEjjgoINMUkuXpUnzkumSfvlGxLRUSBpLGMA7NSk+UvyRnQwwfHiqd15zwbLlywHW5eRdhcw02mqbic1ZmiHLli/njrvvZtzo0YNep3KNxoxJfuE3rwyVtlSu0bgtB75gmmdefPFFCKuktoxqgUUvIEw3fAl4VNKc+PkwwjosNZHURRDiJ2Z2fVIeM7sYuBhg5MhRhYn101tq2UzmhumERlN33rVQGr2ydm3tjG0mC53KNdp669cVRiMYOm2pXKPJ200tlEalNmhU7VGjb4bHo8Cvy9KTX5Puh6RRbFoqeYqk8WZ2ZrUyGzeuT5xhM23q1GQDFy5MTK+UP4lGZ/rkocHQAY2WLl6cGFmi0vm6bu7cxPTj9ttvQFq9WqTRaGUOnA8Z67R69QrmzU2YHZUsBefPTl4hO0mjdjAU21LPqlXM+81vUxt3/pXJC8nOOvHY1HVUIk2Ei942xACt6HzM7NIm655O6Pd8hPCS1ixJK8zsgibrzQVpGoyka4GDgImSFgFntuC8voZrVJ20Tz6uU2fxtpR/sn7yAUDSroTljfcARvWlm1mFyeGv7b+LGJ5c0hjCy1q/b8bYPJGmwZjZ8RmY4hpVoLdUYuWaNTXzuU6dxdtS/imVWv/kk2a22xXA5YQTewTwM+CnaSqXNFzSg8Ay4FYzGzD7ozzkRGqrc0BOugr6uIKMNCqVeltndZvJUbdbH1fQJp2KqhEMnbZU5DBV7XjySeN8xpjZHAAze8bMziCIUhMz6zWzvQjz5WdIGvBmcnnIiXoM7zTtEKMJMtNo2LDhLTW8nZRKJdb05Mr5tE2nomoEQ6ctFTlMlfV2oNsNWB8D7T0j6VPAYqCuuZxmtkLS7YQ3lGuGqigCpTaI0QSuUQKl3hJrX6nd7ZYhrlMC3pbyTzu63dI4ny8A3cDnCX2h44GP1SokaRIhRMUqYD7h7fkTG7Y0gZn7HpKYnjQLrtIMuEbjV7VDjCbITKNKMxIrUWnGlCXMnpFauwxPqWSsXb2upXU2STM69QK3Ed45GUeVYJdF0giGZluqFMeykm6VZrUlzYKrFGexmfhw1okJB2X9lquAj9RR93bAlcBkYAyw1MxuqtvCnJKnuzXXKJlSb4k1q/Lz5NOkTjcTLoq7AucPNp3ygrelZNqhUbWXTG8grmmRhJl9sFrFZvawpKMIgpwDfLFRI/NIHvqpXaPqlEol1q7u/JhPszoRXnx8kqiRmZ3dQvM6jrel/JN1t9tFLaj/QuArVOkzLQ85USTaMQDXAK5RFay3xNpVnXc+NK/ToNUIhk5bKmooMci4283MbmumYkl9a2/Ml3RQleO8FnJCUq46f6uRh64C16g6eel2a0anwa4RDJ22VNRQYtChl0ybYH/gaEnvJbystaWkq83sw606QKXBuaTJBUmDp9D4AGoeugpaQNs1qhQyJ+m8H3PMl5LrqLAI2oXnfKHqsfPS7dYkdWvU3T2et047cEB6vQPOSRpVCvOSFHIJ0k3oGYptqd5JIZVImohQKezYzH0HTnCAtBp15iXThjCz0wgLLK0CVgMbWnlR6zR5uFtrlqGg0dpXiu18zOw0M+vzvmsIM6re1EGTWo63pfzTjm631M5H0hYNHuPdwCcI4SYGDXm8W3ONNqdUKrF2TU+nzRhAEzp9AbizaC9k18LbUv7pSIQDSTMkPQL8MX7eU9K/1nMQM/udmR3ZoI25JE8NxjVKplQqsWbNqk6b8Rot0OnuwaYReFsqAu0I2ZTmyef7wJHAiwBm9hDBu6fBgFskzVeFlQmLGtstZ/GzXKMESqVe1q1b3WkzymmbTsWOGzY02lJR2xGAWWcmHAwzs+f6DT6m/bUcYGaLJW0L3CrpCQtrsb9GUWfp5KzBuEYJlEq9rFkzYGHWTtI2nco1Gjt2QmE0gqHTlorajqBzs90WSpoBmKThwOeAtNM0eiTNBqYR1ro4FrijepH2sN++Ryem17PYWTl56iogxxrVM6On0qJWlWYqppntlqduNzLSqadnZVOhVKpRKcxLM7NJvS3VZt93HJWYnqRzpbBjp557TmJ6mgXpOtXt9mnC27o7AEuBfWNaVSR1Az8A/ht4G/A08LtGDc0bZunEkDRT0pOSnpb0tTaZ4xolYJau2y0jjcB1SiRNW3KNOkvabrd6dEoT220ZcFw6EzdjN+AYwl3AF4BrzOwXDdSTS9LcCcQ7px8AhxKW2L1X0o1m9lgrbXGNkknT7ZaVRuA6VaJWW3KNOk+ap9N6dUqzkumPSIh5ZGa1QnkIeAh4DNgT2FlSt5nlb+5rA6TsKpgBPG1mfwKQdB3wPsI5aRmuUTKlUom1a2s++WSiUazbdUogRVtyjTpMyiefunRK0+32G0Io99uAu4FtgfUpyo0A9gF+aGZ7Az3AgMewos4AKZV65wCj+myPW/8f6PZA+evGi2Jaq3GNEiiVeuesXv3yfKrrlJVG0EadiqoRpGpLrlGHacf1Lk2322ZLyEr6MeleoFoELCoLUT6bhAtbUWeAmNnMTtvQh2uUTJ40gvbqVFSNIF86uUbJtEOjRmK77UxYs6IW44GJkh4n3Dm8mXAnUZGttprMIYcMjEgxb27yzJ16ZlJVmv1z3H7J6aeefsGAtGsuTZ6NVYXFhEWl+pgS09pN2zSqFDcsaWEsaHyxvnIqzZhKijN24Zlfrbf6TmkEbdKpq2sLJm+744D0VmhRialTdk9Mb3Q2aT8GnUaV2lEl6pm9WO+CdEnXOqg9mzSBunRKE+HgZUkvxW0FcCtwWq1yZvYkcADh8XMYoR/1lFrlBhn3Am+QtLOkkYSBzBtbfRDXqCky0QhcpyZwjYpBXTpVffJRuOXck03eq2SVJvQnYGYPAtMlHQacaWaPpC07GDCzVyV9FpgDDAcuM7NHW3kM16g5stAIXKdmcI2KQb06VXU+ZmaSbjazaU3adRxwbZN1FBIzu5mwBHK76neNmqTdGsVjuE5N4BoVg3p0SjPb7UFJezdqTHz8OhpIXAikfAbI+vWdX/iroGSmUdHihuWMtulUrlHOwtUUjUw08nZU5clH0ggzexXYm/Cy0DOE/kwRbhL2SXmMI4D7zWxp0s7yGSBbb/26Qs0A6TSd0KhoccPyQBY6WYFXycwDWWvk7QhUJSbT/Wa2j6Rdk/ab2TOpDhBeNJpjZpenyLsceA6YCLyQkKUV6c3UsaOZTUrI0xE6rBFkd97rqTtXGkH2OjWhUaX0dmiXK50GgUaV0vOrkZklbsADlfal3YBuQmjy8XWWu69d6a2qOw9bJzXq1Hmvp+68bEVvS+3ULi9b0TVqt3bt2KpNOJgk6YtVnNa/VCnbl6eHEN3VaQ+uUTFwnfKPa5Qx1ZzPcGAsoc/TySeuUTFwnfKPa5Qx1ZzPEjM7OzNLNufiNqa3qu480EmNoDPnvZ6680LR21I7tcsLRdeoUnpuNao24eABCwHynJziGhUD1yn/uEbZU835bG1mL2Vsj1MHrlExcJ3yj2uUPRWdj+M4juO0izQRDjJD0mWSlkla0C99qqTbJT0m6VFJp8T0UZL+IOmhmH5WWZnhkh6QdFNZ2rOSHpH0oMrW1JA0QdJsSU9IelzSfpJ2j/n6tlcknZrFecg7STq5RvmiVRrF/al0co3qo57rXas0iun50CmrOd0p57sfSFiQaUG/9O2AfeL/44CngD0IM1PGxvQu4B5g3/j5i8A1wE1l9TwLTEw47pXAJ+L/I4EJ/fYPB54nvHTV8fPU6S1JJ9coX1urNKpHJ9eoeY1q6NS0RnnSKVdPPmZ2BzCg39XMlpjZ/fH/VcDjwPYW6FsnuStuJmkK8JfAJbWOKWk84Udwaax/g5mt6JftEOAZM3uuf/mhSJJOrlG+aIVGAGl1co3qp4HrXVMaxby50SlXzicNknYixF+6J34eLulBYBlwq4WVBC8EvgL0X3jcgFskzdemJWB3BpYDl8fH1kskdfcrN2Sj1DaCa5R/UmoE6XVyjdpAuU4t0AhypFOhnI+kscAvgFPN7BUAM+s1s70Iq+bNkPQZYJmZzU+o4gALAQKPAP5e0oHUWHtdNSI+O5vjGuWflBpNk3QkKXUC3o5r1FL669SsRnlrS4VxPpK6CEL8xMyu778/PjreDnwIOFrSs8B1wMGSro55Fse/y4AbgBkkr71eHsG2asRnZxOuUf6pQ6OZwP6k12kKrlHLqKZTExrlqi0VwvlIEqGP8nEri7EkaZKkCfH/0cChwAVmNsXMdiI8Pv7WzD4sqVvSuJi3GziMMND3PLBQUt8i9IcAj5Ud/ni8q6AmrlH+qVOjJ8zstDp0motr1BKSdGqRRvlqS/XMTmj3Fr/0EmAjwUN/PKYfQOi/fBh4MG7vBf4f8EBMXwB8o199BxFnfwC7AA/F7VHg9LJ8ewH3xXp+CWwV0xuO+DyYtySdXKN8ba3UKK1OrlHzGsX0JJ0+0wqN8qSTv2TqOI7jZE4hut0cx3GcwYU7H8dxHCdz3Pk4juM4mePOx3Ecx8kcdz6O4zhO5nTc+UjqjVFUF0j6uaQxTdR1UF9UV0lHS/palbwT4pv29R7jm5JmNWpjEXGNioHrlH9co0103PkAa81sLzObBmwAPlW+U4G67TSzG83svCpZJhDmzju1cY2KgeuUf1yjSB6cTzl3ArtJ2knSk5KuIrxQNVXSYZLmSro/3jGMBZA0U2FdivuBD/ZVJOkkSRfF/ydLukFhLYyHJL0TOA/YNd6FfCfm+7KkeyU9rM3XnTld0lOS7gJ2Z2jjGhUD1yn/DG2NcvCW7+r4dwTwn8CngZ0IEVr71n2ZCNwBdMfPXwW+AYwCFgJvIKxJ8jM2veF7EnBR/P+nhOB8ENaqGB+PUb7WyWHAxbGeYcBNhNDjbwMeAcYAWwJPA7M6fd5cI9fIdSre5hpt2kbQeUYrhAmHcCdwKfB64DkzmxfT9yUspnS3JAgLIM0F3gT82cz+CKAQUO9kBnIwcAKE6L3ASklb9ctzWNweiJ/HEkQeB9xgZmviMW5s6tsWE9eoGLhO+cc1iuTB+ay1ECb8NeIJ7ylPIqxfcXy/fJuVaxIB3zaz/+h3DF/y1zUqCq5T/nGNInkb86nEPGB/SbtBiNIq6Y3AE8BOknaN+Y6vUP42wuNt36JZ44FVBC/fxxzgY2V9q9tL2pbw+Pt+SaMVosQe1eLvNlhwjYqB65R/hoRGhXA+Zrac0Kd5raSHiY+gZraO8Nj56zgAt6xCFacA75b0CDAf2MPMXiQ81i6Q9B0zu4WwBvrcmG82MM7CcrY/JUSH/S/g3rZ90QLjGhUD1yn/DBWNPKq14ziOkzmFePJxHMdxBhfufBzHcZzMcefjOI7jZI47H8dxHCdz3Pk4juM4mePOx3Ecx8kcdz6O4zhO5gxK5yPpzZJ+K2mlpKclfaDTNg11JH1W0n2S1ku6ot++QxQi9a6RdLukHTtkpuM4GTHonI+kvmixNwFbE94IvjqGp3A6x/8B3wIuK0+UNBG4HvgHgl73Ed6wdhxnEDPoIhxImkaIjTTO4peTdAtwj5n9Q0eNc5D0LWCKmZ0UP58MnGRm74yfu4EXgL3N7ImOGeo4TlsZdE8+FRAwrdNGOIm8hRBHCgAz6wGeiemO4wxSBqPzeZIQcO/LkrokHQa8i7A4kpM/xgIr+6WtZPMIvI7jDDIGnfMxs43A+4G/BJ4HvkRY8W9RJ+1yKrKasGJiOVsSQsA7jjNIGXTOB8DMHjazd5nZNmZ2OLAL8IdO2+Uk8iiwZ9+HOOaza0x3HGeQMiidj6T/J2mUpDGSZgHbAVd02KwhjaQRkkYR1pQfHvUZAdwATJP0obj/G8DDPtnAcQY3g9L5AB8BlhDGfg4BDjWz9Z01achzBrAW+Brw4fj/GXHhrA8B5wAvA+8AjuuUkY7jZMOgm2rtOI7j5J/B+uTjOI7j5JgRlXZI6j8DaTPM7JXWm+PUg2vkOE5RqdjtJmkhYIQXNPvo+2xmtkP7zXOq4Ro5jlNUfMzHcRzHyZyK3W7lSDoO2MXMzpU0BZhsZvNrlLkMOBJYZmapQtt0dY20LbYYGIhg5MhRiflXr16RmL5xY/MT27q7xw9IW79+DRs3blBC9o7TiEaxXF06jR4z1rYcv9WA9I3rNybmf/nlpbWqbJiuri0GpL366kZKpd5cauQ4ziZqPvlIugjoAg40szdL2hqYY2Zvr1HuQMLb61eldT5jx06wt047cED6lKnJAannzf1VYvqixU+lOVxV9n3HUQPSHllwB6tXr8jdha1RjWLZunSavN1U+5uPf2lA+qInkwNIzJ793VpVNsyU7Qf+LpYue44NG9blTiPHcTYnzWy3d5rZJ4F1AGb2EjCyViEzuwN4qTnznJQ0pFHM6zo5jpM5aZzPRknDCAPZSNoGKLXKAEknx0XG7tu4cUOrqh1qZKbR2jU9rarWcZwhTBrn8wPgF8AkSWcBdwH/1CoDzOxiM5tuZtO7ulLdrDsDyUyj0WO6W1Wt4zhDmJoTDszsKknzgffEpGPNbEF7zRocNDLpohFco+bISifHcTaRarYbIRjkRkK3TqqoCJKmAtcCu0l6FLjYzL5XrcxWk7bhmM+cMCD9wq+fnpi/FRML2swVwEXAVRkcq1GNrgKmAFMlnVJLo0pM2X1KI8WaooX6X0F2OjmOQ4qLlKTTCU7k9YSL1DWSTktR9/eB3QgXxQnAaZL2aMLWwpHVYH4TGr0KrCcs6DYcOF/SGW0zNKf4pAvHyZ40Tz4nAHub2RoASecADwDfrlbIzD5Q/lnSfwLbA481ZqpThUY1WgLM7PscNbqnjXY6juMA6bpnlrC5kxoR01IjaSdgbxIubOUzqXpWFScU2eGHH26SFvTZHreTO2ROZhoVabbb4Ycfbm+b/rY86eQ4TqRaYNELCOMHLwGPSpoTPx8G3Jv2AJLGEmZinZoU6NLMLgYuBpi6866FifWzdPlygHVmNr1TNnRCo8nbTS2URrf+z/+w7ZZbdlQnx3EGUq3brW+21KPAr8vS56WtXFIX4aL2EzO7vn7z8ktvb2+nTQDXqCq9vb2sXLOm02Y4jpNARedjZpc2U3FcEnkR4U58iqTxZnZmtTKLnv0Ts048dkB6UhgVgPOv/HlielIdlUgKowMw757k0D199KYIyCrpWuAgYKKkRcCZzZ7Xcjqh0csvLmf2FT8ckF5p5lkrNKpEpRBIffSasWrd2pr1tFsnx3EGUnPCgaRdCUsc7wG8FuHTzJI9wiamA9sAjxDetp8laYWZXdC4ufmht1Q7gICZHZ+BKa5RBXpLJVauqe18stLJcZxNpJntdgXwLeB84Ajgo8QwLtUws7uI68xIGkN46/73jRqaN9I4nwy5AtdoAL2lEq+sre18HMfJnjSz3caY2RwAM3vGzM4gXOBqImm4pAeBZcCtZlZ1JlU9hneaUr6cT2YalUq5GOtKRalUYlWPj/k4Th5J43zWx6CVz0j6lKSjgHFpKjezXjPbi/Di4wxJA0KXlMcNq8vyDlPqzZXzyUyjYcOGt9byNlLqLbF29bpOm+E4TgJpnM8XgG7g88D+wN8BH0t7AEnDgdsJb9/PrJG9MOTsycc1SqBUKrFmlT/5OE4eadsy2pImEWKNfQx4ByHo5YlmdlOVMi0xJuk7Sa1ZX8zMBDBlp11s8XN/nl+0J7ZyhoJGn/uHc/naJ44vtE6OMxip9pLpDVQZtDazD9aoezvgGmBH4AXghWoXtaJRKnX+XUvXqDqlknm3m+PklGqz3S5qpmIze1jSE8CJhPGHWc3UlzdyMubjGlWh1Ovdbo6TV6q9ZHpbMxVL6lsfZb6kg6rkOxkoXKwty8GYj2tUHSuVWLfap1o7Th5Jte5Lg+wPHC3pWeA64GBJV/fP5LPdOsqg12jNK+58HCePpF1Mrm7M7DRJxwOrCOvGjDKzD7freOVMnbL7gLRKYV5m/1vy+mG1wusMBufTSY2SJhdcN3duYt4LTz03MT2NRt7t5jj5JPWTj6QtGjzGu4FPEN6eHzTkbKo14Br1p+Tdbo6TW9KsZDpD0iPAH+PnPSX9az0HMbPfmdmRDdqYS/LkfFyjZEqlEmvWruq0GY7jJJDmyef7wJHAiwBm9hDhTjkNBtwiaX6lBbyKG14nV2FmXKMESqVe1rrzcZxckmbMZ5iZPdevjz7tlfcAM1ssaVvgVklPmNkd5RnKFypr1QuMWZAz5+MaJVAq9bJ2jTsfx8kjaZ58FkqaAVgMQnkqkLx4Sz/MbHH8uwy4AZjRsKU5I0/dbrhGiZRKJdauW91pMxzHSSDNk8+nCd06OwBLgd/EtKpI6ga2Ai4E3gpMBb7esKUJVFpkLokLv356Yvqp556TmF5zJlXKJx9JM4HvAcOBS8zsvFQF6yO3GtWzWN9x++2XmLfSLLjj9qut0Zo1A1YFH0BGGjmOU0ZN5xPviI9roO7JwH1AD/AKcB5h3ZlBgaVwPjFg5w+AQwkrht4r6UYze6yltrhGiViKbresNHIcZ3PSrGT6IxLih5lZrTfeXwRWArtYu6KXdpCSpep2mwE8bWZ/ApB0HfA+oKUXNtcomZKl6nbLRCPHcTYnTbfbb8r+HwV8AFiYotzOwHLgckl7AvOBU8yspzxTUUO3pOx2257Nz9UiQvToVuMaJRC63WpOOMhKI8dxykjT7fbT8s+Sfky6lxFHAPsAnzOzeyR9D/ga8A/96i/kTKq4cuiUftOPL47fJ2tbXKMEzGzOhg1rJwKj8qCT4zibaCS8zs6EsYJaLAIWlS3LPJtwYRsUmFmaRdcWEwbx+5gS09qNa0TuNXKcIU2aMZ+X2TSeMAx4iXQXqPHAREmPA+uBNwN3VyvQ1bUFk7fdcUD6osWpZg03lH/WiccmpifNsPr6Rz9alx3AvcAbJO1MuKAdB/xNvZXUIs8aLVr0ZAozqlPPLLi8auQ4zuZUdT4Kby3uyaY7wVLagWkze1LSAcAlwEjCxfGUJmwtHGb2qqTPAnMI03gvM7NHW3kM16g5stDIcZyBVHU+ZmaSbjazaY1UbmYPAtMlHQacaWaPNFJPkTGzm4Gb21i/a9Qk7dbIcZyBpIlw8KCkvZs8znHAtUk7yuOG5SxkTZFwjRzHKRQVn3wkjTCzV4G9CS/ePUN4GVGEG+590hxA0kjgaOC0pP3lM6lGjhxVmJlUecA1chynqFTrdvsDYRru0U0e4wjgfjNb2mQ9zkBcI8dxCokqjU1LesDMmu3K6XtjfI6ZXZ4i73LgOWAi8EJCllakN1PHjmY2KSFPR+iwRpDdea+n7lxp5DhOMtWczyLgXyoVNLOK+8rq6Ab+lxC+ZWVqo6T7zGx6O9JbVXce6KRGsWzm572euh3HyS/Vut2GA2MJ4wcNEcO0bNNoeacmrpHjOIWkmvNZYmZnZ2aJ0wiukeM4haTaVOuG76ZbQKW4W61Ib1XdeaCTGkFnzns9dTuOk1OqjflsbWYvZWyPUweukeM4RaWi83Ecx3GcdpEmwoHjOI7jtJRcOR9Jl0laJmlBv/Spkm6X9JikRyWdEtNHSfqDpIdi+lllZYZLekDSTWVpz0p6RNKD5eu7SJogabakJyQ9Lmk/SbvHfH3bK5JOzeI85J0knVwjx3HqIVfdbpIOBFYDV5UHypS0HbCdmd0vaRxhxc33A48D3Wa2WlIXYQG1U8xsnqQvAtOBLc3syFjPs8B0M3uh33GvBO40s0tiqJkxZraibP9wQtTod5jZcwxxknRyjRzHqYdcPfmY2R2EtWj6py8xs/vj/6sIF7TtLbA6ZuuKm0maAvwlYamAqkgaDxwIXBrr31B+UYscAjzjF7VAkk6ukeM49ZAr55MGSTsRAo0S1XYAAAP5SURBVGneEz8Pl/QgsAy4Na7KeSHwFaDUr7gBt0iaL+nkmLYzsBy4PHYBXRLf+i+nYsRnZyCukeM4tSiU85E0FvgFcKqZvQJgZr1mthdh+eMZkj4DLDOz+QlVHBAjPR8B/H3sPhpBCM75wxgnrYeyVUC1KeLzz9v41QYNrpHjOGkojPOJ4wW/AH5iZtf33x+7YW4HPgQcHccOrgMOlnR1zLM4/l0G3ADMABYBi+LdOMBswoWuD4/4nBLXyHGctBTC+UgSob//8fJgmZImSZoQ/x8NHApcYGZTzGwnQlfMb83sw5K640B4XzDNw4AFZvY8sFDS7rHaQ4DHyg5/PN6dUxPXyHGceqi6jHbWSLoWOAiYqBCx+UwzuxTYH/gI8EgcOwD4OuGO+Mo402kY8DMzu2lgzQBMBm4I10hGANeY2X/HfZ8DfhK7b/4EfDTa0024WH6ypV+04CTpBDyJa+Q4TkpyNdXacRzHGRoUotvNcRzHGVy483Ecx3Eyx52P4ziOkznufBzHcZzMcefjOI7jZE7HnY+k3hiReIGkn0sa00RdB/VFSJZ0tKSvVck7Ib5pX+8xvilpVqM2FhHXyHGcVtNx5wOsNbO9YnTkDcCnyncqULedZnajmZ1XJcsEoO4L2xDFNXIcp6XkwfmUcyewm6SdJD0p6SpgATBV0mGS5kq6P959jwWQNFNhjZf7gQ/2VSTpJEkXxf8nS7pBYU2ZhyS9EzgP2DXe0X8n5vuypHslPazN1505XdJTku4Cdmdo4xo5jtM0uYlwIGkEIUZX3xvtbwBOjOu+TATOAN5jZj2Svgp8UdI/Az8CDgaeBn5aofrvA/9jZh+Ib9qPJQSmnBYDXiLpsHjMGYCAGxWCWvYQQsDsRThf9xPWqhlyuEaO47SKPDif0WXhWO4kxAd7PfCcmc2L6fsCewB3x9ArI4G5wJuAP5vZHwEUglOezEAOBk6AEGEZWClpq355DovbA/HzWMKFbhxwg5mtice4salvW0xcI8dxWkoenM/avjvbPuLFq6c8ibAOzPH98m1WrkkEfNvM/qPfMXxZZtfIcZwWk7cxn0rMA/aXtBuEYJKS3gg8AewkadeY7/gK5W8DPh3LDldYGXMV4Y65jznAx8rGKbaXtC1wB/B+SaMVIi4f1eLvNlhwjRzHSU0hnI+ZLQdOAq6V9DCxO8fM1hG6cH4dB7OXVajiFODdkh4hjAXsYWYvErqIFkj6jpndAlwDzI35ZgPj4tLQPwUeAv4LuLdtX7TAuEaO49SDR7V2HMdxMqcQTz6O4zjO4MKdj+M4jpM57nwcx3GczHHn4ziO42SOOx/HcRwnc9z5OI7jOJnjzsdxHMfJnP8PRVZRypz81nwAAAAASUVORK5CYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The average accuracy among all subjects is 0.671429 +/- 0.065465\n" - ] - } - ], - "source": [ - "plot_confusion_matrix(cm, title=\"Confusion matrices for different test subjects\")\n", - "print(\"The average accuracy among all subjects is {0:f} +/- {1:f}\".format(np.mean(accuracy), np.std(accuracy)))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.1" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} From 385462b106539528c5a284f4d314f50c6f924a41 Mon Sep 17 00:00:00 2001 From: hrichard Date: Tue, 8 Oct 2019 13:52:17 +0200 Subject: [PATCH 16/24] better checks and handling of temporary files (clean method) --- brainiak/funcalign/fastsrm.py | 390 +++++++++++++++++----------------- 1 file changed, 198 insertions(+), 192 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 6d77a391e..2a741804d 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -1,11 +1,7 @@ """Fast Shared Response Model (FastSRM) """ -# Author: Hugo Richard (INRIA - Parietal) -# under the supervision of Jonathan Pillow (Princeton Neuroscience Institute) -# and Bertrand Thirion (Inria - Parietal) -# building upon code and work of Po-Hsuan Chen (Princeton Neuroscience -# Institute) and Javier Turek (Intel Labs) +# Author: Hugo Richard import logging @@ -25,7 +21,123 @@ logger = logging.getLogger(__name__) -def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, +def check_imgs(imgs): + """ + Check input images + + Parameters + ---------- + + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_voxels] + n_timeframes and n_voxels are assumed to be the same across + subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + if type(imgs) != np.ndarray: + raise ValueError("imgs should be of type " + "np.ndarray but is of type %s" % type(imgs)) + + if len(imgs.shape) != 2: + raise ValueError("imgs should be an array of shape " + "[n_subjects, n_sessions] " + "but its shape is of size %i" % len(imgs.shape)) + + n_subjects, n_sessions = imgs.shape + + if n_subjects <= 1: + raise ValueError("The number of subjects should be greater than 1") + + +def check_n_components(n_supervoxels, n_components, n_timeframes): + """ + Check that n_timeframes >= n_components and n_supervoxels >= n_components + + Parameters + ---------- + + n_supervoxels: int + + n_components: int + + n_timeframes: int + + """ + + if n_supervoxels < n_components: + raise ValueError("The number of regions in the atlas " + "%i is smaller than " + "the number of components %i of fastSRM" % + (n_supervoxels, n_components)) + + if n_timeframes < n_components: + raise ValueError("Number of timeframes %i is shorter than " + "number of components %i" % + (n_timeframes, n_components)) + + +def check_reduced_data(reduced_data_list, + n_components=None, + return_low_ram=False): + + if type(reduced_data_list) != np.ndarray: + raise ValueError("reduced data must have type np.ndarray but" + "has type %s" % type(reduced_data_list)) + + low_ram = is_low_ram(reduced_data_list[0, 0]) + n_subjects, n_sessions = reduced_data_list.shape[:2] + + # Let us check that reduced data have same number of voxels and timeframes + n_timeframes = None + n_supervoxels = None + for n in range(n_subjects): + for m in range(n_sessions): + if low_ram: + data_nm = np.load(reduced_data_list[n, m]) + else: + data_nm = reduced_data_list[n, m] + + if n_timeframes is None and n_supervoxels is None: + n_timeframes, n_supervoxels = data_nm.shape + else: + if n_timeframes != data_nm.shape[0]: + raise ValueError("Subject %i Session %i does not have the " + "same number of timeframes " + "as Subject %i Session %i" % (n, m, 0, 0)) + + if n_supervoxels != data_nm.shape[1]: + raise ValueError( + "Reduced data from Subject %i Session %i" + " does not have the same number of supervoxels as " + "Subject %i Session %i." % (n, m, 0, 0)) + + if n_components is not None: + check_n_components(n_supervoxels, n_components, n_timeframes) + + if return_low_ram: + return low_ram + + +def create_temp_dir(temp_dir): + """ + This check whether temp_dir exists and creates dir otherwise + """ + if not os.path.exists(temp_dir): + os.makedirs(temp_dir) + else: + raise ValueError("Path %s already exists. " + "When a model is used, filesystem should be cleaned " + "by using the .clean() method") + + +def reduce_data_single(img, + atlas=None, + inv_atlas=None, + low_ram=False, temp_dir=None): """Reduce data using given atlas @@ -81,11 +193,11 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, if data.shape[1] != n_voxels: raise ValueError("%s have %i voxels and" " the atlas has %i voxels." - "This is incompatible." % (img, data.shape[1], - n_voxels)) + "This is incompatible." % + (img, data.shape[1], n_voxels)) - reduced_data = np.array([np.mean(data[:, atlas == c], axis=1) - for c in atlas_values]).T + reduced_data = np.array( + [np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T else: # this means that it is a probabilistic atlas assert len(inv_atlas.shape) == 2 @@ -96,8 +208,8 @@ def reduce_data_single(img, atlas=None, inv_atlas=None, low_ram=False, if data.shape[1] != n_voxels: raise ValueError("%s have %i voxels and the atlas" " has %i voxels." - "This is incompatible." % (img, data.shape[1], - n_voxels)) + "This is incompatible." % + (img, data.shape[1], n_voxels)) reduced_data = data.dot(inv_atlas) @@ -163,8 +275,8 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): Each voxel's timecourse is assumed to have mean 0 and variance 1 """ if type(atlas) != np.ndarray: - raise ValueError("atlas should be of type np.ndarray but has type %s" - % (type(atlas))) + raise ValueError("atlas should be of type np.ndarray but has type %s" % + (type(atlas))) if len(atlas.shape) == 2: A = None @@ -179,14 +291,9 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): n_subjects, n_sessions = imgs.shape - reduced_data_list = Parallel(n_jobs=n_jobs)( - delayed(reduce_data_single)( - img, - atlas=A, - inv_atlas=A_inv, - low_ram=low_ram, - temp_dir=temp_dir - ) for img in imgs.flatten()) + reduced_data_list = Parallel(n_jobs=n_jobs)(delayed(reduce_data_single)( + img, atlas=A, inv_atlas=A_inv, low_ram=low_ram, temp_dir=temp_dir) + for img in imgs.flatten()) if low_ram: reduced_data_list = np.reshape(reduced_data_list, @@ -197,42 +304,13 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): (n_subjects, n_sessions)) else: n_timeframes, n_supervoxels = np.array(reduced_data_list).shape[1:] - reduced_data_list = np.reshape(reduced_data_list, - (n_subjects, - n_sessions, - n_timeframes, - n_supervoxels)) + reduced_data_list = np.reshape( + reduced_data_list, + (n_subjects, n_sessions, n_timeframes, n_supervoxels)) return reduced_data_list -def check_shapes(n_supervoxels, n_components, n_timeframes): - """ - Check assumptions about input parameters - - Parameters - ---------- - - n_supervoxels: int - - n_components: int - - n_timeframes: int - - """ - - if n_supervoxels < n_components: - raise ValueError("The number of regions in the atlas " - "%i is smaller than " - "the number of components %i of fastSRM" - % (n_supervoxels, n_components)) - - if n_timeframes < n_components: - raise ValueError("Number of timeframes %i is shorter than " - "number of components %i" % (n_timeframes, - n_components)) - - def is_low_ram(reduced_data): """ Depending on type of reduced_data infer if we are in low-ram mode or not @@ -252,14 +330,13 @@ def is_low_ram(reduced_data): """ if type(reduced_data) == np.ndarray: low_ram = False - elif (type(reduced_data) == str or - type(reduced_data) == np.str_ or - type(reduced_data) == np.str): + elif (type(reduced_data) == str or type(reduced_data) == np.str_ + or type(reduced_data) == np.str): low_ram = True else: raise ValueError("Reduced data are stored using " - "type %s which is neither np.ndarray or str" - % type(reduced_data)) + "type %s which is neither np.ndarray or str" % + type(reduced_data)) return low_ram @@ -307,7 +384,6 @@ def _reduced_space_compute_shared_response(reduced_data_list, # This is just to check that all subjects have same number of # timeframes in a given session - list_n_timeframes = [None] * n_sessions for n in range(n_subjects): for m in range(n_sessions): if low_ram: @@ -317,15 +393,6 @@ def _reduced_space_compute_shared_response(reduced_data_list, n_timeframes, n_supervoxels = data_nm.shape - check_shapes(n_supervoxels, n_components, n_timeframes) - - if list_n_timeframes[m] is None: - list_n_timeframes[m] = n_timeframes - elif list_n_timeframes[m] != n_timeframes: - raise ValueError("Subject %i Session %i does not have the " - "same number of timeframes " - "as Subject %i Session %i" % (n, m, 0, m)) - if reduced_basis_list is None: reduced_basis_list = [] for subject in range(n_subjects): @@ -469,17 +536,13 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): shared response, element i is the shared response during session i """ - if type(reduced_data_list) != np.ndarray: - raise ValueError("reduced data must have type np.ndarray but" - "has type %s" % type(reduced_data_list)) + low_ram = check_reduced_data(reduced_data_list, + n_components=n_components, + return_low_ram=True) - low_ram = is_low_ram(reduced_data_list[0, 0]) n_subjects, n_sessions = reduced_data_list.shape[:2] shared_response = _reduced_space_compute_shared_response( - reduced_data_list, - None, - n_components - ) + reduced_data_list, None, n_components) reduced_basis = [None] * n_subjects for _ in range(n_iter): @@ -497,10 +560,7 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): reduced_basis[n] = _compute_subject_basis(cov) shared_response = _reduced_space_compute_shared_response( - reduced_data_list, - reduced_basis, - n_components - ) + reduced_data_list, reduced_basis, n_components) return shared_response @@ -544,8 +604,8 @@ def _compute_basis_subject_online(sessions, shared_response_list): return _compute_subject_basis(basis_i) -def _compute_shared_response_online_single(subjects, basis_list, - temp_dir, subjects_indexes): +def _compute_shared_response_online_single(subjects, basis_list, temp_dir, + subjects_indexes): """Computes shared response during one session with basis fixed Parameters @@ -637,49 +697,12 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, """ shared_response_list = Parallel(n_jobs=n_jobs)( delayed(_compute_shared_response_online_single)( - subjects, - basis_list, - temp_dir, - subjects_indexes - ) for subjects in imgs.T) + subjects, basis_list, temp_dir, subjects_indexes) + for subjects in imgs.T) return shared_response_list -def check_imgs(imgs): - """ - Check input images - - Parameters - ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected - shape is [n_timeframes, n_voxels] - n_timeframes and n_voxels are assumed to be the same across - subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - """ - if type(imgs) != np.ndarray: - raise ValueError("imgs should be of type " - "np.ndarray but is of type %s" - % type(imgs)) - - if len(imgs.shape) != 2: - raise ValueError("imgs should be an array of shape " - "[n_subjects, n_sessions] " - "but its shape is of size %i" - % len(imgs.shape)) - - n_subjects, n_sessions = imgs.shape - - if n_subjects <= 1: - raise ValueError("The number of subjects should be greater than 1") - - class FastSRM(BaseEstimator, TransformerMixin): """SRM decomposition using a very low amount of memory and computational power @@ -734,16 +757,24 @@ class FastSRM(BaseEstimator, TransformerMixin): basis of all subjects, element i is the basis of subject i or path to basis of all subjects, element i is the path to the basis of subject i + + Notes + ----- + **References:** + H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019: Fast + shared response model for fMRI data (https://arxiv.org/pdf/1909.12537.pdf) """ - def __init__(self, - atlas, - n_components=20, - n_iter=100, - temp_dir=None, - low_ram=False, - random_state=None, - n_jobs=1, - verbose="warn",): + def __init__( + self, + atlas, + n_components=20, + n_iter=100, + temp_dir=None, + low_ram=False, + random_state=None, + n_jobs=1, + verbose="warn", + ): self.random_state = random_state self.n_jobs = n_jobs @@ -766,17 +797,18 @@ def __init__(self, self.low_ram = False if temp_dir is not None: - if not os.path.exists(os.path.join(temp_dir, "fastsrm")): - os.mkdir(os.path.join(temp_dir, "fastsrm")) self.temp_dir = os.path.join(temp_dir, "fastsrm") - - # Remove files in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) - for path in paths: - os.remove(path) - self.low_ram = low_ram + def clean(self): + if self.temp_dir is not None: + if os.path.exists(self.temp_dir): + for root, dirs, files in os.walk(self.temp_dir, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + def fit(self, imgs): """Computes basis across subjects from input imgs @@ -800,27 +832,21 @@ def fit(self, imgs): at the object level. """ - if self.temp_dir is not None: - # Remove former basis in temp folder - paths = glob.glob(os.path.join(self.temp_dir, "*.npy")) - for path in paths: - os.remove(path) - + create_temp_dir(self.temp_dir) check_imgs(imgs) if self.verbose is True: n_subjects, n_sessions = imgs.shape - logger.info("Fitting using %i subjects and %i sessions per subject" - % (n_subjects, n_sessions)) + logger.info( + "Fitting using %i subjects and %i sessions per subject" % + (n_subjects, n_sessions)) logger.info("[FastSRM.fit] Reducing data") - reduced_data = reduce_data( - imgs, - atlas=self.atlas, - n_jobs=self.n_jobs, - low_ram=self.low_ram, - temp_dir=self.temp_dir - ) + reduced_data = reduce_data(imgs, + atlas=self.atlas, + n_jobs=self.n_jobs, + low_ram=self.low_ram, + temp_dir=self.temp_dir) if self.verbose is True: logger.info("[FastSRM.fit] Finds shared " @@ -840,9 +866,7 @@ def fit(self, imgs): basis = [] for i, sessions in enumerate(imgs): basis_i = _compute_basis_subject_online( - sessions, - shared_response_list - ) + sessions, shared_response_list) if self.temp_dir is None: basis.append(basis_i) else: @@ -852,36 +876,20 @@ def fit(self, imgs): del basis_i else: if self.temp_dir is None: - basis = Parallel( - n_jobs=self.n_jobs - )(delayed( - _compute_basis_subject_online - )( - sessions, - shared_response_list - ) for sessions in imgs) + basis = Parallel(n_jobs=self.n_jobs)( + delayed(_compute_basis_subject_online)( + sessions, shared_response_list) for sessions in imgs) else: Parallel(n_jobs=self.n_jobs)( delayed(_compute_and_save_corr_mat)( - subject, - shared_response_list[m], - self.temp_dir - ) + subject, shared_response_list[m], self.temp_dir) for m, subjects in enumerate(imgs.T) - for subject in subjects - ) - - basis = Parallel( - n_jobs=self.n_jobs - )(delayed( - _compute_and_save_subject_basis - )( - i, - sessions, - self.temp_dir - ) - for i, sessions in enumerate(imgs) - ) + for subject in subjects) + + basis = Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_subject_basis)(i, sessions, + self.temp_dir) + for i, sessions in enumerate(imgs)) self.basis_list = basis return self @@ -949,16 +957,14 @@ def transform(self, imgs, subjects_indexes=None): subjects_indexes = np.array(subjects_indexes) shared_response = _compute_shared_response_online( - imgs, - self.basis_list, - self.temp_dir, - self.n_jobs, - subjects_indexes - ) + imgs, self.basis_list, self.temp_dir, self.n_jobs, + subjects_indexes) return shared_response - def inverse_transform(self, shared_response_list, subjects_indexes=None, + def inverse_transform(self, + shared_response_list, + subjects_indexes=None, sessions_indexes=None): """From shared response and basis from training data reconstruct subject's data @@ -1004,8 +1010,8 @@ def inverse_transform(self, shared_response_list, subjects_indexes=None, if self.temp_dir is None: basis_i = self.basis_list[i] else: - basis_i = np.load(os.path.join(self.temp_dir, - "basis_%i.npy" % i)) + basis_i = np.load( + os.path.join(self.temp_dir, "basis_%i.npy" % i)) for j in sessions_indexes: data_.append(shared_response_list[j].dot(basis_i)) From 51625255a0bdd9538736b9754bab4e3df65ec7b2 Mon Sep 17 00:00:00 2001 From: hrichard Date: Wed, 9 Oct 2019 17:00:31 +0200 Subject: [PATCH 17/24] temporary mydetsrm file to see if behavior is the same as regular DetSRM --- brainiak/funcalign/fastsrm.py | 20 +- brainiak/funcalign/mydetsrm.py | 329 +++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 brainiak/funcalign/mydetsrm.py diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 2a741804d..37b08c48c 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -1,4 +1,23 @@ """Fast Shared Response Model (FastSRM) + +The implementations are based on the following publications: + +.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", + P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge + Advances in Neural Information Processing Systems (NIPS), 2015. + http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model + +.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging + Datasets", + Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. + Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, + Kenneth A. Norman, + IEEE International Conference on Big Data, 2016. + https://doi.org/10.1109/BigData.2016.7840719 + +.. [Richard2019] "Fast Shared Response Model for fMRI data" + H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019 + https://arxiv.org/pdf/1909.12537.pdf """ # Author: Hugo Richard @@ -11,7 +30,6 @@ from sklearn.exceptions import NotFittedError from joblib import Parallel, delayed import os -import glob import hashlib __all__ = [ diff --git a/brainiak/funcalign/mydetsrm.py b/brainiak/funcalign/mydetsrm.py new file mode 100644 index 000000000..c956cbd4b --- /dev/null +++ b/brainiak/funcalign/mydetsrm.py @@ -0,0 +1,329 @@ +import numpy as np +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.utils import check_random_state +from scipy.sparse import diags +import scipy + + +def hyperalign(X, Y, scale=False, primal=None): + """ + Hyperalign X with Y using R and sc such that + frobenius norm ||sc RX - Y||^2 is minimized and + R is an orthogonal matrix + sc is a scalar + Parameters + ---------- + X: (n_features, n_timeframes) nd array + source data + Y: (n_features, n_timeframes) nd array + target data + scale: bool + If scale is true, computes a floating scaling parameter sc such that: + ||sc * RX - Y||^2 is minimized and + - R is an orthogonal matrix + - sc is a scalar + If scale is false sc is set to 1 + primal: bool or None, optional, + Whether the SVD is done on the YX^T (primal) or Y^TX (dual) + if None primal is used iff n_features <= n_timeframes + + Returns + ---------- + R: (n_features, n_features) nd array + transformation matrix + sc: int + scaling parameter + """ + if np.linalg.norm(X) == 0 or np.linalg.norm(Y) == 0: + return diags(np.ones(X.shape[1])).tocsr(), 1 + + if primal is None: + primal = X.shape[1] >= X.shape[0] + + if primal: + A = Y.dot(X.T) + if A.shape[0] == A.shape[1]: + A += +1.e-18 * np.eye(A.shape[0]) + U, s, V = scipy.linalg.svd(A, full_matrices=0) + R = U.dot(V) + else: # "dual" mode + Uy, sy, Vy = scipy.linalg.svd(Y, full_matrices=0) + Ux, sx, Vx = scipy.linalg.svd(X, full_matrices=0) + A = np.diag(sy).dot(Vy).dot(Vx.T).dot(np.diag(sx)) + U, s, V = scipy.linalg.svd(A) + R = Uy.dot(U).dot(V).dot(Ux.T) + """ + if X.shape[0] > 10000: + R = diags(np.ones(X.shape[0])).tocsr() + s = np.sum(Y * X, 1) + """ + if scale: + sc = s.sum() / (np.linalg.norm(X)**2) + else: + sc = 1 + return R, sc + + +def create_orthogonal_matrix(rows, cols, random_state=None): + """ + Creates matrix W with orthogonal columns: + W.T.dot(W) = I + Parameters + ---------- + rows: int + number of rows + cols: int + number of columns + random_state : int or RandomState + Pseudo number generator state used for random sampling. + Returns + --------- + Matrix W of shape (rows, cols) such that W.T.dot(W) = np.eye(cols) + """ + v = rows + k = cols + if random_state is None: + rnd_matrix = np.random.rand(v, k) + else: + rnd_matrix = random_state.rand(v, k) + q, r = np.linalg.qr(rnd_matrix) + return q + + +def _compute_shared_response(compressed_data, basis, scale): + """ + Computes the shared response S using subject basis and scaling + the basis refers to sc_i * W_i + the scale refers to sc_i + """ + s = None + for m in range(len(basis)): + data_m = compressed_data[m] + if s is None: + s = basis[m].T.dot(data_m) + else: + s = s + basis[m].T.dot(data_m) + s /= np.sum(scale**2) + return s + + +def fast_srm(reduced_data, + random_state=None, + max_iter=10, + tol=1e-6, + use_scaling=False, + n_components=None): + """ + Computes shared response and basis in reduced space + the basis refers to sc_i * W_i + the scale refers to sc_i + + Parameters + ---------- + reduced_data: list of n_subjects np array of shape n_voxels, n_timeframes + The reduced data + random_state: RandomState + max_iter: int + tol: int + use_scaling: bool + If True the scaling procedure is used + n_components: int or None + number of components if n_voxels != n_components + Returns + ------- + scale: np array of shape n_subjects + shared_response: np array of shape n_components, n_timeframes + basis: list of n_subjects arrays of shape n_voxels, n_components + """ + + n_subjects = len(reduced_data) + basis = [] + scale = [] + random_state = check_random_state(random_state) + for subject in range(n_subjects): + n_voxels, n_timeframes = reduced_data[subject].shape + if n_components is None: + n_components = n_voxels + q = create_orthogonal_matrix(n_voxels, + n_components, + random_state=random_state) + basis.append(q) + scale.append(1.) + scale = np.array(scale) + + shared_response = _compute_shared_response(reduced_data, basis, scale) + for n_iter in range(max_iter): + for i in range(n_subjects): + X_i = reduced_data[i] + R, sc = hyperalign(shared_response, X_i, scale=use_scaling) + basis[i] = sc * R + + shared_response = _compute_shared_response(reduced_data, basis, scale) + + if np.sum([ + np.linalg.norm(reduced_data[i] - basis[i].dot(shared_response)) + for i in range(n_subjects) + ], + axis=0) < tol: + break + + return scale, basis, shared_response + + +class MyDetSRM(BaseEstimator, TransformerMixin): + """My Deterministic Shared Response Model (DetSRM) + """ + def __init__(self, n_iter=10, features=50, rand_seed=0): + self.n_iter = n_iter + self.features = features + self.rand_seed = rand_seed + return + + def fit(self, X, y=None): + """Compute the Deterministic Shared Response Model + + Parameters + ---------- + X : list of 2D arrays, element i has shape=[voxels_i, samples] + Each element in the list contains the fMRI data of one subject. + + y : not used + """ + _, self.w_, self.s_ = fast_srm(X, + self.rand_seed, + max_iter=self.n_iter, + n_components=self.features) + + return self + + def transform(self, X, y=None): + """Use the model to transform data to the Shared Response subspace + + Parameters + ---------- + X : list of 2D arrays, element i has shape=[voxels_i, samples_i] + Each element in the list contains the fMRI data of one subject. + + y : not used + + + Returns + ------- + s : list of 2D arrays, element i has shape=[features_i, samples_i] + Shared responses from input data (X) + """ + + # Check the number of subjects + if len(X) != len(self.w_): + raise ValueError("The number of subjects does not match the one" + " in the model.") + + s = [None] * len(X) + for subject in range(len(X)): + s[subject] = self.w_[subject].T.dot(X[subject]) + + return s + + def _objective_function(self, data, w, s): + """Calculate the objective function + + Parameters + ---------- + + data : list of 2D arrays, element i has shape=[voxels_i, samples] + Each element in the list contains the fMRI data of one subject. + + w : list of 2D arrays, element i has shape=[voxels_i, features] + The orthogonal transforms (mappings) :math:`W_i` for each subject. + + s : array, shape=[features, samples] + The shared response + + Returns + ------- + + objective : float + The objective function value. + """ + subjects = len(data) + objective = 0.0 + for m in range(subjects): + objective += \ + np.linalg.norm(data[m] - w[m].dot(s), 'fro') ** 2 + + return objective * 0.5 / data[0].shape[1] + + def _compute_shared_response(self, data, w): + """ Compute the shared response S + + Parameters + ---------- + + data : list of 2D arrays, element i has shape=[voxels_i, samples] + Each element in the list contains the fMRI data of one subject. + + w : list of 2D arrays, element i has shape=[voxels_i, features] + The orthogonal transforms (mappings) :math:`W_i` for each subject. + + Returns + ------- + + s : array, shape=[features, samples] + The shared response for the subjects data with the mappings in w. + """ + s = np.zeros((w[0].shape[1], data[0].shape[1])) + for m in range(len(w)): + s = s + w[m].T.dot(data[m]) + s /= len(w) + + return s + + @staticmethod + def _update_transform_subject(Xi, S): + """Updates the mappings `W_i` for one subject. + + Parameters + ---------- + + Xi : array, shape=[voxels, timepoints] + The fMRI data :math:`X_i` for aligning the subject. + + S : array, shape=[features, timepoints] + The shared response. + + Returns + ------- + + Wi : array, shape=[voxels, features] + The orthogonal transform (mapping) :math:`W_i` for the subject. + """ + A = Xi.dot(S.T) + # Solve the Procrustes problem + U, _, V = np.linalg.svd(A, full_matrices=False) + return U.dot(V) + + def transform_subject(self, X): + """Transform a new subject using the existing model. + The subject is assumed to have recieved equivalent stimulation + + Parameters + ---------- + + X : 2D array, shape=[voxels, timepoints] + The fMRI data of the new subject. + + Returns + ------- + + w : 2D array, shape=[voxels, features] + Orthogonal mapping `W_{new}` for new subject + """ + + # Check the number of TRs in the subject + if X.shape[1] != self.s_.shape[1]: + raise ValueError("The number of timepoints(TRs) does not match the" + "one in the model.") + + w = self._update_transform_subject(X, self.s_) + + return w From 6b5aa33764e39f9eaa89e30c2b1e285251d4635d Mon Sep 17 00:00:00 2001 From: hrichard Date: Fri, 11 Oct 2019 19:13:37 +0200 Subject: [PATCH 18/24] Add explicit calls to DetSRM when not using low ram --- brainiak/funcalign/fastsrm.py | 722 +++++++++++++++++++++++++++------- 1 file changed, 578 insertions(+), 144 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 37b08c48c..2374a67b5 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -22,15 +22,17 @@ # Author: Hugo Richard +import hashlib import logging +import os import numpy as np import scipy +from joblib import Parallel, delayed + +from brainiak.funcalign.srm import DetSRM from sklearn.base import BaseEstimator, TransformerMixin from sklearn.exceptions import NotFittedError -from joblib import Parallel, delayed -import os -import hashlib __all__ = [ "FastSRM", @@ -39,63 +41,407 @@ logger = logging.getLogger(__name__) -def check_imgs(imgs): +def get_shape(path): + """Get shape of saved np array + Parameters + ---------- + path: str + path to np array + """ + f = open(path, "rb") + version = np.lib.format.read_magic(f) + shape, fortran_order, dtype = np.lib.format._read_array_header(f, version) + f.close() + return shape + + +def is_low_ram(reduced_data): """ - Check input images + Depending on type of reduced_data infer if we are in low-ram mode or not + Parameters + ---------- + reduced_data : str or array, shape=[n_timeframes, n_supervoxels] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected shape is + [n_timeframes, n_supervoxels] + or Element i, j of the array is the data in array of + shape=[n_timeframes, n_supervoxels] + n_timeframes and n_supervoxels are + assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + """ + if type(reduced_data) == np.ndarray: + low_ram = False + elif (type(reduced_data) == str or type(reduced_data) == np.str_ + or type(reduced_data) == np.str): + low_ram = True + else: + raise ValueError("Reduced data are stored using " + "type %s which is neither np.ndarray or str" % + type(reduced_data)) + return low_ram + + +def safe_load(data): + """If data is an array returns data else returns np.load(data)""" + if isinstance(data, np.ndarray): + return data + else: + return np.load(data) + return data + + +def assert_non_empty_list(input_list, list_name): + """ + Check that input list is not empty + Parameters + ---------- + input_list: list + list_name: str + Name of the list + """ + if len(input_list) == 0: + raise ValueError("%s is a list of length 0 which is not valid" % + list_name) + + +def assert_array_2axis(array, name_array): + """Check that input is an np array with 2 axes + + Parameters + ---------- + array: np array + name_array: str + Name of the array + """ + + if not isinstance(array, np.ndarray): + raise ValueError("%s should be of type " + "np.ndarray but is of type %s" % + (name_array, type(array))) + + if len(array.shape) != 2: + raise ValueError("%s must have exactly 2 axes" + "but has %i axes" % (name_array, len(array.shape))) + + +def _check_imgs_list(imgs): + """ + Checks that imgs is a non empty list of elements of the same type + + Parameters + ---------- + + imgs : list + """ + # Check the list is non empty + assert_non_empty_list(imgs, "imgs") + + # Check that all input have same type + for i in range(len(imgs)): + if not isinstance(imgs[i], type(imgs[0])): + raise ValueError("imgs[%i] has type %s whereas \ + imgs[%i] has type %s. This is inconsistent." % + (i, type(imgs[i]), 0, type(imgs[0]))) + + +def _check_imgs_list_list(imgs): + """ + Check input images if they are list of list of arrays + + Parameters + ---------- + + imgs : list of list of array of shape [n_voxels, n_components] + imgs is a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + n_timeframes and n_voxels are assumed to be the same across + subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + ------- + shapes: array + Shape of input images + """ + n_subjects = len(imgs) + + # Check that the number of session is not 0 + assert_non_empty_list(imgs[0], "imgs[%i]" % 0) + + # Check that the number of sessions is the same for all subjects + n_sessions = None + for i in range(len(imgs)): + if n_sessions is None: + n_sessions = len(imgs[i]) + if n_sessions != len(imgs[i]): + raise ValueError("imgs[%i] has length %i whereas imgs[%i] \ + has length %i. All subjects should have the same number \ + of sessions." % (i, len(imgs[i]), 0, len(imgs[0]))) + + shapes = np.zeros((n_subjects, n_sessions, 2)) + # Run array-level checks + for i in range(len(imgs)): + for j in range(len(imgs[i])): + assert_array_2axis(imgs[i, j], "imgs[%i, %i]" % (i, j)) + shapes[i, j, :] = imgs[i, j].shape + + return shapes + + +def _check_imgs_list_array(imgs): + """ + Check input images if they are list of arrays. + In this case returned images are a list of list of arrays + where element i,j of the array is a numpy array of + shape [n_voxels, n_timeframes] that contains the data of subject i + collected during session j. Parameters ---------- + imgs : array of str, shape=[n_subjects, n_sessions] + imgs is a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + n_timeframes and n_voxels are assumed to be the same across + subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + ------- + shapes: array + Shape of input images + new_imgs: list of list of array of shape [n_voxels, n_components] + """ + n_subjects = len(imgs) + n_sessions = 1 + shapes = np.zeros((n_subjects, n_sessions, 2)) + new_imgs = [] + for i in range(len(imgs)): + assert_array_2axis(imgs[i], "imgs[%i]" % i) + shapes[i, 0, :] = imgs[i].shape + new_imgs.append([imgs[i]]) + + return new_imgs, shapes + + +def _check_imgs_array(imgs): + """Check input image if it is an array + + Parameters + ---------- imgs : array of str, shape=[n_subjects, n_sessions] Element i, j of the array is a path to the data of subject i collected during session j. Data are loaded with numpy.load and expected - shape is [n_timeframes, n_voxels] + shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 + + Returns + ------- + shapes : array + Shape of input images """ - if type(imgs) != np.ndarray: - raise ValueError("imgs should be of type " - "np.ndarray but is of type %s" % type(imgs)) + assert_array_2axis(imgs, "imgs") + n_subjects, n_sessions = imgs.shape - if len(imgs.shape) != 2: - raise ValueError("imgs should be an array of shape " - "[n_subjects, n_sessions] " - "but its shape is of size %i" % len(imgs.shape)) + shapes = np.zeros((n_subjects, n_sessions, 2)) + for i in range(n_subjects): + for j in range(n_sessions): + if not (isinstance(imgs[i, j], str) or isinstance( + imgs[i, j], np.str_) or isinstance(imgs[i, j], np.str)): + raise ValueError("imgs[i, j] is stored using " + "type %s which is not a str" % + type(imgs[i, j])) + shapes[i, j, :] = get_shape(imgs[i, j]) + return shapes - n_subjects, n_sessions = imgs.shape + +def _check_shapes_atlas(n_components, n_voxels, atlas_shape): + """Check if n_voxel in the atlas is consistent with number of voxels in + the data, that number of supervoxels is lower than number of voxels + but greater than number of components + + Parameters + ---------- + n_components : int + n_voxels : int + number of voxels in the data + atlas_shape: tuple""" + if atlas_shape is not None: + n_supervoxels, n_atlas_voxels = atlas_shape + if n_atlas_voxels != n_voxels: + raise ValueError("Number of voxels in the atlas is not the same \ + as the number of voxels in input data (imgs)") + + if n_supervoxels > n_voxels: + raise ValueError("Number of regions in the atlas should be less \ + than the number of voxels") + + if n_components is not None: + if n_supervoxels < n_components: + raise ValueError("Number of regions in the atlas should \ + be bigger than the number of components") + + +def _check_shapes_components(n_components, n_timeframes): + """Check that n_timeframes is greater than number of components""" + if n_components is not None: + if n_timeframes < n_components: + raise ValueError("Number of timeframes %i is shorter than " + "number of components %i" % + (n_timeframes, n_components)) + + +def _check_shapes(shapes, n_components=None, atlas_shape=None): + """Check that number of voxels is the same for each subjects. Number of + timeframes can vary between sessions but must be consistent across + subjects + + Parameters + ---------- + shapes : array of shape (n_subjects, n_sessions, 2) + Array of shapes of input images + """ + n_subjects, n_sessions, _ = shapes.shape if n_subjects <= 1: raise ValueError("The number of subjects should be greater than 1") + n_timeframes_list = [None] * n_sessions + n_voxels = None + for n in range(n_subjects): + for m in range(n_sessions): + if n_timeframes_list[m] is None: + n_timeframes_list[m] = shapes[m, n, 1] + + if n_voxels is None: + n_voxels = shapes[m, n, 0] + + if n_timeframes_list[m] != shapes[m, n, 1]: + raise ValueError("Subject %i Session %i does not have the " + "same number of timeframes " + "as Subject %i Session %i" % (n, m, 0, m)) + + if n_voxels != shapes[m, n, 0]: + raise ValueError("Subject %i Session %i" + " does not have the same number of voxels as " + "Subject %i Session %i." % (n, m, 0, 0)) + + _check_shapes_components(n_components, np.sum(n_timeframes_list)) + _check_shapes_atlas(n_components, n_voxels, atlas_shape) + + +def check_atlas(atlas): + """ Check input atlas -def check_n_components(n_supervoxels, n_components, n_timeframes): + Parameters + ---------- + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + or str + Probabilistic or deterministic atlas on which to project the data + Deterministic atlas is an array of shape [n_voxels,] where values + range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + If atlas is a str the corresponding array is loaded with numpy.load + and expected shape is (n_voxels,) for a deterministic atlas and + (n_supervoxels, n_voxels) for a probabilistic atlas. + + Returns + ------- + shape : array + atlas shape + """ + + if not (isinstance(atlas, np.ndarray) or isinstance(atlas, str) + or isinstance(atlas, np.str_) or isinstance(atlas, np.str)): + raise ValueError("Atlas is stored using " + "type %s which is neither np.ndarray or str" % + type(atlas)) + + if isinstance(atlas, np.ndarray): + return atlas.shape + else: + shape = get_shape(atlas) + if len(shape) == 1: + # We have a deterministic atlas + n_voxels = atlas.shape[0] + n_supervoxels = len(np.unique(np.load(atlas))) - 1 + return (n_supervoxels, n_voxels) + elif len(shape) == 2: + return shape + else: + raise ValueError("Atlas has %i axes. It should have either 0 or 1 \ + axes." % len(shape)) + + +def check_imgs(imgs, n_components=None, atlas_shape=None): """ - Check that n_timeframes >= n_components and n_supervoxels >= n_components + Check input images Parameters ---------- - n_supervoxels: int + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_voxels, n_timeframes] + n_timeframes and n_voxels are assumed to be the same across + subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 - n_components: int + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. - n_timeframes: int + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + Returns + ------- + reshaped_input: bool + True if input had to be reshaped to match the + n_subjects, n_sessions input + new_imgs: list of list of array or np array + input imgs reshaped if it is a list of arrays so that it becomes a + list of list of arrays + shapes: array + Shape of input images """ + reshaped_input = False + new_imgs = imgs + if isinstance(imgs, list): + _check_imgs_list(imgs) + if isinstance(imgs[0], list): + shapes = _check_imgs_list_list(imgs) + elif isinstance(imgs[0], np.ndarray): + new_imgs, shapes = _check_imgs_list_array(imgs) + reshaped_input = True + else: + raise ValueError( + "since imgs is a list it should be a list of list of array or \ + a list of array but imgs[0] as type %s" % type(imgs[0])) + elif isinstance(imgs, np.ndarray): + shapes = _check_imgs_array(imgs) + else: + raise ValueError( + "imgs should either be a list of an array but has type" % + type(imgs)) - if n_supervoxels < n_components: - raise ValueError("The number of regions in the atlas " - "%i is smaller than " - "the number of components %i of fastSRM" % - (n_supervoxels, n_components)) + _check_shapes(shapes, n_components, atlas_shape) - if n_timeframes < n_components: - raise ValueError("Number of timeframes %i is shorter than " - "number of components %i" % - (n_timeframes, n_components)) + return reshaped_input, new_imgs, shapes def check_reduced_data(reduced_data_list, @@ -109,30 +455,31 @@ def check_reduced_data(reduced_data_list, low_ram = is_low_ram(reduced_data_list[0, 0]) n_subjects, n_sessions = reduced_data_list.shape[:2] - # Let us check that reduced data have same number of voxels and timeframes - n_timeframes = None + # Let us check that reduced data have compatible shapes + n_timeframes_list = [None] * n_sessions n_supervoxels = None for n in range(n_subjects): for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] + data_nm = safe_load(reduced_data_list[n, m], low_ram) - if n_timeframes is None and n_supervoxels is None: - n_timeframes, n_supervoxels = data_nm.shape - else: - if n_timeframes != data_nm.shape[0]: - raise ValueError("Subject %i Session %i does not have the " - "same number of timeframes " - "as Subject %i Session %i" % (n, m, 0, 0)) + if n_timeframes_list[m] is None: + n_timeframes_list[m] = data_nm.shape[0] + + if n_supervoxels is None: + n_supervoxels = data_nm.shape[1] - if n_supervoxels != data_nm.shape[1]: - raise ValueError( - "Reduced data from Subject %i Session %i" - " does not have the same number of supervoxels as " - "Subject %i Session %i." % (n, m, 0, 0)) + if n_timeframes_list[m] != data_nm.shape[0]: + raise ValueError("Subject %i Session %i does not have the " + "same number of timeframes " + "as Subject %i Session %i" % (n, m, 0, m)) + if n_supervoxels != data_nm.shape[1]: + raise ValueError( + "Reduced data from Subject %i Session %i" + " does not have the same number of supervoxels as " + "Subject %i Session %i." % (n, m, 0, 0)) + + n_timeframes = np.sum(n_timeframes_list) if n_components is not None: check_n_components(n_supervoxels, n_components, n_timeframes) @@ -144,6 +491,9 @@ def create_temp_dir(temp_dir): """ This check whether temp_dir exists and creates dir otherwise """ + if temp_dir is None: + return None + if not os.path.exists(temp_dir): os.makedirs(temp_dir) else: @@ -152,7 +502,9 @@ def create_temp_dir(temp_dir): "by using the .clean() method") -def reduce_data_single(img, +def reduce_data_single(subject_index, + session_index, + img, atlas=None, inv_atlas=None, low_ram=False, @@ -161,17 +513,21 @@ def reduce_data_single(img, Parameters ---------- + subject_index : int - img : str + session_index : int + + img : str or array path to data. Data are loaded with numpy.load and expected shape is - [n_timeframes, n_voxels] + (n_voxels, n_timeframes) n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 - atlas : array, shape=[n_supervoxels, n_voxels] or None - or None or array, shape=[n_voxels] + img can also be an array of shape (n_voxels, n_timeframes) + + atlas : array, shape=[n_supervoxels, n_voxels] or [n_voxels] or None Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. @@ -197,38 +553,28 @@ def reduce_data_single(img, reduced_data : array, shape=[n_timeframes, n_supervoxels] reduced data """ - if atlas is None and inv_atlas is None: - raise ValueError("An atlas or the pseudo inverse of" - " a probabilistic atlas should be provided") + # Here we return to the conventions of the paper + data = safe_load(img).T + + n_timeframes, n_voxels = data.shape + + # Here we check that input is normalized + if (np.max(np.abs(np.mean(data, axis=0))) > 1e-6 + or np.max(np.abs(np.var(data, axis=0) - 1))) > 1e-6: + ValueError("Data in imgs[%i, %i] does not have 0 mean and unit \ + variance. If you are using NiftiMasker to mask your data \ + (nilearn) please use standardize=True." % + (subject_index, session_index)) if inv_atlas is None and atlas is not None: - n_voxels = atlas.shape[0] atlas_values = np.unique(atlas) if 0 in atlas_values: atlas_values = atlas_values[1:] - data = np.load(img) - - if data.shape[1] != n_voxels: - raise ValueError("%s have %i voxels and" - " the atlas has %i voxels." - "This is incompatible." % - (img, data.shape[1], n_voxels)) reduced_data = np.array( [np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T else: # this means that it is a probabilistic atlas - assert len(inv_atlas.shape) == 2 - - n_voxels = inv_atlas.shape[0] - data = np.load(img) - - if data.shape[1] != n_voxels: - raise ValueError("%s have %i voxels and the atlas" - " has %i voxels." - "This is incompatible." % - (img, data.shape[1], n_voxels)) - reduced_data = data.dot(inv_atlas) if low_ram: @@ -256,6 +602,14 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values @@ -292,26 +646,28 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 """ - if type(atlas) != np.ndarray: - raise ValueError("atlas should be of type np.ndarray but has type %s" % - (type(atlas))) + loaded_atlas = safe_load(atlas) - if len(atlas.shape) == 2: + if len(loaded_atlas.shape) == 2: A = None - A_inv = atlas.T.dot(np.linalg.inv(atlas.dot(atlas.T))) + A_inv = loaded_atlas.T.dot( + np.linalg.inv(loaded_atlas.dot(loaded_atlas.T))) else: - if len(atlas.shape) != 1: - raise ValueError("atlas should have shape of length 1 " - "(deterministic) or 2 (probabilistic) but input" - "atlas has shape of length %i" % len(atlas.shape)) - A = atlas + A = loaded_atlas A_inv = None - n_subjects, n_sessions = imgs.shape + n_subjects = len(imgs) + n_sessions = len(imgs[0]) - reduced_data_list = Parallel(n_jobs=n_jobs)(delayed(reduce_data_single)( - img, atlas=A, inv_atlas=A_inv, low_ram=low_ram, temp_dir=temp_dir) - for img in imgs.flatten()) + reduced_data_list = Parallel(n_jobs=n_jobs)( + delayed(reduce_data_single)(i, + j, + imgs[i, j], + atlas=A, + inv_atlas=A_inv, + low_ram=low_ram, + temp_dir=temp_dir) + for i in range(n_subjects) for j in range(n_sessions)) if low_ram: reduced_data_list = np.reshape(reduced_data_list, @@ -329,35 +685,6 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): return reduced_data_list -def is_low_ram(reduced_data): - """ - Depending on type of reduced_data infer if we are in low-ram mode or not - Parameters - ---------- - reduced_data : str or array, shape=[n_timeframes, n_supervoxels] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected shape is - [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of - shape=[n_timeframes, n_supervoxels] - n_timeframes and n_supervoxels are - assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - """ - if type(reduced_data) == np.ndarray: - low_ram = False - elif (type(reduced_data) == str or type(reduced_data) == np.str_ - or type(reduced_data) == np.str): - low_ram = True - else: - raise ValueError("Reduced data are stored using " - "type %s which is neither np.ndarray or str" % - type(reduced_data)) - return low_ram - - def _reduced_space_compute_shared_response(reduced_data_list, reduced_basis_list, n_components=50): @@ -521,13 +848,13 @@ def _compute_subject_basis(corr_mat): return U.dot(V) -def fast_srm(reduced_data_list, n_iter=10, n_components=None): +def fast_srm(reduced_data_list, n_iter=10, n_components=None, low_ram=False): """Computes shared response and basis in reduced space Parameters ---------- - reduced_data_list : array of str, shape=[n_subjects, n_sessions] + reduced_data_list : array, shape=[n_subjects, n_sessions] or array, shape=[n_subjects, n_sessions, n_timeframes, n_supervoxels] Element i, j of the array is a path to the data of subject i collected during session j. @@ -553,10 +880,64 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): shape=[n_timeframes, n_components] shared response, element i is the shared response during session i """ + if low_ram: + return lowram_srm(reduced_data_list, n_iter, n_components) + else: + # We need to switch data to DetSRM format + n_subjects, n_sessions = reduced_data_list.shape[:2] + # We store the correspondence between timeframes and session + timeframes_slices = [] + current_j = 0 + for j in range(n_sessions): + timeframes_slices.append( + slice(current_j, current_j + len(reduced_data_list[0, j]))) + current_j = len(reduced_data_list[0][j]) + # Now we can concatenate everything + X = [ + np.concatenate(reduced_data_list[i], axis=0).T + for i in range(n_subjects) + ] + + srm = DetSRM(n_iter=n_iter, features=n_components) + srm.fit(X) + + # SRM gives a list of data projected in shared space + # we get the shared response by averaging those + concatenated_s = np.mean(srm.transform(X), axis=0).T + + # Let us return the shared response sliced by sessions + return [concatenated_s[i] for i in timeframes_slices] + + +def lowram_srm(reduced_data_list, n_iter=10, n_components=None): + """Computes shared response and basis in reduced space - low_ram = check_reduced_data(reduced_data_list, - n_components=n_components, - return_low_ram=True) + Parameters + ---------- + + reduced_data_list : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_timeframes, n_supervoxels] + n_timeframes and n_supervoxels are + assumed to be the same across subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + n_iter : int + Number of iterations performed + + n_components : int or None + number of components + + Returns + ------- + + shared_response_list : list of array, element i has + shape=[n_timeframes, n_components] + shared response, element i is the shared response during session i + """ n_subjects, n_sessions = reduced_data_list.shape[:2] shared_response = _reduced_space_compute_shared_response( @@ -567,10 +948,7 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None): for n in range(n_subjects): cov = None for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] + data_nm = np.load(reduced_data_list[n, m]) if cov is None: cov = shared_response[m].T.dot(data_nm) else: @@ -690,6 +1068,14 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + basis_list : None or list of array, element i has shape=[n_components, n_voxels] basis of all subjects, element i is the basis of subject i @@ -734,9 +1120,13 @@ class FastSRM(BaseEstimator, TransformerMixin): ---------- atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + or str Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. + If atlas is a str the corresponding array is loaded with numpy.load + and expected shape is (n_voxels,) for a deterministic atlas and + (n_supervoxels, n_voxels) for a probabilistic atlas. n_components : int Number of timecourses of the shared coordinates @@ -770,11 +1160,13 @@ class FastSRM(BaseEstimator, TransformerMixin): Attributes ---------- - `basis_list`: list of array, element i has shape=[n_components, n_voxels] + `basis_list`: list of array, element i has shape=[n_voxels, n_components] or list of str - basis of all subjects, element i is the basis of subject i - or path to basis of all subjects, element i is the path to the - basis of subject i + - if basis is a list of array, element i is the basis of subject i + - if basis is a list of str, element i is the path to the basis + of subject i that is loaded with np.load yielding an array of + shape [n_voxels, n_components]. + Note that any call to clean erases this attribute Notes ----- @@ -782,6 +1174,7 @@ class FastSRM(BaseEstimator, TransformerMixin): H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019: Fast shared response model for fMRI data (https://arxiv.org/pdf/1909.12537.pdf) """ + def __init__( self, atlas, @@ -819,6 +1212,10 @@ def __init__( self.low_ram = low_ram def clean(self): + # TODO: Check that this does erase fastsrm file + """This erases temporary files and basis_list attribute to free memory. + This method should be called when fitted model is not needed anymore. + """ if self.temp_dir is not None: if os.path.exists(self.temp_dir): for root, dirs, files in os.walk(self.temp_dir, topdown=False): @@ -827,6 +1224,9 @@ def clean(self): for name in dirs: os.rmdir(os.path.join(root, name)) + if self.basis_list is not None: + self.basis_list is None + def fit(self, imgs): """Computes basis across subjects from input imgs @@ -837,21 +1237,31 @@ def fit(self, imgs): Element i, j of the array is a path to the data of subject i collected during session j. Data are loaded with numpy.load and expected - shape is [n_timeframes, n_voxels] + shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + Returns ------- self : object Returns the instance itself. Contains attributes listed at the object level. """ - + atlas_shape = check_atlas(self.atlas) + reshaped_input, imgs, shapes = check_imgs( + imgs, n_components=self.n_components, atlas_shape=atlas_shape) + self.clean() create_temp_dir(self.temp_dir) - check_imgs(imgs) if self.verbose is True: n_subjects, n_sessions = imgs.shape @@ -870,11 +1280,10 @@ def fit(self, imgs): logger.info("[FastSRM.fit] Finds shared " "response using reduced data") - shared_response_list = fast_srm( - reduced_data, - n_iter=self.n_iter, - n_components=self.n_components, - ) + shared_response_list = fast_srm(reduced_data, + n_iter=self.n_iter, + n_components=self.n_components, + low_ram=self.low_ram) if self.verbose is True: logger.info("[FastSRM.fit] Finds basis using " @@ -912,7 +1321,7 @@ def fit(self, imgs): self.basis_list = basis return self - def fit_transform(self, imgs, **fit_params): + def fit_transform(self, imgs, subjects_indexes=None, aggregate="mean"): """Computes basis across subjects and shared response from input imgs return shared response. @@ -922,12 +1331,24 @@ def fit_transform(self, imgs, **fit_params): Element i, j of the array is a path to the data of subject i collected during session j. Data are loaded with numpy.load and expected - shape is [n_timeframes, n_voxels] + shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + + aggregate: str or None, default="mean" + if "mean": returns the mean shared response S from all subjects + if None: returns the subject-specific response in shared space S_i + Returns -------- shared_response_list : list of array, element i has @@ -935,9 +1356,11 @@ def fit_transform(self, imgs, **fit_params): shared response, element i is the shared response during session i """ self.fit(imgs) - return self.transform(imgs) + return self.transform(imgs, + subjects_indexes=subjects_indexes, + aggregate=aggregate) - def transform(self, imgs, subjects_indexes=None): + def transform(self, imgs, subjects_indexes=None, aggregate="mean"): """From data in imgs and basis from training data, computes shared response. @@ -948,22 +1371,33 @@ def transform(self, imgs, subjects_indexes=None): Element i, j of the array is a path to the data of subject i collected during session j. Data are loaded with numpy.load and expected - shape is [n_timeframes, n_voxels] + shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) subjects_indexes : list or None: if None imgs[i] will be transformed using basis[i] otherwise imgs[i] will be transformed using basis[subjects_index[i]] + aggregate: str or None, default="mean" + if "mean": returns the mean shared response S from all subjects + if None: returns the subject-specific response in shared space S_i + Returns ------- shared_response_list : list of array, element i has - shape=[n_timeframes, n_components] + shape=[n_components, n_timeframes] shared response, element i is the shared response during session i """ if self.basis_list is None: @@ -991,7 +1425,7 @@ def inverse_transform(self, ---------- shared_response_list : list of array, element i has - shape=[n_timeframes, n_components] + shape=[n_components, n_timeframes] shared response, element i is the shared response during session i subjects_indexes : list or None From bb5f1affa3d9256bc1f6466395d1fb2e85e5240e Mon Sep 17 00:00:00 2001 From: hrichard Date: Tue, 15 Oct 2019 16:59:28 +0200 Subject: [PATCH 19/24] Add funcalign compatible API --- brainiak/funcalign/fastsrm.py | 483 ++++++++++++++++++++++++++-------- 1 file changed, 373 insertions(+), 110 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 2374a67b5..beb50db79 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -127,6 +127,21 @@ def assert_array_2axis(array, name_array): "but has %i axes" % (name_array, len(array.shape))) +def assert_valid_index(indexes, max_value, name_indexes): + """ + Check that indexes are between 0 and max_value and number + of indexes is less than max_value + """ + if len(indexes) > max_value: + raise ValueError("The length of %s should \ + be less than %i but is %i" % (name_indexes, max_value, len(indexes))) + for i, ind_i in enumerate(indexes): + if ind_i < 0 or ind_i >= len(max_value): + raise ValueError("Index %i of %s has value %i \ + whereas value should be between 0 and %i" % + (i, name_indexes, ind_i, max_value)) + + def _check_imgs_list(imgs): """ Checks that imgs is a non empty list of elements of the same type @@ -444,47 +459,113 @@ def check_imgs(imgs, n_components=None, atlas_shape=None): return reshaped_input, new_imgs, shapes -def check_reduced_data(reduced_data_list, - n_components=None, - return_low_ram=False): +def _check_shared_response_list_of_list(shared_response, n_components, + input_shapes): - if type(reduced_data_list) != np.ndarray: - raise ValueError("reduced data must have type np.ndarray but" - "has type %s" % type(reduced_data_list)) + # Check that shared_response is indeed a list of list of arrays + n_subjects = len(shared_response) + n_sessions = None + for i in range(len(shared_response)): + if not isinstance(shared_response[i], list): + raise ValueError("shared_response[0] is a list but\ + shared_response[%i] is not a list this is incompatible." % i) + assert_non_empty_list(shared_response[i], "shared_response[i]" % i) + if n_sessions is None: + n_sessions = len(shared_response[i]) + elif n_sessions != len(shared_response[i]): + raise ValueError( + "shared_response[%i] has len %i whereas \ + shared_response[0] has len %i. They should have same length" % + (i, len(shared_response[i]), len(shared_response[0]))) + for j in range(len(shared_response[i])): + assert_array_2axis(shared_response[i][j], + "shared_response[%i][%i]" % (i, j)) + + return _check_shared_response_list_sessions([ + np.mean([shared_response[i][j] for i in range(n_subjects)], axis=0) + for j in range(n_sessions) + ], n_components, input_shapes) + + +def _check_shared_response_list_sessions(shared_response, n_components, + input_shapes): + for j in range(len(shared_response)): + assert_array_2axis(shared_response[j], "shared_response[%i]" % j) + if input_shapes is not None: + if shared_response[j].shape[1] != input_shapes[0][j][1]: + raise ValueError( + "Number of timeframes in input images during \ + session %i does not match the number of \ + timeframes during session %i of shared_response (%i != %i)" % + (j, j, shared_response[j].shape[1], input_shapes[0, j, 1])) + if n_components is not None: + if shared_response[j].shape[0] != n_components: + raise ValueError( + "Number of components in \ + shared_response during session %i is different than\ + the number of components of the model (%i != %i)" % + (j, shared_response[j].shape[0], n_components)) + return shared_response - low_ram = is_low_ram(reduced_data_list[0, 0]) - n_subjects, n_sessions = reduced_data_list.shape[:2] - # Let us check that reduced data have compatible shapes - n_timeframes_list = [None] * n_sessions - n_supervoxels = None - for n in range(n_subjects): - for m in range(n_sessions): - data_nm = safe_load(reduced_data_list[n, m], low_ram) +def _check_shared_response_list_subjects(shared_response, n_components, + input_shapes): + for i in range(len(shared_response)): + assert_array_2axis(shared_response[i], "shared_response[%i]" % i) - if n_timeframes_list[m] is None: - n_timeframes_list[m] = data_nm.shape[0] + return _check_shared_response_array(np.mean(shared_response, axis=0), + n_components, input_shapes) - if n_supervoxels is None: - n_supervoxels = data_nm.shape[1] - if n_timeframes_list[m] != data_nm.shape[0]: - raise ValueError("Subject %i Session %i does not have the " - "same number of timeframes " - "as Subject %i Session %i" % (n, m, 0, m)) +def _check_shared_response_array(shared_response, n_components, input_shapes): + assert_array_2axis(shared_response, "shared_response") + return _check_shared_response_list_sessions([shared_response], + n_components, input_shapes) - if n_supervoxels != data_nm.shape[1]: - raise ValueError( - "Reduced data from Subject %i Session %i" - " does not have the same number of supervoxels as " - "Subject %i Session %i." % (n, m, 0, 0)) - n_timeframes = np.sum(n_timeframes_list) - if n_components is not None: - check_n_components(n_supervoxels, n_components, n_timeframes) +def check_shared_response(shared_response, + aggregate="mean", + n_components=None, + input_shapes=None): + """ + Check that shared response has valid input and turn it into + a session-wise shared response - if return_low_ram: - return low_ram + Returns + ------- + added_session: bool + True if an artificial sessions was added to match the list of + session input type for shared_response + reshaped_shared_response: list of arrays + shared response (reshaped to match the list of session input) + """ + # Depending on aggregate and shape of input we infer what to do + if isinstance(shared_response, list): + assert_non_empty_list(shared_response, "shared_response") + if isinstance(shared_response[0], list): + if aggregate == "mean": + raise ValueError("self.aggregate has value 'mean' but\ + shared response is a list of list. This is\ + incompatible") + return False, _check_shared_response_list_of_list( + shared_response, n_components, input_shapes) + elif isinstance(shared_response[0], np.ndarray): + if aggregate == "mean": + return False, _check_shared_response_list_sessions( + shared_response, n_components, input_shapes) + else: + return True, _check_shared_response_list_subjects( + shared_response, n_components, input_shapes) + else: + raise ValueError("shared_response is a list but\ + shared_response[0] is neither a list or an array.\ + This is invalid.") + elif isinstance(shared_response, np.ndarray): + return True, _check_shared_response_array(shared_response, + n_components, input_shapes) + else: + raise ValueError("shared_response should be either\ + a list or an array but is of type %s" % type(shared_response)) def create_temp_dir(temp_dir): @@ -817,6 +898,7 @@ def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): corr_mat = np.load(path) else: corr_mat += np.load(path) + os.remove(path) basis_i = _compute_subject_basis(corr_mat) path = os.path.join(temp_dir, "basis_%i" % subject_number) np.save(path, basis_i) @@ -848,7 +930,11 @@ def _compute_subject_basis(corr_mat): return U.dot(V) -def fast_srm(reduced_data_list, n_iter=10, n_components=None, low_ram=False): +def fast_srm(reduced_data_list, + n_iter=10, + n_components=None, + low_ram=False, + seed=0): """Computes shared response and basis in reduced space Parameters @@ -898,7 +984,7 @@ def fast_srm(reduced_data_list, n_iter=10, n_components=None, low_ram=False): for i in range(n_subjects) ] - srm = DetSRM(n_iter=n_iter, features=n_components) + srm = DetSRM(n_iter=n_iter, features=n_components, rand_seed=seed) srm.fit(X) # SRM gives a list of data projected in shared space @@ -1001,7 +1087,7 @@ def _compute_basis_subject_online(sessions, shared_response_list): def _compute_shared_response_online_single(subjects, basis_list, temp_dir, - subjects_indexes): + subjects_indexes, aggregate): """Computes shared response during one session with basis fixed Parameters @@ -1027,33 +1113,50 @@ def _compute_shared_response_online_single(subjects, basis_list, temp_dir, list of indexes corresponding to the subjects to use to compute shared response + aggregate: str or None, default="mean" + if "mean": returns the mean shared response S from all subjects + if None: returns the subject-specific response in shared space S_i + Returns ------- - shared_response : array, shape=[n_timeframes, n_components] + shared_response : array, shape=[n_timeframes, n_components] or list shared response """ n = 0 - shared_response = None + if aggregate == "mean": + shared_response = None + if aggregate is None: + shared_response = [] + for k, i in enumerate(subjects_indexes): subject = subjects[k] - data = np.load(subject) + # Transpose to be consistent with paper + data = safe_load(subject).T if temp_dir is None: basis_i = basis_list[i] else: basis_i = np.load(os.path.join(temp_dir, "basis_%i.npy" % i)) - if shared_response is None: - shared_response = data.dot(basis_i.T) - else: - shared_response += data.dot(basis_i.T) + if aggregate == "mean": + if shared_response is None: + shared_response = data.dot(basis_i.T) + else: + shared_response += data.dot(basis_i.T) + n += 1 + + if aggregate is None: + shared_response.append(data.dot(basis_i.T)) + + if aggregate is None: + return shared_response - n += 1 - return shared_response / float(n) + if aggregate == "mean": + return shared_response / float(n) def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, - subjects_indexes): + subjects_indexes, aggregate): """Computes shared response with basis fixed Parameters @@ -1072,10 +1175,6 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, the array is a numpy array of shape [n_voxels, n_timeframes] that contains the data of subject i collected during session j. - imgs can also be a list of arrays where element i of the array is - a numpy array of shape [n_voxels, n_timeframes] that contains the - data of subject i (number of sessions is implicitly 1) - basis_list : None or list of array, element i has shape=[n_components, n_voxels] basis of all subjects, element i is the basis of subject i @@ -1092,17 +1191,35 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, list of indexes corresponding to the subjects to use to compute shared response + aggregate: str or None, default="mean" + if "mean": returns the mean shared response S from all subjects + if None: returns the subject-specific response in shared space S_i + Returns ------- - shared_response_list : list of array, element i has - shape=[n_timeframes, n_components] + shared_response_list : list of array or list of list of array shared response, element i is the shared response during session i + or element i, j is the shared response of subject i during session j """ + + n_subjects = len(subjects_indexes) + n_sessions = len(imgs[0]) + shared_response_list = Parallel(n_jobs=n_jobs)( - delayed(_compute_shared_response_online_single)( - subjects, basis_list, temp_dir, subjects_indexes) - for subjects in imgs.T) + delayed(_compute_shared_response_online_single) + ([imgs[i, j] for i in range(n_subjects)], basis_list, temp_dir, + subjects_indexes, aggregate) for j in range(n_sessions)) + + if aggregate is None: + shared_response_list = [[ + shared_response_list[j][i].T for i in range(n_subjects) + ] for j in range(n_sessions)] + + if aggregate == "mean": + shared_response_list = [ + shared_response_list[j].T for j in range(n_sessions) + ] return shared_response_list @@ -1145,8 +1262,8 @@ class FastSRM(BaseEstimator, TransformerMixin): this increases the number of IO but reduces memory complexity when the number of subject and / or sessions is large - random_state : int or RandomState - Pseudo number generator state used for random sampling. + seed : int + Seed used for random sampling. n_jobs : int, optional, default=1 The number of CPUs to use to do the computation. @@ -1157,15 +1274,21 @@ class FastSRM(BaseEstimator, TransformerMixin): if False, logs are disabled. if "warn" only warnings are printed. + aggregate: str or None, default="mean" + if "mean": shared_response is the mean shared response S + from all subjects + if None: shared_response contains all subject-specific responses + in shared space S_i + Attributes ---------- - `basis_list`: list of array, element i has shape=[n_voxels, n_components] + `basis_list`: list of array, element i has shape=[n_components, n_voxels] or list of str - if basis is a list of array, element i is the basis of subject i - if basis is a list of str, element i is the path to the basis of subject i that is loaded with np.load yielding an array of - shape [n_voxels, n_components]. + shape [n_components, n_voxels]. Note that any call to clean erases this attribute Notes @@ -1174,26 +1297,29 @@ class FastSRM(BaseEstimator, TransformerMixin): H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019: Fast shared response model for fMRI data (https://arxiv.org/pdf/1909.12537.pdf) """ - - def __init__( - self, - atlas, - n_components=20, - n_iter=100, - temp_dir=None, - low_ram=False, - random_state=None, - n_jobs=1, - verbose="warn", - ): - - self.random_state = random_state + def __init__(self, + atlas, + n_components=20, + n_iter=100, + temp_dir=None, + low_ram=False, + seed=None, + n_jobs=1, + verbose="warn", + aggregate="mean"): + + self.seed = seed self.n_jobs = n_jobs self.verbose = verbose self.n_components = n_components self.n_iter = n_iter self.atlas = atlas + if aggregate is not None and aggregate != "mean": + raise ValueError("aggregate can have only value mean or None") + + self.aggregate = aggregate + self.basis_list = None if temp_dir is None: @@ -1283,7 +1409,8 @@ def fit(self, imgs): shared_response_list = fast_srm(reduced_data, n_iter=self.n_iter, n_components=self.n_components, - low_ram=self.low_ram) + low_ram=self.low_ram, + seed=self.seed) if self.verbose is True: logger.info("[FastSRM.fit] Finds basis using " @@ -1321,7 +1448,7 @@ def fit(self, imgs): self.basis_list = basis return self - def fit_transform(self, imgs, subjects_indexes=None, aggregate="mean"): + def fit_transform(self, imgs, subjects_indexes=None): """Computes basis across subjects and shared response from input imgs return shared response. @@ -1345,22 +1472,26 @@ def fit_transform(self, imgs, subjects_indexes=None, aggregate="mean"): a numpy array of shape [n_voxels, n_timeframes] that contains the data of subject i (number of sessions is implicitly 1) - aggregate: str or None, default="mean" - if "mean": returns the mean shared response S from all subjects - if None: returns the subject-specific response in shared space S_i - Returns -------- - shared_response_list : list of array, element i has - shape=[n_timeframes, n_components] - shared response, element i is the shared response during session i + shared_response : list of arrays, list of list of array or arrays + - if imgs is a list of array and self.aggregate="mean", shared + response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None, shared + response is a list of array, element i is the projection of data of + subject i in shared space. + - if imgs is an array or a list of list of array and + self.aggregate="mean", shared response is a list of array, + element j is the shared response during session j + - if imgs is an array or a list of list of array and + self.aggregate=None, shared response is a list of list of array, + element i, j is the projection of data of subject i collected + during session j in shared space. """ self.fit(imgs) - return self.transform(imgs, - subjects_indexes=subjects_indexes, - aggregate=aggregate) + return self.transform(imgs, subjects_indexes=subjects_indexes) - def transform(self, imgs, subjects_indexes=None, aggregate="mean"): + def transform(self, imgs, subjects_indexes=None): """From data in imgs and basis from training data, computes shared response. @@ -1390,43 +1521,85 @@ def transform(self, imgs, subjects_indexes=None, aggregate="mean"): otherwise imgs[i] will be transformed using basis[subjects_index[i]] - aggregate: str or None, default="mean" - if "mean": returns the mean shared response S from all subjects - if None: returns the subject-specific response in shared space S_i - Returns - ------- - shared_response_list : list of array, element i has - shape=[n_components, n_timeframes] - shared response, element i is the shared response during session i - """ + -------- + shared_response : list of arrays, list of list of array or arrays + - if imgs is a list of array and self.aggregate="mean", shared + response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None, shared + response is a list of array, element i is the projection of data of + subject i in shared space. + - if imgs is an array or a list of list of array and + self.aggregate="mean", shared response is a list of array, + element j is the shared response during session j + - if imgs is an array or a list of list of array and + self.aggregate=None, shared response is a list of list of array, + element i, j is the projection of data of subject i collected + during session j in shared space. + """ + aggregate = self.aggregate if self.basis_list is None: raise NotFittedError("The model fit has not been run yet.") + atlas_shape = check_atlas(self.atlas) + reshaped_input, imgs, shapes = check_imgs( + imgs, n_components=self.n_components, atlas_shape=atlas_shape) + if subjects_indexes is None: subjects_indexes = np.arange(len(imgs)) else: subjects_indexes = np.array(subjects_indexes) + # Transform specific checks + if len(subjects_indexes) < len(imgs): + raise ValueError("Input data imgs has len %i whereas \ + subject_indexes has len %i. The number of basis used to compute \ + the shared response should be equal to the number of subjects in \ + imgs" % (len(imgs), len(subjects_indexes))) + + assert_valid_index(subjects_indexes, len(self.basis_list), + "subjects_indexes") + shared_response = _compute_shared_response_online( imgs, self.basis_list, self.temp_dir, self.n_jobs, - subjects_indexes) + subjects_indexes, aggregate) + + # If shared response has only 1 session we need to reshape it + if reshaped_input: + if aggregate == "mean": + shared_response = shared_response[0] + if aggregate is None: + shared_response = [ + shared_response[i][0] for i in range(len(subjects_indexes)) + ] return shared_response - def inverse_transform(self, - shared_response_list, - subjects_indexes=None, - sessions_indexes=None): + def inverse_transform( + self, + shared_response, + subjects_indexes=None, + sessions_indexes=None, + ): """From shared response and basis from training data reconstruct subject's data Parameters ---------- - shared_response_list : list of array, element i has - shape=[n_components, n_timeframes] - shared response, element i is the shared response during session i + shared_response : list of arrays, list of list of array or arrays + - if imgs is a list of array and self.aggregate="mean", shared + response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None, shared + response is a list of array, element i is the projection of data of + subject i in shared space. + - if imgs is an array or a list of list of array and + self.aggregate="mean", shared response is a list of array, + element j is the shared response during session j + - if imgs is an array or a list of list of array and + self.aggregate=None, shared response is a list of list of array, + element i, j is the projection of data of subject i collected + during session j in shared space. subjects_indexes : list or None if None reconstructs data of all subjects' used during train @@ -1444,29 +1617,119 @@ def inverse_transform(self, n_timeframes, n_voxels] Reconstructed data for chosen subjects and sessions """ + added_session, shared = check_shared_response( + shared_response, self.aggregate, n_components=self.n_components) n_subjects = len(self.basis_list) + n_sessions = len(shared) + + for j in range(n_sessions): + assert_array_2axis(shared[j]) if subjects_indexes is None: subjects_indexes = np.arange(n_subjects) else: subjects_indexes = np.array(subjects_indexes) + assert_valid_index(subjects_indexes, n_subjects, "subjects_indexes") + if sessions_indexes is None: - sessions_indexes = np.arange(len(shared_response_list)) + sessions_indexes = np.arange(len(shared)) else: sessions_indexes = np.array(sessions_indexes) + assert_valid_index(sessions_indexes, n_sessions, "sessions_indexes") + data = [] for i in subjects_indexes: data_ = [] + basis_i = safe_load(self.basis_list[i]) + if added_session: + data.append(basis_i.T.dot(shared[0])) + else: + for j in sessions_indexes: + data_.append(basis_i.T.dot(shared[j])) + data.append(np.array(data_)) + return np.array(data) + + def add_subjects(self, imgs, shared_response): + """ Add subjects to the current fit + Each new basis will be appended at the end of the list of basis + (which can be accessed using self.basis) + imgs : array of str, shape=[n_subjects, n_sessions] + Element i, j of the array is a path to the data of subject i + collected during session j. + Data are loaded with numpy.load and expected + shape is [n_voxels, n_timeframes] + n_timeframes and n_voxels are assumed to be the same across + subjects + n_timeframes can vary across sessions + Each voxel's timecourse is assumed to have mean 0 and variance 1 + + imgs can also be a list of list of arrays where element i, j of + the array is a numpy array of shape [n_voxels, n_timeframes] that + contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i of the array is + a numpy array of shape [n_voxels, n_timeframes] that contains the + data of subject i (number of sessions is implicitly 1) + + shared_response : list of arrays, list of list of array or arrays + - if imgs is a list of array and self.aggregate="mean", shared + response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None, shared + response is a list of array, element i is the projection of data of + subject i in shared space. + - if imgs is an array or a list of list of array and + self.aggregate="mean", shared response is a list of array, + element j is the shared response during session j + - if imgs is an array or a list of list of array and + self.aggregate=None, shared response is a list of list of array, + element i, j is the projection of data of subject i collected + during session j in shared space. + """ + atlas_shape = check_atlas(self.atlas) + reshaped_input, imgs, shapes = check_imgs( + imgs, n_components=self.n_components, atlas_shape=atlas_shape) + + _, shared_response_list = check_shared_response( + shared_response, + n_components=self.n_components, + aggregate=self.aggregate, + input_shapes=shapes) + + # we need to transpose shared_response_list to be consistent with + # other functions + shared_response_list = [ + shared_response_list[j].T for j in range(len(shared_response_list)) + ] + + if self.n_jobs == 1: + basis = [] + for i, sessions in enumerate(imgs): + basis_i = _compute_basis_subject_online( + sessions, shared_response_list) + if self.temp_dir is None: + basis.append(basis_i) + else: + path = os.path.join(self.temp_dir, "basis_%i" % i) + np.save(path, basis_i) + basis.append(path + ".npy") + del basis_i + else: if self.temp_dir is None: - basis_i = self.basis_list[i] + basis = Parallel(n_jobs=self.n_jobs)( + delayed(_compute_basis_subject_online)( + sessions, shared_response_list) for sessions in imgs) else: - basis_i = np.load( - os.path.join(self.temp_dir, "basis_%i.npy" % i)) + Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_corr_mat)( + subject, shared_response_list[m], self.temp_dir) + for m, subjects in enumerate(imgs.T) + for subject in subjects) - for j in sessions_indexes: - data_.append(shared_response_list[j].dot(basis_i)) + basis = Parallel(n_jobs=self.n_jobs)( + delayed(_compute_and_save_subject_basis)( + len(self.basis_list) + i, sessions, self.temp_dir) + for i, sessions in enumerate(imgs)) - data.append(np.array(data_)) - return np.array(data) + self.basis_list += basis From e3703c8f6339c0635472732c9d15c99ded3cc823 Mon Sep 17 00:00:00 2001 From: hrichard Date: Tue, 15 Oct 2019 17:12:40 +0200 Subject: [PATCH 20/24] Add the option to provide no atlas (defaults to standard SRM) --- brainiak/funcalign/fastsrm.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index beb50db79..df22f6efa 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -362,7 +362,7 @@ def check_atlas(atlas): Parameters ---------- atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - or str + or str or None Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. @@ -372,9 +372,11 @@ def check_atlas(atlas): Returns ------- - shape : array + shape : array or None atlas shape """ + if atlas is None: + return None if not (isinstance(atlas, np.ndarray) or isinstance(atlas, str) or isinstance(atlas, np.str_) or isinstance(atlas, np.str)): @@ -654,9 +656,11 @@ def reduce_data_single(subject_index, reduced_data = np.array( [np.mean(data[:, atlas == c], axis=1) for c in atlas_values]).T - else: + elif inv_atlas is not None and atlas is None: # this means that it is a probabilistic atlas reduced_data = data.dot(inv_atlas) + else: + reduced_data = data if low_ram: name = hashlib.md5(img.encode()).hexdigest() @@ -692,6 +696,7 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): data of subject i (number of sessions is implicitly 1) atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] + or None Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. @@ -727,15 +732,18 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): n_timeframes can vary across sessions Each voxel's timecourse is assumed to have mean 0 and variance 1 """ - loaded_atlas = safe_load(atlas) - - if len(loaded_atlas.shape) == 2: + if atlas is None: A = None - A_inv = loaded_atlas.T.dot( - np.linalg.inv(loaded_atlas.dot(loaded_atlas.T))) - else: - A = loaded_atlas A_inv = None + else: + loaded_atlas = safe_load(atlas) + if len(loaded_atlas.shape) == 2: + A = None + A_inv = loaded_atlas.T.dot( + np.linalg.inv(loaded_atlas.dot(loaded_atlas.T))) + else: + A = loaded_atlas + A_inv = None n_subjects = len(imgs) n_sessions = len(imgs[0]) @@ -1237,7 +1245,7 @@ class FastSRM(BaseEstimator, TransformerMixin): ---------- atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - or str + or str or None, default=None Probabilistic or deterministic atlas on which to project the data Deterministic atlas is an array of shape [n_voxels,] where values range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. @@ -1298,7 +1306,7 @@ class FastSRM(BaseEstimator, TransformerMixin): shared response model for fMRI data (https://arxiv.org/pdf/1909.12537.pdf) """ def __init__(self, - atlas, + atlas=None, n_components=20, n_iter=100, temp_dir=None, From f5deb8a3d5f0a5ddff656a6bedee7e35f985567f Mon Sep 17 00:00:00 2001 From: hrichard Date: Fri, 18 Oct 2019 15:44:10 +0200 Subject: [PATCH 21/24] Handles various input format --- brainiak/funcalign/fastsrm.py | 346 +++++++------- tests/funcalign/test_fastsrm.py | 816 +++++++++++++++++++++----------- 2 files changed, 723 insertions(+), 439 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index df22f6efa..2d8b13d07 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -33,6 +33,7 @@ from brainiak.funcalign.srm import DetSRM from sklearn.base import BaseEstimator, TransformerMixin from sklearn.exceptions import NotFittedError +import uuid __all__ = [ "FastSRM", @@ -55,42 +56,20 @@ def get_shape(path): return shape -def is_low_ram(reduced_data): - """ - Depending on type of reduced_data infer if we are in low-ram mode or not - Parameters - ---------- - reduced_data : str or array, shape=[n_timeframes, n_supervoxels] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected shape is - [n_timeframes, n_supervoxels] - or Element i, j of the array is the data in array of - shape=[n_timeframes, n_supervoxels] - n_timeframes and n_supervoxels are - assumed to be the same across subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - """ - if type(reduced_data) == np.ndarray: - low_ram = False - elif (type(reduced_data) == str or type(reduced_data) == np.str_ - or type(reduced_data) == np.str): - low_ram = True - else: - raise ValueError("Reduced data are stored using " - "type %s which is neither np.ndarray or str" % - type(reduced_data)) - return low_ram - - def safe_load(data): """If data is an array returns data else returns np.load(data)""" if isinstance(data, np.ndarray): return data else: return np.load(data) - return data + + +def safe_encode(img): + if isinstance(img, np.ndarray): + name = hashlib.md5(img.tostring()).hexdigest() + else: + name = hashlib.md5(img.encode()).hexdigest() + return name def assert_non_empty_list(input_list, list_name): @@ -123,7 +102,7 @@ def assert_array_2axis(array, name_array): (name_array, type(array))) if len(array.shape) != 2: - raise ValueError("%s must have exactly 2 axes" + raise ValueError("%s must have exactly 2 axes " "but has %i axes" % (name_array, len(array.shape))) @@ -132,14 +111,11 @@ def assert_valid_index(indexes, max_value, name_indexes): Check that indexes are between 0 and max_value and number of indexes is less than max_value """ - if len(indexes) > max_value: - raise ValueError("The length of %s should \ - be less than %i but is %i" % (name_indexes, max_value, len(indexes))) for i, ind_i in enumerate(indexes): - if ind_i < 0 or ind_i >= len(max_value): - raise ValueError("Index %i of %s has value %i \ - whereas value should be between 0 and %i" % - (i, name_indexes, ind_i, max_value)) + if ind_i < 0 or ind_i >= max_value: + raise ValueError("Index %i of %s has value %i " + "whereas value should be between 0 and %i" % + (i, name_indexes, ind_i, max_value - 1)) def _check_imgs_list(imgs): @@ -157,8 +133,9 @@ def _check_imgs_list(imgs): # Check that all input have same type for i in range(len(imgs)): if not isinstance(imgs[i], type(imgs[0])): - raise ValueError("imgs[%i] has type %s whereas \ - imgs[%i] has type %s. This is inconsistent." % + raise ValueError("imgs[%i] has type %s whereas " + "imgs[%i] has type %s. " + "This is inconsistent." % (i, type(imgs[i]), 0, type(imgs[0]))) @@ -194,16 +171,17 @@ def _check_imgs_list_list(imgs): if n_sessions is None: n_sessions = len(imgs[i]) if n_sessions != len(imgs[i]): - raise ValueError("imgs[%i] has length %i whereas imgs[%i] \ - has length %i. All subjects should have the same number \ - of sessions." % (i, len(imgs[i]), 0, len(imgs[0]))) + raise ValueError("imgs[%i] has length %i whereas imgs[%i] " + "has length %i. All subjects should have " + "the same number of sessions." % + (i, len(imgs[i]), 0, len(imgs[0]))) shapes = np.zeros((n_subjects, n_sessions, 2)) # Run array-level checks for i in range(len(imgs)): for j in range(len(imgs[i])): - assert_array_2axis(imgs[i, j], "imgs[%i, %i]" % (i, j)) - shapes[i, j, :] = imgs[i, j].shape + assert_array_2axis(imgs[i][j], "imgs[%i][%i]" % (i, j)) + shapes[i, j, :] = imgs[i][j].shape return shapes @@ -274,50 +252,40 @@ def _check_imgs_array(imgs): for j in range(n_sessions): if not (isinstance(imgs[i, j], str) or isinstance( imgs[i, j], np.str_) or isinstance(imgs[i, j], np.str)): - raise ValueError("imgs[i, j] is stored using " + raise ValueError("imgs[%i, %i] is stored using " "type %s which is not a str" % - type(imgs[i, j])) + (i, j, type(imgs[i, j]))) shapes[i, j, :] = get_shape(imgs[i, j]) return shapes -def _check_shapes_atlas(n_components, n_voxels, atlas_shape): - """Check if n_voxel in the atlas is consistent with number of voxels in - the data, that number of supervoxels is lower than number of voxels - but greater than number of components +def _check_shapes_components(n_components, n_timeframes): + """Check that n_timeframes is greater than number of components""" + + +def _check_shapes_atlas_compatibility(n_voxels, + n_timeframes, + n_components=None, + atlas_shape=None): + if n_components is not None: + if np.sum(n_timeframes) < n_components: + raise ValueError("Total number of timeframes is shorter than " + "number of components (%i < %i)" % + (np.sum(n_timeframes), n_components)) - Parameters - ---------- - n_components : int - n_voxels : int - number of voxels in the data - atlas_shape: tuple""" if atlas_shape is not None: n_supervoxels, n_atlas_voxels = atlas_shape if n_atlas_voxels != n_voxels: - raise ValueError("Number of voxels in the atlas is not the same \ - as the number of voxels in input data (imgs)") - - if n_supervoxels > n_voxels: - raise ValueError("Number of regions in the atlas should be less \ - than the number of voxels") - - if n_components is not None: - if n_supervoxels < n_components: - raise ValueError("Number of regions in the atlas should \ - be bigger than the number of components") - - -def _check_shapes_components(n_components, n_timeframes): - """Check that n_timeframes is greater than number of components""" - if n_components is not None: - if n_timeframes < n_components: - raise ValueError("Number of timeframes %i is shorter than " - "number of components %i" % - (n_timeframes, n_components)) + raise ValueError( + "Number of voxels in the atlas is not the same " + "as the number of voxels in input data (%i != %i)" % + (n_atlas_voxels, n_voxels)) -def _check_shapes(shapes, n_components=None, atlas_shape=None): +def _check_shapes(shapes, + n_components=None, + atlas_shape=None, + ignore_nsubjects=False): """Check that number of voxels is the same for each subjects. Number of timeframes can vary between sessions but must be consistent across subjects @@ -329,7 +297,7 @@ def _check_shapes(shapes, n_components=None, atlas_shape=None): """ n_subjects, n_sessions, _ = shapes.shape - if n_subjects <= 1: + if n_subjects <= 1 and not ignore_nsubjects: raise ValueError("The number of subjects should be greater than 1") n_timeframes_list = [None] * n_sessions @@ -337,26 +305,26 @@ def _check_shapes(shapes, n_components=None, atlas_shape=None): for n in range(n_subjects): for m in range(n_sessions): if n_timeframes_list[m] is None: - n_timeframes_list[m] = shapes[m, n, 1] + n_timeframes_list[m] = shapes[n, m, 1] if n_voxels is None: n_voxels = shapes[m, n, 0] - if n_timeframes_list[m] != shapes[m, n, 1]: + if n_timeframes_list[m] != shapes[n, m, 1]: raise ValueError("Subject %i Session %i does not have the " "same number of timeframes " "as Subject %i Session %i" % (n, m, 0, m)) - if n_voxels != shapes[m, n, 0]: + if n_voxels != shapes[n, m, 0]: raise ValueError("Subject %i Session %i" " does not have the same number of voxels as " "Subject %i Session %i." % (n, m, 0, 0)) - _check_shapes_components(n_components, np.sum(n_timeframes_list)) - _check_shapes_atlas(n_components, n_voxels, atlas_shape) + _check_shapes_atlas_compatibility(n_voxels, np.sum(n_timeframes_list), + n_components, atlas_shape) -def check_atlas(atlas): +def check_atlas(atlas, n_components=None): """ Check input atlas Parameters @@ -370,6 +338,9 @@ def check_atlas(atlas): and expected shape is (n_voxels,) for a deterministic atlas and (n_supervoxels, n_voxels) for a probabilistic atlas. + n_components : int + Number of timecourses of the shared coordinates + Returns ------- shape : array or None @@ -385,22 +356,40 @@ def check_atlas(atlas): type(atlas)) if isinstance(atlas, np.ndarray): - return atlas.shape + shape = atlas.shape else: shape = get_shape(atlas) - if len(shape) == 1: - # We have a deterministic atlas - n_voxels = atlas.shape[0] - n_supervoxels = len(np.unique(np.load(atlas))) - 1 - return (n_supervoxels, n_voxels) - elif len(shape) == 2: - return shape - else: - raise ValueError("Atlas has %i axes. It should have either 0 or 1 \ - axes." % len(shape)) + + if len(shape) == 1: + # We have a deterministic atlas + atlas_array = safe_load(atlas) + n_voxels = atlas_array.shape[0] + n_supervoxels = len(np.unique(atlas_array)) - 1 + shape = (n_supervoxels, n_voxels) + elif len(shape) != 2: + raise ValueError( + "Atlas has %i axes. It should have either 1 or 2 axes." % + len(shape)) + + n_supervoxels, n_voxels = shape + + if n_supervoxels > n_voxels: + raise ValueError("Number of regions in the atlas is bigger than " + "the number of voxels (%i > %i)" % + (n_supervoxels, n_voxels)) + + if n_components is not None: + if n_supervoxels < n_components: + raise ValueError("Number of regions in the atlas is " + "lower than the number of components " + "(%i < %i)" % (n_supervoxels, n_components)) + return shape -def check_imgs(imgs, n_components=None, atlas_shape=None): +def check_imgs(imgs, + n_components=None, + atlas_shape=None, + ignore_nsubjects=False): """ Check input images @@ -447,20 +436,29 @@ def check_imgs(imgs, n_components=None, atlas_shape=None): reshaped_input = True else: raise ValueError( - "since imgs is a list it should be a list of list of array or \ - a list of array but imgs[0] as type %s" % type(imgs[0])) + "Since imgs is a list, it should be a list of list " + "of arrays or a list of arrays but imgs[0] has type %s" % + type(imgs[0])) elif isinstance(imgs, np.ndarray): shapes = _check_imgs_array(imgs) else: raise ValueError( - "imgs should either be a list of an array but has type" % + "Input imgs should either be a list or an array but has type %s" % type(imgs)) - _check_shapes(shapes, n_components, atlas_shape) + _check_shapes(shapes, n_components, atlas_shape, ignore_nsubjects) return reshaped_input, new_imgs, shapes +def check_indexes(indexes, name): + if not (indexes is None or isinstance(indexes, list) + or isinstance(indexes, np.ndarray)): + raise ValueError( + "%s should be either a list, an array or None but received type %s" + % (name, type(indexes))) + + def _check_shared_response_list_of_list(shared_response, n_components, input_shapes): @@ -469,15 +467,17 @@ def _check_shared_response_list_of_list(shared_response, n_components, n_sessions = None for i in range(len(shared_response)): if not isinstance(shared_response[i], list): - raise ValueError("shared_response[0] is a list but\ - shared_response[%i] is not a list this is incompatible." % i) - assert_non_empty_list(shared_response[i], "shared_response[i]" % i) + raise ValueError("shared_response[0] is a list but " + "shared_response[%i] is not a list " + "this is incompatible." % i) + assert_non_empty_list(shared_response[i], "shared_response[%i]" % i) if n_sessions is None: n_sessions = len(shared_response[i]) elif n_sessions != len(shared_response[i]): raise ValueError( - "shared_response[%i] has len %i whereas \ - shared_response[0] has len %i. They should have same length" % + "shared_response[%i] has len %i whereas " + "shared_response[0] has len %i. They should " + "have same length" % (i, len(shared_response[i]), len(shared_response[0]))) for j in range(len(shared_response[i])): assert_array_2axis(shared_response[i][j], @@ -496,16 +496,18 @@ def _check_shared_response_list_sessions(shared_response, n_components, if input_shapes is not None: if shared_response[j].shape[1] != input_shapes[0][j][1]: raise ValueError( - "Number of timeframes in input images during \ - session %i does not match the number of \ - timeframes during session %i of shared_response (%i != %i)" % + "Number of timeframes in input images during " + "session %i does not match the number of " + "timeframes during session %i " + "of shared_response (%i != %i)" % (j, j, shared_response[j].shape[1], input_shapes[0, j, 1])) if n_components is not None: if shared_response[j].shape[0] != n_components: raise ValueError( - "Number of components in \ - shared_response during session %i is different than\ - the number of components of the model (%i != %i)" % + "Number of components in " + "shared_response during session %i is " + "different than " + "the number of components of the model (%i != %i)" % (j, shared_response[j].shape[0], n_components)) return shared_response @@ -521,8 +523,15 @@ def _check_shared_response_list_subjects(shared_response, n_components, def _check_shared_response_array(shared_response, n_components, input_shapes): assert_array_2axis(shared_response, "shared_response") + if input_shapes is None: + new_input_shapes = None + else: + n_subjects, n_sessions, _ = input_shapes.shape + new_input_shapes = np.zeros((n_subjects, 1, 2)) + new_input_shapes[:, 0, 0] = input_shapes[:, 0, 0] + new_input_shapes[:, 0, 1] = np.sum(input_shapes[:, :, 1], axis=1) return _check_shared_response_list_sessions([shared_response], - n_components, input_shapes) + n_components, new_input_shapes) def check_shared_response(shared_response, @@ -546,9 +555,9 @@ def check_shared_response(shared_response, assert_non_empty_list(shared_response, "shared_response") if isinstance(shared_response[0], list): if aggregate == "mean": - raise ValueError("self.aggregate has value 'mean' but\ - shared response is a list of list. This is\ - incompatible") + raise ValueError("self.aggregate has value 'mean' but " + "shared response is a list of list. This is " + "incompatible") return False, _check_shared_response_list_of_list( shared_response, n_components, input_shapes) elif isinstance(shared_response[0], np.ndarray): @@ -559,15 +568,16 @@ def check_shared_response(shared_response, return True, _check_shared_response_list_subjects( shared_response, n_components, input_shapes) else: - raise ValueError("shared_response is a list but\ - shared_response[0] is neither a list or an array.\ - This is invalid.") + raise ValueError("shared_response is a list but " + "shared_response[0] is neither a list " + "or an array. This is invalid.") elif isinstance(shared_response, np.ndarray): return True, _check_shared_response_array(shared_response, n_components, input_shapes) else: - raise ValueError("shared_response should be either\ - a list or an array but is of type %s" % type(shared_response)) + raise ValueError("shared_response should be either " + "a list or an array but is of type %s" % + type(shared_response)) def create_temp_dir(temp_dir): @@ -582,7 +592,7 @@ def create_temp_dir(temp_dir): else: raise ValueError("Path %s already exists. " "When a model is used, filesystem should be cleaned " - "by using the .clean() method") + "by using the .clean() method" % temp_dir) def reduce_data_single(subject_index, @@ -663,7 +673,7 @@ def reduce_data_single(subject_index, reduced_data = data if low_ram: - name = hashlib.md5(img.encode()).hexdigest() + name = safe_encode(img) path = os.path.join(temp_dir, "reduced_data_" + name) np.save(path, reduced_data) return path + ".npy" @@ -751,7 +761,7 @@ def reduce_data(imgs, atlas, n_jobs=1, low_ram=False, temp_dir=None): reduced_data_list = Parallel(n_jobs=n_jobs)( delayed(reduce_data_single)(i, j, - imgs[i, j], + imgs[i][j], atlas=A, inv_atlas=A_inv, low_ram=low_ram, @@ -812,7 +822,6 @@ def _reduced_space_compute_shared_response(reduced_data_list, """ n_subjects, n_sessions = reduced_data_list.shape[:2] - low_ram = is_low_ram(reduced_data_list[0, 0]) s = [None] * n_sessions @@ -820,11 +829,7 @@ def _reduced_space_compute_shared_response(reduced_data_list, # timeframes in a given session for n in range(n_subjects): for m in range(n_sessions): - if low_ram: - data_nm = np.load(reduced_data_list[n, m]) - else: - data_nm = reduced_data_list[n, m] - + data_nm = safe_load(reduced_data_list[n][m]) n_timeframes, n_supervoxels = data_nm.shape if reduced_basis_list is None: @@ -861,8 +866,8 @@ def _compute_and_save_corr_mat(img, shared_response, temp_dir): shared_response : array, shape=[n_timeframes, n_components] shared response """ - data = np.load(img) - name = hashlib.md5(img.encode()).hexdigest() + data = safe_load(img).T + name = safe_encode(img) path = os.path.join(temp_dir, "corr_mat_" + name) np.save(path, shared_response.T.dot(data)) @@ -900,7 +905,7 @@ def _compute_and_save_subject_basis(subject_number, sessions, temp_dir): """ corr_mat = None for session in sessions: - name = hashlib.md5(session.encode()).hexdigest() + name = safe_encode(session) path = os.path.join(temp_dir, "corr_mat_" + name + ".npy") if corr_mat is None: corr_mat = np.load(path) @@ -1084,7 +1089,7 @@ def _compute_basis_subject_online(sessions, shared_response_list): basis_i = None i = 0 for session in sessions: - data = np.load(session) + data = safe_load(session).T if basis_i is None: basis_i = shared_response_list[i].T.dot(data) else: @@ -1216,13 +1221,13 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, shared_response_list = Parallel(n_jobs=n_jobs)( delayed(_compute_shared_response_online_single) - ([imgs[i, j] for i in range(n_subjects)], basis_list, temp_dir, + ([imgs[i][j] for i in range(n_subjects)], basis_list, temp_dir, subjects_indexes, aggregate) for j in range(n_sessions)) if aggregate is None: shared_response_list = [[ - shared_response_list[j][i].T for i in range(n_subjects) - ] for j in range(n_sessions)] + shared_response_list[j][i].T for j in range(n_sessions) + ] for i in range(n_subjects)] if aggregate == "mean": shared_response_list = [ @@ -1342,11 +1347,11 @@ def __init__(self, self.low_ram = False if temp_dir is not None: - self.temp_dir = os.path.join(temp_dir, "fastsrm") + self.temp_dir = os.path.join(temp_dir, + "fastsrm" + str(uuid.uuid4())) self.low_ram = low_ram def clean(self): - # TODO: Check that this does erase fastsrm file """This erases temporary files and basis_list attribute to free memory. This method should be called when fitted model is not needed anymore. """ @@ -1355,8 +1360,7 @@ def clean(self): for root, dirs, files in os.walk(self.temp_dir, topdown=False): for name in files: os.remove(os.path.join(root, name)) - for name in dirs: - os.rmdir(os.path.join(root, name)) + os.rmdir(self.temp_dir) if self.basis_list is not None: self.basis_list is None @@ -1391,17 +1395,13 @@ def fit(self, imgs): Returns the instance itself. Contains attributes listed at the object level. """ - atlas_shape = check_atlas(self.atlas) + atlas_shape = check_atlas(self.atlas, self.n_components) reshaped_input, imgs, shapes = check_imgs( imgs, n_components=self.n_components, atlas_shape=atlas_shape) self.clean() create_temp_dir(self.temp_dir) if self.verbose is True: - n_subjects, n_sessions = imgs.shape - logger.info( - "Fitting using %i subjects and %i sessions per subject" % - (n_subjects, n_sessions)) logger.info("[FastSRM.fit] Reducing data") reduced_data = reduce_data(imgs, @@ -1444,9 +1444,8 @@ def fit(self, imgs): else: Parallel(n_jobs=self.n_jobs)( delayed(_compute_and_save_corr_mat)( - subject, shared_response_list[m], self.temp_dir) - for m, subjects in enumerate(imgs.T) - for subject in subjects) + imgs[i][j], shared_response_list[j], self.temp_dir) + for j in range(len(imgs[0])) for i in range(len(imgs))) basis = Parallel(n_jobs=self.n_jobs)( delayed(_compute_and_save_subject_basis)(i, sessions, @@ -1549,10 +1548,10 @@ def transform(self, imgs, subjects_indexes=None): if self.basis_list is None: raise NotFittedError("The model fit has not been run yet.") - atlas_shape = check_atlas(self.atlas) + atlas_shape = check_atlas(self.atlas, self.n_components) reshaped_input, imgs, shapes = check_imgs( imgs, n_components=self.n_components, atlas_shape=atlas_shape) - + check_indexes(subjects_indexes, "subjects_indexes") if subjects_indexes is None: subjects_indexes = np.arange(len(imgs)) else: @@ -1560,10 +1559,12 @@ def transform(self, imgs, subjects_indexes=None): # Transform specific checks if len(subjects_indexes) < len(imgs): - raise ValueError("Input data imgs has len %i whereas \ - subject_indexes has len %i. The number of basis used to compute \ - the shared response should be equal to the number of subjects in \ - imgs" % (len(imgs), len(subjects_indexes))) + raise ValueError("Input data imgs has len %i whereas " + "subject_indexes has len %i. " + "The number of basis used to compute " + "the shared response should be equal " + "to the number of subjects in imgs" % + (len(imgs), len(subjects_indexes))) assert_valid_index(subjects_indexes, len(self.basis_list), "subjects_indexes") @@ -1620,10 +1621,13 @@ def inverse_transform( Returns ------- - reconstructed_data: array - shape=[len(subjects_indexes), len(sessions_indexes), - n_timeframes, n_voxels] - Reconstructed data for chosen subjects and sessions + reconstructed_data: list of list of arrays or list of arrays + if list of list element i, j is the reconstructed data + for subject subjects_indexes[i] and session sessions_indexes[j] + as an np array of shape n_voxels, n_timeframes + if list element i is the reconstructed data + for subject subject_indexes[i] + as an np array of shape n_voxels, n_timeframes """ added_session, shared = check_shared_response( shared_response, self.aggregate, n_components=self.n_components) @@ -1631,7 +1635,10 @@ def inverse_transform( n_sessions = len(shared) for j in range(n_sessions): - assert_array_2axis(shared[j]) + assert_array_2axis(shared[j], "shared_response[%i]" % j) + + check_indexes(subjects_indexes, "subjects_indexes") + check_indexes(sessions_indexes, "sessions_indexes") if subjects_indexes is None: subjects_indexes = np.arange(n_subjects) @@ -1656,8 +1663,8 @@ def inverse_transform( else: for j in sessions_indexes: data_.append(basis_i.T.dot(shared[j])) - data.append(np.array(data_)) - return np.array(data) + data.append(data_) + return data def add_subjects(self, imgs, shared_response): """ Add subjects to the current fit @@ -1695,9 +1702,12 @@ def add_subjects(self, imgs, shared_response): element i, j is the projection of data of subject i collected during session j in shared space. """ - atlas_shape = check_atlas(self.atlas) + atlas_shape = check_atlas(self.atlas, self.n_components) reshaped_input, imgs, shapes = check_imgs( - imgs, n_components=self.n_components, atlas_shape=atlas_shape) + imgs, + n_components=self.n_components, + atlas_shape=atlas_shape, + ignore_nsubjects=True) _, shared_response_list = check_shared_response( shared_response, @@ -1719,7 +1729,8 @@ def add_subjects(self, imgs, shared_response): if self.temp_dir is None: basis.append(basis_i) else: - path = os.path.join(self.temp_dir, "basis_%i" % i) + path = os.path.join( + self.temp_dir, "basis_%i" % (len(self.basis_list) + i)) np.save(path, basis_i) basis.append(path + ".npy") del basis_i @@ -1731,9 +1742,8 @@ def add_subjects(self, imgs, shared_response): else: Parallel(n_jobs=self.n_jobs)( delayed(_compute_and_save_corr_mat)( - subject, shared_response_list[m], self.temp_dir) - for m, subjects in enumerate(imgs.T) - for subject in subjects) + imgs[i][j], shared_response_list[j], self.temp_dir) + for j in range(len(imgs[0])) for i in range(len(imgs))) basis = Parallel(n_jobs=self.n_jobs)( delayed(_compute_and_save_subject_basis)( diff --git a/tests/funcalign/test_fastsrm.py b/tests/funcalign/test_fastsrm.py index 49918e96d..95101570e 100644 --- a/tests/funcalign/test_fastsrm.py +++ b/tests/funcalign/test_fastsrm.py @@ -1,26 +1,15 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. import numpy as np import tempfile -from brainiak.funcalign.fastsrm import FastSRM, reduce_data, \ - _reduced_space_compute_shared_response +from brainiak.funcalign.fastsrm import FastSRM, reduce_data,\ + _reduced_space_compute_shared_response, create_temp_dir,\ + check_atlas from brainiak.funcalign.fastsrm import _compute_basis_subject_online, fast_srm from brainiak.funcalign.fastsrm import _compute_and_save_corr_mat from brainiak.funcalign.fastsrm import _compute_and_save_subject_basis +from brainiak.funcalign.fastsrm import check_shared_response from sklearn.exceptions import NotFittedError -from brainiak.funcalign.fastsrm import check_imgs, check_shapes, is_low_ram -from brainiak.funcalign.fastsrm import reduce_data_single +from brainiak.funcalign.fastsrm import check_imgs +from brainiak.funcalign.fastsrm import safe_load import os import pytest from numpy.testing import assert_array_almost_equal @@ -51,17 +40,24 @@ def to_path(X, dirpath): return np.array(paths) -def generate_data(n_voxels, n_timeframes, n_subjects, n_components, - datadir, noise_level=0.1): +def generate_data(n_voxels, + n_timeframes, + n_subjects, + n_components, + datadir, + noise_level=0.1, + input_format="array"): n_sessions = len(n_timeframes) cumsum_timeframes = np.cumsum([0] + n_timeframes) - slices_timeframes = [slice(cumsum_timeframes[i], cumsum_timeframes[i + 1]) - for i in range(n_sessions)] + slices_timeframes = [ + slice(cumsum_timeframes[i], cumsum_timeframes[i + 1]) + for i in range(n_sessions) + ] # Create a Shared response S with K = 3 theta = np.linspace(-4 * np.pi, 4 * np.pi, int(np.sum(n_timeframes))) z = np.linspace(-2, 2, int(np.sum(n_timeframes))) - r = z ** 2 + 1 + r = z**2 + 1 x = r * np.sin(theta) y = r * np.cos(theta) @@ -77,19 +73,32 @@ def generate_data(n_voxels, n_timeframes, n_subjects, n_components, for session in range(n_sessions): S_s = S[:, slices_timeframes[session]] S_s = S_s - np.mean(S_s, axis=1, keepdims=True) - noise = noise_level * np.random.random((n_voxels, - n_timeframes[session])) + noise = noise_level * np.random.random( + (n_voxels, n_timeframes[session])) noise = noise - np.mean(noise, axis=1, keepdims=True) data = Q.dot(S_s) + noise - X_.append(data.T) + X_.append(data) X.append(X_) # create paths such that paths[i, j] contains data # of subject i during session j paths = to_path(X, datadir) - S = [(S[:, s] - np.mean(S[:, s], axis=1, keepdims=True)).T + S = [(S[:, s] - np.mean(S[:, s], axis=1, keepdims=True)) for s in slices_timeframes] - return paths, W, S + + if input_format == "array": + return paths, W, S + + elif input_format == "list_of_list": + return X, W, S + + elif input_format == "list_of_array": + return [ + np.concatenate([X[i][j].T for j in range(n_sessions)]).T + for i in range(n_subjects) + ], W, S + else: + raise ValueError("Wrong input_format") def test_generated_data(): @@ -104,50 +113,277 @@ def test_generated_data(): n_sessions = len(n_timeframes) np.random.seed(0) - paths, _, _ = generate_data(n_voxels, n_timeframes, n_subjects, + paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir) # Test if generated data has the good shape for subject in range(n_subjects): for session in range(n_sessions): - assert (np.load(paths[subject, session]).shape == - (n_timeframes[session], n_voxels)) + assert (np.load( + paths[subject, session]).shape == (n_voxels, + n_timeframes[session])) + + # Test if generated basis have good shape + assert len(W) == n_subjects + for w in W: + assert w.shape == (n_components, n_voxels) + + assert len(S) == n_sessions + for j, s in enumerate(S): + assert s.shape == (n_components, n_timeframes[j]) + + +def test_bad_aggregate(): + with pytest.raises(ValueError, + match="aggregate can have only value mean or None"): + FastSRM(aggregate="invalid") + +def test_check_atlas(): + assert check_atlas(None) is None + with pytest.raises(ValueError, + match=("Atlas is stored using type " + "which is neither np.ndarray or str")): + check_atlas([]) + + A = np.random.rand(100, 1000) + assert check_atlas(A) == (100, 1000) -def test_reduce_data_bad_input(): with tempfile.TemporaryDirectory() as datadir: - n_timeframes = (250, 250) - # We authorize different timeframes for different sessions - # but they should be the same across subject - n_voxels = 100 + f = os.path.join(datadir, "atlas") + np.save(f, A) + assert check_atlas(f + ".npy") == (100, 1000) - np.random.seed(0) + A = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 5]) + assert check_atlas(A) == (5, 11) + + with tempfile.TemporaryDirectory() as datadir: + f = os.path.join(datadir, "atlas") + np.save(f, A) + assert check_atlas(f + ".npy") == (5, 11) - # First let us test how reduce_data reacts to bad input - X = [[np.random.rand(n_timeframes[0], n_voxels)]] - X = to_path(X, datadir)[0, 0] + with pytest.raises(ValueError, + match=("Atlas has 3 axes. It should have either " + "1 or 2 axes.")): + check_atlas(np.random.rand(5, 1, 2)) - # atlas is not an nd array - with pytest.raises(ValueError): - reduce_data(X, atlas="path_to_atlas") + with pytest.raises(ValueError, + match=(r"Number of regions in the atlas is lower than " + r"the number of components \(3 < 5\)")): + check_atlas(np.random.rand(3, 10), n_components=5) - # atlas is not an nd array of dimension > 2 - with pytest.raises(ValueError): - reduce_data(X, atlas=np.random.rand(2, 3, 4)) + with pytest.raises(ValueError, + match=(r"Number of regions in the atlas is bigger than " + r"the number of voxels \(5 > 2\)")): + check_atlas(np.random.rand(5, 2)) - # both atlases are None - with pytest.raises(ValueError): - reduce_data_single(X, atlas=None, inv_atlas=None) - # deterministic atlas and data are incompatible - with pytest.raises(ValueError): - reduce_data_single(X, atlas=np.random.rand(n_voxels - 1)) +empty_list_error = "%s is a list of length 0 which is not valid" +array_type_error = ("%s should be of type np.ndarray but is of type %s") +array_2axis_error = ("%s must have exactly 2 axes but has %i axes") - # probabilistic atlas and data are incompatible - with pytest.raises(ValueError): - reduce_data_single(X, - inv_atlas=np.random.rand(n_voxels - 1, - n_timeframes[0])) + +def test_check_imgs(): + with pytest.raises( + ValueError, + match=(r"Since imgs is a list, it should be a list of list " + r"of arrays or a list " + r"of arrays but imgs\[0\] has type ")): + check_imgs(["bla"]) + + with pytest.raises( + ValueError, + match=("Input imgs should either be a list or an array but " + "has type ")): + check_imgs("bla") + + with pytest.raises(ValueError, match=empty_list_error % "imgs"): + check_imgs([]) + + with pytest.raises( + ValueError, + match=r"imgs\[1\] has type whereas imgs\[0\] has " + "type . This is inconsistent."): + check_imgs([0, "bla"]) + + with pytest.raises(ValueError, match=empty_list_error % r"imgs\[0\]"): + check_imgs([[]]) + + with pytest.raises( + ValueError, + match=(r"imgs\[1\] has length 1 whereas imgs\[0\] has length 2." + " All subjects should have the same number of sessions.")): + check_imgs([["a", "a"], ["a"]]) + + with pytest.raises(ValueError, + match=array_type_error % + (r"imgs\[0\]\[0\]", r"")): + check_imgs([["bka"]]) + + with pytest.raises(ValueError, + match=array_2axis_error % (r"imgs\[0\]\[0\]", 1)): + check_imgs([[np.random.rand(5)]]) + + with pytest.raises(ValueError, + match=array_2axis_error % (r"imgs\[0\]", 1)): + check_imgs([np.random.rand(5)]) + + with pytest.raises(ValueError, + match=(r"imgs\[0, 0\] is stored using type " + " which is not a str")): + check_imgs(np.random.rand(5, 3)) + + with pytest.raises(ValueError, match=array_2axis_error % (r"imgs", 1)): + check_imgs(np.random.rand(5)) + + with pytest.raises( + ValueError, + match=("The number of subjects should be greater than 1")): + check_imgs([np.random.rand(5, 3)]) + + with pytest.raises( + ValueError, + match=("Subject 1 Session 0 does not have the same number " + "of timeframes as Subject 0 Session 0")): + check_imgs([np.random.rand(10, 5), np.random.rand(10, 10)]) + + with pytest.raises( + ValueError, + match=("Subject 1 Session 0 does not have the same number " + "of voxels as Subject 0 Session 0")): + check_imgs([np.random.rand(10, 5), np.random.rand(20, 5)]) + + with pytest.raises( + ValueError, + match=("Total number of timeframes is shorter than number " + r"of components \(5 < 8\)")): + check_imgs([np.random.rand(10, 5), + np.random.rand(10, 5)], + n_components=8) + + with pytest.raises( + ValueError, + match=("Number of voxels in the atlas is not the same as " + r"the number of voxels in input data \(11 != 10\)")): + check_imgs([np.random.rand(10, 5), + np.random.rand(10, 5)], + n_components=3, + atlas_shape=(8, 11)) + + +def test_check_shared(): + n_subjects = 2 + n_sessions = 2 + input_shapes = np.zeros((n_subjects, n_sessions, 2)) + input_shapes[0, 0, 0] = 10 + input_shapes[0, 0, 1] = 3 + input_shapes[0, 1, 0] = 10 + input_shapes[0, 1, 1] = 2 + input_shapes[1, 0, 0] = 10 + input_shapes[1, 0, 1] = 3 + input_shapes[1, 1, 0] = 10 + input_shapes[1, 1, 1] = 2 + + shared_list_list = [[ + np.array([[1, 2, 3], [4, 5, 6]]), + np.array([[1, 2], [4, 5]]), + ], [ + np.array([[2, 3, 4], [5, 6, 7]]), + np.array([[2, 3], [5, 6]]), + ]] + + shared_list_subjects = [ + np.array([[1, 2, 3, 1, 2], [4, 5, 6, 4, 5]]), + np.array([[2, 3, 4, 2, 3], [5, 6, 7, 5, 6]]) + ] + + shared_list_sessions = [ + np.array([[1.5, 2.5, 3.5], [4.5, 5.5, 6.5]]), + np.array([[1.5, 2.5], [4.5, 5.5]]), + ] + + shared_array = np.array([[1.5, 2.5, 3.5, 1.5, 2.5], + [4.5, 5.5, 6.5, 4.5, 5.5]]) + + with pytest.raises(ValueError, + match=(r"shared_response should be either a list or an " + "array but is of type ")): + check_shared_response("bla") + + with pytest.raises( + ValueError, + match=(r"shared_response is a list but shared_response\[0\] " + "is neither a list or an array. This is invalid.")): + check_shared_response(["bla", "bli"]) + + with pytest.raises( + ValueError, + match=(r"shared_response\[0\] is a list but shared_response\[1\] " + "is not a list this is incompatible")): + check_shared_response( + [[np.random.rand(2, 2)], np.array([1])], aggregate=None) + + with pytest.raises(ValueError, + match=(r"shared_response\[1\] has len 1 whereas " + r"shared_response\[0\] has len 2. They should " + "have same len")): + check_shared_response([[np.random.rand(2, 2), + np.random.rand(2, 2)], [np.random.rand(2, 2)]], + aggregate=None) + + with pytest.raises( + ValueError, + match=('Number of timeframes in input images during session 0 ' + 'does not match the number of timeframes during session ' + r'0 of shared_response \(2 != 3\)')): + check_shared_response( + [np.random.rand(2, 2), np.random.rand(2, 2)], + aggregate="mean", + input_shapes=input_shapes) + + with pytest.raises(ValueError, + match=("Number of components in shared_response " + "during session 0 is different than " + "the number of components of the " + r"model \(2 != 4\)")): + check_shared_response(np.random.rand(2, 10), n_components=4) + + with pytest.raises(ValueError, + match=("self.aggregate has value 'mean' but shared " + "response is a list of list. " + "This is incompatible")): + added_session, reshaped_shared = check_shared_response( + shared_list_list, + aggregate="mean", + n_components=2, + input_shapes=input_shapes) + + added_session, reshaped_shared = check_shared_response( + shared_list_subjects, + aggregate=None, + n_components=2, + input_shapes=input_shapes) + assert added_session + assert_array_almost_equal(np.array(reshaped_shared), + shared_array.reshape(1, 2, 5)) + + added_session, reshaped_shared = check_shared_response( + shared_list_sessions, + aggregate="mean", + n_components=2, + input_shapes=input_shapes) + assert not added_session + for j in range(len(reshaped_shared)): + assert_array_almost_equal(reshaped_shared[j], shared_list_sessions[j]) + + added_session, reshaped_shared = check_shared_response( + shared_array, + aggregate="mean", + n_components=2, + input_shapes=input_shapes) + assert added_session + assert_array_almost_equal(np.array(reshaped_shared), + shared_array.reshape(1, 2, 5)) def test_reduce_data_dummyatlases(): @@ -167,21 +403,26 @@ def test_reduce_data_dummyatlases(): # test atlas that reduces nothing atlas = np.arange(1, n_voxels + 1) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + data = reduce_data(paths, + atlas=atlas, + n_jobs=n_jobs, low_ram=False) for i in range(n_subjects): for j in range(n_sessions): - assert_array_almost_equal(data[i, j], np.load(paths[i, j])) + assert_array_almost_equal(data[i, j].T, + np.load(paths[i, j])) # test atlas that reduces everything atlas = np.ones(n_voxels) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + data = reduce_data(paths, + atlas=atlas, + n_jobs=n_jobs, low_ram=False) for i in range(n_subjects): for j in range(n_sessions): - assert_array_almost_equal(data[i, j].flatten(), - np.mean(np.load(paths[i, j]), - axis=1)) + assert_array_almost_equal( + data[i, j].T.flatten(), + np.mean(np.load(paths[i, j]), axis=0)) def test_reduce_data_outputshapes(): @@ -203,15 +444,16 @@ def test_reduce_data_outputshapes(): # Test if reduced data has the good shape # probabilistic atlas atlas = np.random.rand(n_supervoxels, n_voxels) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, + data = reduce_data(paths, + atlas=atlas, + n_jobs=n_jobs, low_ram=False, temp_dir=None) for subject in range(n_subjects): for session in range(n_sessions): - assert data[subject, - session].shape == (n_timeframes[session], - n_supervoxels) + assert data[subject, session].shape == ( + n_timeframes[session], n_supervoxels) # deterministic atlas det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) @@ -220,13 +462,16 @@ def test_reduce_data_outputshapes(): det_atlas = np.round(np.random.rand(n_voxels) * n_supervoxels) n_unique = len(np.unique(det_atlas)[1:]) - data = reduce_data(paths, atlas=det_atlas, n_jobs=n_jobs, - low_ram=True, temp_dir=datadir) + data = reduce_data(paths, + atlas=det_atlas, + n_jobs=n_jobs, + low_ram=True, + temp_dir=datadir) for subject in range(n_subjects): for session in range(n_sessions): - assert (np.load(data[subject, session]).shape - == (n_timeframes[session], n_supervoxels)) + assert (np.load(data[subject, session]).shape == ( + n_timeframes[session], n_supervoxels)) def test_reduced_data_srm(): @@ -245,24 +490,12 @@ def test_reduced_data_srm(): paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, n_components, datadir, 0) - # Test if generated data has the good shape - for subject in range(n_subjects): - for session in range(n_sessions): - assert (np.load(paths[subject, session]).shape - == (n_timeframes[session], n_voxels)) - - # Test if generated basis have good shape - assert len(W) == n_subjects - for w in W: - assert w.shape == (n_components, n_voxels) - - assert len(S) == n_sessions - for j, s in enumerate(S): - assert s.shape == (n_timeframes[j], n_components) - atlas = np.arange(1, n_voxels + 1) - data = reduce_data(paths, atlas=atlas, n_jobs=n_jobs, - low_ram=False, temp_dir=None) + data = reduce_data(paths, + atlas=atlas, + n_jobs=n_jobs, + low_ram=False, + temp_dir=None) # Test if shared response has the good shape shared_response_list = \ @@ -274,12 +507,13 @@ def test_reduced_data_srm(): assert len(shared_response_list) == n_sessions for session in range(n_sessions): - assert (shared_response_list[session].shape == - (n_timeframes[session], n_components)) + assert (shared_response_list[session].shape == ( + n_timeframes[session], n_components)) # Test basis from shared response for i, sessions in enumerate(paths): - basis = _compute_basis_subject_online(sessions, S) + basis = _compute_basis_subject_online( + sessions, [S[k].T for k in range(len(S))]) # test shape assert basis.shape == (n_components, n_voxels) # test orthogonality @@ -289,24 +523,14 @@ def test_reduced_data_srm(): # Test reduced_data_shared_response shared_response_list = _reduced_space_compute_shared_response( - data, - reduced_basis_list=W, - n_components=n_components - ) + data, reduced_basis_list=W, n_components=n_components) for session in range(n_sessions): - S_real = np.mean([data[i, session].dot(W[i].T) for i in - range(n_subjects)], axis=0) + S_real = np.mean( + [data[i, session].dot(W[i].T) for i in range(n_subjects)], + axis=0) assert_array_almost_equal(shared_response_list[session], S_real) assert_array_almost_equal(shared_response_list[session], - S[session]) - - # Test fast_srm for reduced_data - # test first what happens with bad input - with pytest.raises(ValueError): - fast_srm([["path"]]) - - with pytest.raises(ValueError): - fast_srm(np.array([[{"bla": 5}]])) + S[session].T) shared_response_list = fast_srm(data, n_components=n_components) @@ -315,7 +539,7 @@ def test_reduced_data_srm(): shared_response_list) for j, session in enumerate(sessions): assert_array_almost_equal(shared_response_list[j].dot(basis), - np.load(paths[i, j])) + np.load(paths[i, j]).T) def test_compute_and_save(): @@ -331,17 +555,10 @@ def test_compute_and_save(): for m, subjects in enumerate(paths.T): for subject in subjects: - _compute_and_save_corr_mat( - subject, - S[m], - datadir - ) + _compute_and_save_corr_mat(subject, S[m].T, datadir) for i, sessions in enumerate(paths): - basis = _compute_and_save_subject_basis(i, - sessions, - datadir - ) + basis = _compute_and_save_subject_basis(i, sessions, datadir) assert_array_almost_equal(np.load(basis), W[i]) @@ -357,7 +574,6 @@ def test_fastsrm_class(): n_timeframes = [250, 245] n_subjects = 5 n_components = 3 # number of components used for SRM model - n_sessions = len(n_timeframes) np.random.seed(0) paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, @@ -377,176 +593,234 @@ def test_fastsrm_class(): srm.transform(paths) srm.fit(paths) - basis = srm.basis_list - print(basis) - shared_response = srm.transform(paths) - - shared_response_fittransform = srm.fit_transform(paths) - for j in range(n_sessions): - assert_array_almost_equal(shared_response_fittransform[j], - shared_response[j]) - - for i in range(n_subjects): - for j in range(n_sessions): - basis_i = np.load(basis[i]) - assert_array_almost_equal(shared_response[j].dot(basis_i), - np.load(paths[i, j])) - - shared_response_partial = srm.transform( - paths[np.arange(1, 5)], - subjects_indexes=list(range(1, 5)) - ) - - for j in range(n_sessions): - assert_array_almost_equal(shared_response_partial[j], - shared_response[j]) - - reconstructed_data = srm.inverse_transform( - shared_response, - subjects_indexes=[0, 2], - sessions_indexes=[1]) - - for i, ii in enumerate([0, 2]): - for j, jj in enumerate([1]): - assert_array_almost_equal(reconstructed_data[i, j], - np.load(paths[ii, jj])) - - # Test bad input shapes n_timeframes varies across sessions - X = [[np.random.rand(n_timeframes[0], n_voxels), - np.random.rand(n_timeframes[1], n_voxels)], - [np.random.rand(n_timeframes[1], n_voxels), - np.random.rand(n_timeframes[0], n_voxels)]] - - with pytest.raises(ValueError): - srm.fit(to_path(X, datadir)) - - -# Test SRM class without temp_dir -def test_fastsrm_class_in_memory(): - with tempfile.TemporaryDirectory() as datadir: - np.random.seed(0) - - # We authorize different timeframes for different sessions - # but they should be the same across subject - n_voxels = 100 - n_timeframes = [250, 245] - n_subjects = 5 - n_components = 3 # number of components used for SRM model - - np.random.seed(0) - paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, - n_components, datadir, 0) - - atlas = np.arange(1, n_voxels + 1) - - for n_jobs in [1, 2]: - srm = FastSRM(atlas=atlas, - n_components=n_components, - n_iter=10, - temp_dir=None, - low_ram=True, - verbose=True, - n_jobs=n_jobs) - - srm.fit(paths) - basis = srm.basis_list - print(basis) - shared_response = srm.transform(paths) - - # Reconstruct data using all indexes - reconstructed_data = srm.inverse_transform( - shared_response, - subjects_indexes=None, - sessions_indexes=None) - - for i in range(len(paths)): - for j in range(len(paths[i])): - assert_array_almost_equal(reconstructed_data[i, j], - np.load(paths[i, j])) + # An error can occur if temporary directory already exists + with pytest.raises(ValueError, + match=("Path %s already exists. When a model " + "is used, filesystem should be " + r"cleaned by using the .clean\(\) " + "method" % srm.temp_dir)): + # Error can occur if the filesystem is uncleaned + create_temp_dir(srm.temp_dir) + create_temp_dir(srm.temp_dir) + shared_response = srm.transform(paths) -def test_check_imgs(): + # Raise error when wrong index + with pytest.raises(ValueError, + match=("subjects_indexes should be either " + "a list, an array or None but " + "received type ")): + srm.transform(paths, subjects_indexes=1000) + + with pytest.raises(ValueError, + match=("subjects_indexes should be either " + "a list, an array or None but " + "received type ")): + srm.inverse_transform(shared_response, subjects_indexes=1000) + + with pytest.raises(ValueError, + match=("sessions_indexes should be either " + "a list, an array or None but " + "received type ")): + srm.inverse_transform(shared_response, sessions_indexes=1000) + + with pytest.raises(ValueError, + match=("Input data imgs has len 5 whereas " + "subject_indexes has len 1. " + "The number of basis used to compute " + "the shared response should be equal to " + "the number of subjects in imgs")): + srm.transform(paths, subjects_indexes=[0]) + + with pytest.raises(ValueError, + match=("Index 1 of subjects_indexes has value 8 " + "whereas value should be between 0 and 4")): + srm.transform(paths[:2], subjects_indexes=[0, 8]) + + with pytest.raises(ValueError, + match=("Index 1 of sessions_indexes has value 8 " + "whereas value should be between 0 and 1")): + srm.inverse_transform(shared_response, sessions_indexes=[0, 8]) + + # Check behavior of .clean + assert os.path.exists(srm.temp_dir) + srm.clean() + assert not os.path.exists(srm.temp_dir) + + +n_voxels = 10 +n_subjects = 5 +n_components = 3 # number of components used for SRM model + + +def apply_aggregate(shared_response, aggregate, input_format): + if aggregate is None: + if input_format == "list_of_array": + return [np.mean(shared_response, axis=0)] + else: + return [ + np.mean([ + shared_response[i][j] for i in range(len(shared_response)) + ], + axis=0) for j in range(len(shared_response[0])) + ] + else: + if input_format == "list_of_array": + return [shared_response] + else: + return shared_response + + +def apply_input_format(X, input_format): + if input_format == "array": + n_sessions = len(X[0]) + XX = [[np.load(X[i, j]) for j in range(len(X[i]))] + for i in range(len(X))] + elif input_format == "list_of_array": + XX = [[x] for x in X] + n_sessions = 1 + else: + XX = X + n_sessions = len(X[0]) + return XX, n_sessions + + +@pytest.mark.parametrize("input_format", + ["array", "list_of_list", "list_of_array"]) +@pytest.mark.parametrize("low_ram", [True, False]) +@pytest.mark.parametrize("tempdir", [True, False]) +@pytest.mark.parametrize( + "atlas", [None, np.arange(1, n_voxels + 1), + np.eye(n_voxels)]) +@pytest.mark.parametrize("n_jobs", [1, 2]) +@pytest.mark.parametrize("n_timeframes", [[25, 25], [25, 24]]) +@pytest.mark.parametrize("aggregate", ["mean", None]) +def test_fastsrm_class_correctness(input_format, low_ram, tempdir, atlas, + n_jobs, n_timeframes, aggregate): with tempfile.TemporaryDirectory() as datadir: np.random.seed(0) + X, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0, input_format) - # We authorize different timeframes for different sessions - # but they should be the same across subject - n_voxels = 100 - n_timeframes = [250, 245] - n_subjects = 5 - n_components = 3 # number of components used for SRM model + XX, n_sessions = apply_input_format(X, input_format) - np.random.seed(0) - paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, - n_components, datadir, 0) + if tempdir: + temp_dir = datadir + else: + temp_dir = None - # Raises an error because of wrong type - with pytest.raises(ValueError): - imgs = ["sdfdf", "sdfsdf"] - check_imgs(imgs) - - # Raises an error because of wrong shape - with pytest.raises(ValueError): - imgs = np.random.rand(3, 3, 3) - check_imgs(imgs) - - # Raises an error because only one subject - with pytest.raises(ValueError): - check_imgs(paths[:1]) + srm = FastSRM(atlas=atlas, + n_components=n_components, + n_iter=10, + temp_dir=temp_dir, + low_ram=low_ram, + verbose=True, + n_jobs=n_jobs, + aggregate=aggregate, + seed=0) + # Check that there is no difference between fit_transform + # and fit then transform -def test_islow_ram(): - with pytest.raises(ValueError): - is_low_ram(["wrong type"]) + srm.fit(X) + basis = [safe_load(b) for b in srm.basis_list] + shared_response_raw = srm.transform(X) + shared_response = apply_aggregate(shared_response_raw, aggregate, + input_format) + shared_response_fittransform = apply_aggregate(srm.fit_transform(X), + aggregate, input_format) - assert is_low_ram(np.random.rand(10, 300)) is False - assert is_low_ram("path_to_data") is True + for j in range(n_sessions): + assert_array_almost_equal(shared_response_fittransform[j], + shared_response[j]) + # Check that the decomposition works + for i in range(n_subjects): + for j in range(n_sessions): + assert_array_almost_equal(shared_response[j].T.dot(basis[i]), + XX[i][j].T) -def test_shapes(): - with pytest.raises(ValueError): - check_shapes(n_supervoxels=10, n_components=50, n_timeframes=100) + # Check that if we use all subjects but one if gives almost the + # same shared response + shared_response_partial_raw = srm.transform(X[1:5], + subjects_indexes=list( + range(1, 5))) - with pytest.raises(ValueError): - check_shapes(n_supervoxels=50, n_components=30, n_timeframes=20) + shared_response_partial = apply_aggregate(shared_response_partial_raw, + aggregate, input_format) + for j in range(n_sessions): + assert_array_almost_equal(shared_response_partial[j], + shared_response[j]) + # Check that if we perform add 2 times the same subject we + # obtain the same decomposition + srm.add_subjects(X[:1], shared_response_raw) + assert_array_almost_equal(safe_load(srm.basis_list[0]), + safe_load(srm.basis_list[-1])) + + +@pytest.mark.parametrize("input_format", + ["array", "list_of_list", "list_of_array"]) +@pytest.mark.parametrize("low_ram", [True, False]) +@pytest.mark.parametrize("tempdir", [True, False]) +@pytest.mark.parametrize( + "atlas", [None, np.arange(1, n_voxels + 1), + np.eye(n_voxels)]) +@pytest.mark.parametrize("n_jobs", [1, 2]) +@pytest.mark.parametrize("n_timeframes", [[25, 25], [25, 24]]) +@pytest.mark.parametrize("aggregate", ["mean", None]) +def test_class_srm_inverse_transform(input_format, low_ram, tempdir, atlas, + n_jobs, n_timeframes, aggregate): -def test_multiple_fit(): with tempfile.TemporaryDirectory() as datadir: - np.random.seed(0) + X, W, S = generate_data(n_voxels, n_timeframes, n_subjects, + n_components, datadir, 0, input_format) - # We authorize different timeframes for different sessions - # but they should be the same across subject - n_voxels = 100 - n_timeframes = [250, 245] - n_subjects = 5 - n_components = 3 # number of components used for SRM model - - np.random.seed(0) - paths, W, S = generate_data(n_voxels, n_timeframes, n_subjects, - n_components, datadir, 0) + if tempdir: + temp_dir = datadir + else: + temp_dir = None - atlas = np.arange(1, n_voxels + 1) - - for n_jobs in [1, 2]: - srm = FastSRM(atlas=atlas, - n_components=n_components, - n_iter=10, - temp_dir=datadir, - verbose=True, - n_jobs=n_jobs) - srm.fit(paths) - basis = srm.basis_list - print(basis) - shared_response = srm.transform(paths) - - # Reconstruct data using all indexes - reconstructed_data = srm.inverse_transform( - shared_response, - subjects_indexes=None, - sessions_indexes=None) - - for i in range(len(paths)): - for j in range(len(paths[i])): - assert_array_almost_equal(reconstructed_data[i, j], - np.load(paths[i, j])) + srm = FastSRM(atlas=atlas, + n_components=n_components, + n_iter=10, + temp_dir=temp_dir, + low_ram=low_ram, + verbose=True, + n_jobs=n_jobs, + aggregate=aggregate, + seed=0) + + # Check that there is no difference between fit_transform + # and fit then transform + + srm.fit(X) + shared_response_raw = srm.transform(X) + # Check inverse transform + if input_format == "list_of_array": + reconstructed_data = srm.inverse_transform(shared_response_raw, + subjects_indexes=[0, 2]) + for i, ii in enumerate([0, 2]): + assert_array_almost_equal(reconstructed_data[i], X[ii]) + + reconstructed_data = srm.inverse_transform(shared_response_raw, + subjects_indexes=None) + for i in range(len(X)): + assert_array_almost_equal(reconstructed_data[i], X[i]) + else: + reconstructed_data = srm.inverse_transform(shared_response_raw, + sessions_indexes=[1], + subjects_indexes=[0, 2]) + for i, ii in enumerate([0, 2]): + for j, jj in enumerate([1]): + assert_array_almost_equal(reconstructed_data[i][j], + safe_load(X[ii][jj])) + + reconstructed_data = srm.inverse_transform(shared_response_raw, + subjects_indexes=None, + sessions_indexes=None) + + for i in range(len(X)): + for j in range(len(X[i])): + assert_array_almost_equal(reconstructed_data[i][j], + safe_load(X[i][j])) From 3e7b50d0018f0fde45c8d59d35dc64494e771870 Mon Sep 17 00:00:00 2001 From: hrichard Date: Fri, 18 Oct 2019 15:49:00 +0200 Subject: [PATCH 22/24] delete temporary files --- brainiak/funcalign/mydetsrm.py | 329 --------------------------------- 1 file changed, 329 deletions(-) delete mode 100644 brainiak/funcalign/mydetsrm.py diff --git a/brainiak/funcalign/mydetsrm.py b/brainiak/funcalign/mydetsrm.py deleted file mode 100644 index c956cbd4b..000000000 --- a/brainiak/funcalign/mydetsrm.py +++ /dev/null @@ -1,329 +0,0 @@ -import numpy as np -from sklearn.base import BaseEstimator, TransformerMixin -from sklearn.utils import check_random_state -from scipy.sparse import diags -import scipy - - -def hyperalign(X, Y, scale=False, primal=None): - """ - Hyperalign X with Y using R and sc such that - frobenius norm ||sc RX - Y||^2 is minimized and - R is an orthogonal matrix - sc is a scalar - Parameters - ---------- - X: (n_features, n_timeframes) nd array - source data - Y: (n_features, n_timeframes) nd array - target data - scale: bool - If scale is true, computes a floating scaling parameter sc such that: - ||sc * RX - Y||^2 is minimized and - - R is an orthogonal matrix - - sc is a scalar - If scale is false sc is set to 1 - primal: bool or None, optional, - Whether the SVD is done on the YX^T (primal) or Y^TX (dual) - if None primal is used iff n_features <= n_timeframes - - Returns - ---------- - R: (n_features, n_features) nd array - transformation matrix - sc: int - scaling parameter - """ - if np.linalg.norm(X) == 0 or np.linalg.norm(Y) == 0: - return diags(np.ones(X.shape[1])).tocsr(), 1 - - if primal is None: - primal = X.shape[1] >= X.shape[0] - - if primal: - A = Y.dot(X.T) - if A.shape[0] == A.shape[1]: - A += +1.e-18 * np.eye(A.shape[0]) - U, s, V = scipy.linalg.svd(A, full_matrices=0) - R = U.dot(V) - else: # "dual" mode - Uy, sy, Vy = scipy.linalg.svd(Y, full_matrices=0) - Ux, sx, Vx = scipy.linalg.svd(X, full_matrices=0) - A = np.diag(sy).dot(Vy).dot(Vx.T).dot(np.diag(sx)) - U, s, V = scipy.linalg.svd(A) - R = Uy.dot(U).dot(V).dot(Ux.T) - """ - if X.shape[0] > 10000: - R = diags(np.ones(X.shape[0])).tocsr() - s = np.sum(Y * X, 1) - """ - if scale: - sc = s.sum() / (np.linalg.norm(X)**2) - else: - sc = 1 - return R, sc - - -def create_orthogonal_matrix(rows, cols, random_state=None): - """ - Creates matrix W with orthogonal columns: - W.T.dot(W) = I - Parameters - ---------- - rows: int - number of rows - cols: int - number of columns - random_state : int or RandomState - Pseudo number generator state used for random sampling. - Returns - --------- - Matrix W of shape (rows, cols) such that W.T.dot(W) = np.eye(cols) - """ - v = rows - k = cols - if random_state is None: - rnd_matrix = np.random.rand(v, k) - else: - rnd_matrix = random_state.rand(v, k) - q, r = np.linalg.qr(rnd_matrix) - return q - - -def _compute_shared_response(compressed_data, basis, scale): - """ - Computes the shared response S using subject basis and scaling - the basis refers to sc_i * W_i - the scale refers to sc_i - """ - s = None - for m in range(len(basis)): - data_m = compressed_data[m] - if s is None: - s = basis[m].T.dot(data_m) - else: - s = s + basis[m].T.dot(data_m) - s /= np.sum(scale**2) - return s - - -def fast_srm(reduced_data, - random_state=None, - max_iter=10, - tol=1e-6, - use_scaling=False, - n_components=None): - """ - Computes shared response and basis in reduced space - the basis refers to sc_i * W_i - the scale refers to sc_i - - Parameters - ---------- - reduced_data: list of n_subjects np array of shape n_voxels, n_timeframes - The reduced data - random_state: RandomState - max_iter: int - tol: int - use_scaling: bool - If True the scaling procedure is used - n_components: int or None - number of components if n_voxels != n_components - Returns - ------- - scale: np array of shape n_subjects - shared_response: np array of shape n_components, n_timeframes - basis: list of n_subjects arrays of shape n_voxels, n_components - """ - - n_subjects = len(reduced_data) - basis = [] - scale = [] - random_state = check_random_state(random_state) - for subject in range(n_subjects): - n_voxels, n_timeframes = reduced_data[subject].shape - if n_components is None: - n_components = n_voxels - q = create_orthogonal_matrix(n_voxels, - n_components, - random_state=random_state) - basis.append(q) - scale.append(1.) - scale = np.array(scale) - - shared_response = _compute_shared_response(reduced_data, basis, scale) - for n_iter in range(max_iter): - for i in range(n_subjects): - X_i = reduced_data[i] - R, sc = hyperalign(shared_response, X_i, scale=use_scaling) - basis[i] = sc * R - - shared_response = _compute_shared_response(reduced_data, basis, scale) - - if np.sum([ - np.linalg.norm(reduced_data[i] - basis[i].dot(shared_response)) - for i in range(n_subjects) - ], - axis=0) < tol: - break - - return scale, basis, shared_response - - -class MyDetSRM(BaseEstimator, TransformerMixin): - """My Deterministic Shared Response Model (DetSRM) - """ - def __init__(self, n_iter=10, features=50, rand_seed=0): - self.n_iter = n_iter - self.features = features - self.rand_seed = rand_seed - return - - def fit(self, X, y=None): - """Compute the Deterministic Shared Response Model - - Parameters - ---------- - X : list of 2D arrays, element i has shape=[voxels_i, samples] - Each element in the list contains the fMRI data of one subject. - - y : not used - """ - _, self.w_, self.s_ = fast_srm(X, - self.rand_seed, - max_iter=self.n_iter, - n_components=self.features) - - return self - - def transform(self, X, y=None): - """Use the model to transform data to the Shared Response subspace - - Parameters - ---------- - X : list of 2D arrays, element i has shape=[voxels_i, samples_i] - Each element in the list contains the fMRI data of one subject. - - y : not used - - - Returns - ------- - s : list of 2D arrays, element i has shape=[features_i, samples_i] - Shared responses from input data (X) - """ - - # Check the number of subjects - if len(X) != len(self.w_): - raise ValueError("The number of subjects does not match the one" - " in the model.") - - s = [None] * len(X) - for subject in range(len(X)): - s[subject] = self.w_[subject].T.dot(X[subject]) - - return s - - def _objective_function(self, data, w, s): - """Calculate the objective function - - Parameters - ---------- - - data : list of 2D arrays, element i has shape=[voxels_i, samples] - Each element in the list contains the fMRI data of one subject. - - w : list of 2D arrays, element i has shape=[voxels_i, features] - The orthogonal transforms (mappings) :math:`W_i` for each subject. - - s : array, shape=[features, samples] - The shared response - - Returns - ------- - - objective : float - The objective function value. - """ - subjects = len(data) - objective = 0.0 - for m in range(subjects): - objective += \ - np.linalg.norm(data[m] - w[m].dot(s), 'fro') ** 2 - - return objective * 0.5 / data[0].shape[1] - - def _compute_shared_response(self, data, w): - """ Compute the shared response S - - Parameters - ---------- - - data : list of 2D arrays, element i has shape=[voxels_i, samples] - Each element in the list contains the fMRI data of one subject. - - w : list of 2D arrays, element i has shape=[voxels_i, features] - The orthogonal transforms (mappings) :math:`W_i` for each subject. - - Returns - ------- - - s : array, shape=[features, samples] - The shared response for the subjects data with the mappings in w. - """ - s = np.zeros((w[0].shape[1], data[0].shape[1])) - for m in range(len(w)): - s = s + w[m].T.dot(data[m]) - s /= len(w) - - return s - - @staticmethod - def _update_transform_subject(Xi, S): - """Updates the mappings `W_i` for one subject. - - Parameters - ---------- - - Xi : array, shape=[voxels, timepoints] - The fMRI data :math:`X_i` for aligning the subject. - - S : array, shape=[features, timepoints] - The shared response. - - Returns - ------- - - Wi : array, shape=[voxels, features] - The orthogonal transform (mapping) :math:`W_i` for the subject. - """ - A = Xi.dot(S.T) - # Solve the Procrustes problem - U, _, V = np.linalg.svd(A, full_matrices=False) - return U.dot(V) - - def transform_subject(self, X): - """Transform a new subject using the existing model. - The subject is assumed to have recieved equivalent stimulation - - Parameters - ---------- - - X : 2D array, shape=[voxels, timepoints] - The fMRI data of the new subject. - - Returns - ------- - - w : 2D array, shape=[voxels, features] - Orthogonal mapping `W_{new}` for new subject - """ - - # Check the number of TRs in the subject - if X.shape[1] != self.s_.shape[1]: - raise ValueError("The number of timepoints(TRs) does not match the" - "one in the model.") - - w = self._update_transform_subject(X, self.s_) - - return w From 2009da155790b437be7bed3f38a59f4c39bd021f Mon Sep 17 00:00:00 2001 From: hrichard Date: Sun, 20 Oct 2019 17:59:30 +0200 Subject: [PATCH 23/24] Enhance docs and remove parallel tests --- brainiak/funcalign/fastsrm.py | 374 ++++++++++++++++---------------- tests/funcalign/test_fastsrm.py | 84 ++++--- 2 files changed, 219 insertions(+), 239 deletions(-) diff --git a/brainiak/funcalign/fastsrm.py b/brainiak/funcalign/fastsrm.py index 2d8b13d07..184c09c19 100644 --- a/brainiak/funcalign/fastsrm.py +++ b/brainiak/funcalign/fastsrm.py @@ -1,19 +1,6 @@ """Fast Shared Response Model (FastSRM) -The implementations are based on the following publications: - -.. [Chen2015] "A Reduced-Dimension fMRI Shared Response Model", - P.-H. Chen, J. Chen, Y. Yeshurun-Dishon, U. Hasson, J. Haxby, P. Ramadge - Advances in Neural Information Processing Systems (NIPS), 2015. - http://papers.nips.cc/paper/5855-a-reduced-dimension-fmri-shared-response-model - -.. [Anderson2016] "Enabling Factor Analysis on Thousand-Subject Neuroimaging - Datasets", - Michael J. Anderson, Mihai Capotă, Javier S. Turek, Xia Zhu, Theodore L. - Willke, Yida Wang, Po-Hsuan Chen, Jeremy R. Manning, Peter J. Ramadge, - Kenneth A. Norman, - IEEE International Conference on Big Data, 2016. - https://doi.org/10.1109/BigData.2016.7840719 +The implementation is based on the following publications: .. [Richard2019] "Fast Shared Response Model for fMRI data" H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019 @@ -1238,25 +1225,27 @@ def _compute_shared_response_online(imgs, basis_list, temp_dir, n_jobs, class FastSRM(BaseEstimator, TransformerMixin): - """SRM decomposition using a very low amount of memory and - computational power + """SRM decomposition using a very low amount of memory and \ +computational power thanks to the use of an atlas \ +as described in [Richard2019]_. - Given multi-subject data, factorize it as a shared response S among all - subjects and an orthogonal transform (basis) W per subject: + Given multi-subject data, factorize it as a shared response S \ +among all subjects and an orthogonal transform (basis) W per subject: .. math:: X_i \\approx W_i S, \\forall i=1 \\dots N Parameters ---------- - atlas : array, shape=[n_supervoxels, n_voxels] or array, shape=[n_voxels] - or str or None, default=None - Probabilistic or deterministic atlas on which to project the data - Deterministic atlas is an array of shape [n_voxels,] where values - range from 1 to n_supervoxels. Voxels labelled 0 will be ignored. - If atlas is a str the corresponding array is loaded with numpy.load - and expected shape is (n_voxels,) for a deterministic atlas and - (n_supervoxels, n_voxels) for a probabilistic atlas. + atlas : array, shape=[n_supervoxels, n_voxels] or array,\ +shape=[n_voxels] or str or None, default=None + Probabilistic or deterministic atlas on which to project the data. \ +Deterministic atlas is an array of shape [n_voxels,] \ +where values range from 1 \ +to n_supervoxels. Voxels labelled 0 will be ignored. If atlas is a str the \ +corresponding array is loaded with numpy.load and expected shape \ +is (n_voxels,) for a deterministic atlas and \ +(n_supervoxels, n_voxels) for a probabilistic atlas. n_components : int Number of timecourses of the shared coordinates @@ -1265,50 +1254,50 @@ class FastSRM(BaseEstimator, TransformerMixin): Number of iterations to perform temp_dir : str or None - path to dir where temporary results are stored - if None temporary results will be stored in memory. This - can results in memory errors when the number of subjects - and / or sessions is large + Path to dir where temporary results are stored. If None \ +temporary results will be stored in memory. This can results in memory \ +errors when the number of subjects and/or sessions is large low_ram : bool - if True and temp_dir is not None, reduced_data will be saved on disk - this increases the number of IO but reduces memory complexity when - the number of subject and / or sessions is large + If True and temp_dir is not None, reduced_data will be saved on \ +disk. This increases the number of IO but reduces memory complexity when \ +the number of subject and/or sessions is large seed : int Seed used for random sampling. n_jobs : int, optional, default=1 - The number of CPUs to use to do the computation. - -1 means all CPUs, -2 all CPUs but one, and so on. + The number of CPUs to use to do the computation. \ +-1 means all CPUs, -2 all CPUs but one, and so on. verbose : bool or "warn" - if True, logs are enabled. - if False, logs are disabled. - if "warn" only warnings are printed. + If True, logs are enabled. If False, logs are disabled. \ +If "warn" only warnings are printed. aggregate: str or None, default="mean" - if "mean": shared_response is the mean shared response S - from all subjects - if None: shared_response contains all subject-specific responses - in shared space S_i + If "mean", shared_response is the mean shared response \ +from all subjects. If None, shared_response contains all \ +subject-specific responses in shared space Attributes ---------- - `basis_list`: list of array, element i has shape=[n_components, n_voxels] - or list of str + `basis_list`: list of array, element i has \ +shape=[n_components, n_voxels] or list of str - if basis is a list of array, element i is the basis of subject i - - if basis is a list of str, element i is the path to the basis - of subject i that is loaded with np.load yielding an array of - shape [n_components, n_voxels]. - Note that any call to clean erases this attribute + - if basis is a list of str, element i is the path to the basis \ +of subject i that is loaded with np.load yielding an array of \ +shape [n_components, n_voxels]. - Notes + Note that any call to the clean method erases this attribute + + Note ----- - **References:** - H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019: Fast - shared response model for fMRI data (https://arxiv.org/pdf/1909.12537.pdf) + + **References:** + H. Richard, L. Martin, A. Pinho, J. Pillow, B. Thirion, 2019: \ +Fast shared response model for fMRI data (https://arxiv.org/pdf/1909.12537.pdf) + """ def __init__(self, atlas=None, @@ -1352,8 +1341,9 @@ def __init__(self, self.low_ram = low_ram def clean(self): - """This erases temporary files and basis_list attribute to free memory. - This method should be called when fitted model is not needed anymore. + """This erases temporary files and basis_list attribute to \ +free memory. This method should be called when fitted model \ +is not needed anymore. """ if self.temp_dir is not None: if os.path.exists(self.temp_dir): @@ -1370,30 +1360,27 @@ def fit(self, imgs): Parameters ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected - shape is [n_voxels, n_timeframes] - n_timeframes and n_voxels are assumed to be the same across - subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - imgs can also be a list of list of arrays where element i, j of - the array is a numpy array of shape [n_voxels, n_timeframes] that - contains the data of subject i collected during session j. - - imgs can also be a list of arrays where element i of the array is - a numpy array of shape [n_voxels, n_timeframes] that contains the - data of subject i (number of sessions is implicitly 1) + imgs : array of str, shape=[n_subjects, n_sessions] or \ +list of list of arrays or list of arrays + Element i, j of the array is a path to the data of subject i \ +collected during session j. Data are loaded with numpy.load and expected \ +shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed \ +to be the same across subjects n_timeframes can vary across sessions. \ +Each voxel's timecourse is assumed to have mean 0 and variance 1 + + imgs can also be a list of list of arrays where element i, j \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i (number of sessions is implicitly 1) Returns ------- self : object - Returns the instance itself. Contains attributes listed - at the object level. + Returns the instance itself. Contains attributes listed \ +at the object level. """ atlas_shape = check_atlas(self.atlas, self.n_components) reshaped_input, imgs, shapes = check_imgs( @@ -1461,39 +1448,41 @@ def fit_transform(self, imgs, subjects_indexes=None): Parameters ---------- - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected - shape is [n_voxels, n_timeframes] - n_timeframes and n_voxels are assumed to be the same across - subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - imgs can also be a list of list of arrays where element i, j of - the array is a numpy array of shape [n_voxels, n_timeframes] that - contains the data of subject i collected during session j. + imgs : array of str, shape=[n_subjects, n_sessions] or \ +list of list of arrays or list of arrays + Element i, j of the array is a path to the data of subject i \ +collected during session j. Data are loaded with numpy.load and expected \ +shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed \ +to be the same across subjects n_timeframes can vary across sessions. \ +Each voxel's timecourse is assumed to have mean 0 and variance 1 + + imgs can also be a list of list of arrays where element i, j \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i (number of sessions is implicitly 1) - imgs can also be a list of arrays where element i of the array is - a numpy array of shape [n_voxels, n_timeframes] that contains the - data of subject i (number of sessions is implicitly 1) + subjects_indexes : list or None: + if None imgs[i] will be transformed using basis_list[i]. \ +Otherwise imgs[i] will be transformed using basis_list[subjects_index[i]] Returns -------- - shared_response : list of arrays, list of list of array or arrays - - if imgs is a list of array and self.aggregate="mean", shared - response is an array of shape (n_components, n_timeframes) - - if imgs is a list of array and self.aggregate=None, shared - response is a list of array, element i is the projection of data of - subject i in shared space. - - if imgs is an array or a list of list of array and - self.aggregate="mean", shared response is a list of array, - element j is the shared response during session j - - if imgs is an array or a list of list of array and - self.aggregate=None, shared response is a list of list of array, - element i, j is the projection of data of subject i collected - during session j in shared space. + shared_response : list of arrays, list of list of arrays or array + - if imgs is a list of array and self.aggregate="mean": shared \ +response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None: shared \ +response is a list of array, element i is the projection of data of \ +subject i in shared space. + - if imgs is an array or a list of list of array and \ +self.aggregate="mean": shared response is a list of array, \ +element j is the shared response during session j + - if imgs is an array or a list of list of array and \ +self.aggregate=None: shared response is a list of list of array, \ +element i, j is the projection of data of subject i collected \ +during session j in shared space. """ self.fit(imgs) return self.transform(imgs, subjects_indexes=subjects_indexes) @@ -1504,45 +1493,41 @@ def transform(self, imgs, subjects_indexes=None): Parameters ---------- - - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected - shape is [n_voxels, n_timeframes] - n_timeframes and n_voxels are assumed to be the same across - subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - imgs can also be a list of list of arrays where element i, j of - the array is a numpy array of shape [n_voxels, n_timeframes] that - contains the data of subject i collected during session j. - - imgs can also be a list of arrays where element i of the array is - a numpy array of shape [n_voxels, n_timeframes] that contains the - data of subject i (number of sessions is implicitly 1) + imgs : array of str, shape=[n_subjects, n_sessions] or \ +list of list of arrays or list of arrays + Element i, j of the array is a path to the data of subject i \ +collected during session j. Data are loaded with numpy.load and expected \ +shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed \ +to be the same across subjects n_timeframes can vary across sessions. \ +Each voxel's timecourse is assumed to have mean 0 and variance 1 + + imgs can also be a list of list of arrays where element i, j \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i (number of sessions is implicitly 1) subjects_indexes : list or None: - if None imgs[i] will be transformed using basis[i] - otherwise imgs[i] will be transformed using - basis[subjects_index[i]] + if None imgs[i] will be transformed using basis_list[i]. \ +Otherwise imgs[i] will be transformed using basis[subjects_index[i]] Returns -------- - shared_response : list of arrays, list of list of array or arrays - - if imgs is a list of array and self.aggregate="mean", shared - response is an array of shape (n_components, n_timeframes) - - if imgs is a list of array and self.aggregate=None, shared - response is a list of array, element i is the projection of data of - subject i in shared space. - - if imgs is an array or a list of list of array and - self.aggregate="mean", shared response is a list of array, - element j is the shared response during session j - - if imgs is an array or a list of list of array and - self.aggregate=None, shared response is a list of list of array, - element i, j is the projection of data of subject i collected - during session j in shared space. + shared_response : list of arrays, list of list of arrays or array + - if imgs is a list of array and self.aggregate="mean": shared \ +response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None: shared \ +response is a list of array, element i is the projection of data of \ +subject i in shared space. + - if imgs is an array or a list of list of array and \ +self.aggregate="mean": shared response is a list of array, \ +element j is the shared response during session j + - if imgs is an array or a list of list of array and \ +self.aggregate=None: shared response is a list of list of array, \ +element i, j is the projection of data of subject i collected \ +during session j in shared space. """ aggregate = self.aggregate if self.basis_list is None: @@ -1596,38 +1581,39 @@ def inverse_transform( Parameters ---------- - shared_response : list of arrays, list of list of array or arrays - - if imgs is a list of array and self.aggregate="mean", shared - response is an array of shape (n_components, n_timeframes) - - if imgs is a list of array and self.aggregate=None, shared - response is a list of array, element i is the projection of data of - subject i in shared space. - - if imgs is an array or a list of list of array and - self.aggregate="mean", shared response is a list of array, - element j is the shared response during session j - - if imgs is an array or a list of list of array and - self.aggregate=None, shared response is a list of list of array, - element i, j is the projection of data of subject i collected - during session j in shared space. + shared_response : list of arrays, list of list of arrays or array + - if imgs is a list of array and self.aggregate="mean": shared \ +response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None: shared \ +response is a list of array, element i is the projection of data of \ +subject i in shared space. + - if imgs is an array or a list of list of array and \ +self.aggregate="mean": shared response is a list of array, \ +element j is the shared response during session j + - if imgs is an array or a list of list of array and \ +self.aggregate=None: shared response is a list of list of array, \ +element i, j is the projection of data of subject i collected \ +during session j in shared space. subjects_indexes : list or None - if None reconstructs data of all subjects' used during train - otherwise reconstructs data using subject's specified by - subjects_indexes + if None reconstructs data of all subjects used during train. \ +Otherwise reconstructs data of subjects specified by subjects_indexes. sessions_indexes : list or None - if None reconstructs data using all sessions - otherwise uses only specified sessions + if None reconstructs data of all sessions. \ +Otherwise uses reconstructs data of sessions specified by sessions_indexes. Returns ------- reconstructed_data: list of list of arrays or list of arrays - if list of list element i, j is the reconstructed data - for subject subjects_indexes[i] and session sessions_indexes[j] - as an np array of shape n_voxels, n_timeframes - if list element i is the reconstructed data - for subject subject_indexes[i] - as an np array of shape n_voxels, n_timeframes + - if reconstructed_data is a list of list : element i, j is \ +the reconstructed data for subject subjects_indexes[i] and \ +session sessions_indexes[j] as an np array of shape n_voxels, \ +n_timeframes + - if reconstructed_data is a list : element i is the \ +reconstructed data for subject \ +subject_indexes[i] as an np array of shape n_voxels, n_timeframes + """ added_session, shared = check_shared_response( shared_response, self.aggregate, n_components=self.n_components) @@ -1667,40 +1653,42 @@ def inverse_transform( return data def add_subjects(self, imgs, shared_response): - """ Add subjects to the current fit - Each new basis will be appended at the end of the list of basis - (which can be accessed using self.basis) - imgs : array of str, shape=[n_subjects, n_sessions] - Element i, j of the array is a path to the data of subject i - collected during session j. - Data are loaded with numpy.load and expected - shape is [n_voxels, n_timeframes] - n_timeframes and n_voxels are assumed to be the same across - subjects - n_timeframes can vary across sessions - Each voxel's timecourse is assumed to have mean 0 and variance 1 - - imgs can also be a list of list of arrays where element i, j of - the array is a numpy array of shape [n_voxels, n_timeframes] that - contains the data of subject i collected during session j. + """ Add subjects to the current fit. Each new basis will be \ +appended at the end of the list of basis (which can \ +be accessed using self.basis) - imgs can also be a list of arrays where element i of the array is - a numpy array of shape [n_voxels, n_timeframes] that contains the - data of subject i (number of sessions is implicitly 1) + Parameters + ---------- - shared_response : list of arrays, list of list of array or arrays - - if imgs is a list of array and self.aggregate="mean", shared - response is an array of shape (n_components, n_timeframes) - - if imgs is a list of array and self.aggregate=None, shared - response is a list of array, element i is the projection of data of - subject i in shared space. - - if imgs is an array or a list of list of array and - self.aggregate="mean", shared response is a list of array, - element j is the shared response during session j - - if imgs is an array or a list of list of array and - self.aggregate=None, shared response is a list of list of array, - element i, j is the projection of data of subject i collected - during session j in shared space. + imgs : array of str, shape=[n_subjects, n_sessions] or \ +list of list of arrays or list of arrays + Element i, j of the array is a path to the data of subject i \ +collected during session j. Data are loaded with numpy.load and expected \ +shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed \ +to be the same across subjects n_timeframes can vary across sessions. \ +Each voxel's timecourse is assumed to have mean 0 and variance 1 + + imgs can also be a list of list of arrays where element i, j \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i collected during session j. + + imgs can also be a list of arrays where element i \ +of the array is a numpy array of shape [n_voxels, n_timeframes] \ +that contains the data of subject i (number of sessions is implicitly 1) + + shared_response : list of arrays, list of list of arrays or array + - if imgs is a list of array and self.aggregate="mean": shared \ +response is an array of shape (n_components, n_timeframes) + - if imgs is a list of array and self.aggregate=None: shared \ +response is a list of array, element i is the projection of data of \ +subject i in shared space. + - if imgs is an array or a list of list of array and \ +self.aggregate="mean": shared response is a list of array, \ +element j is the shared response during session j + - if imgs is an array or a list of list of array and \ +self.aggregate=None: shared response is a list of list of array, \ +element i, j is the projection of data of subject i collected \ +during session j in shared space. """ atlas_shape = check_atlas(self.atlas, self.n_components) reshaped_input, imgs, shapes = check_imgs( diff --git a/tests/funcalign/test_fastsrm.py b/tests/funcalign/test_fastsrm.py index 95101570e..d7486dd4f 100644 --- a/tests/funcalign/test_fastsrm.py +++ b/tests/funcalign/test_fastsrm.py @@ -1,18 +1,16 @@ -import numpy as np -import tempfile -from brainiak.funcalign.fastsrm import FastSRM, reduce_data,\ - _reduced_space_compute_shared_response, create_temp_dir,\ - check_atlas -from brainiak.funcalign.fastsrm import _compute_basis_subject_online, fast_srm -from brainiak.funcalign.fastsrm import _compute_and_save_corr_mat -from brainiak.funcalign.fastsrm import _compute_and_save_subject_basis -from brainiak.funcalign.fastsrm import check_shared_response -from sklearn.exceptions import NotFittedError -from brainiak.funcalign.fastsrm import check_imgs -from brainiak.funcalign.fastsrm import safe_load import os +import tempfile + +import numpy as np import pytest from numpy.testing import assert_array_almost_equal +from sklearn.exceptions import NotFittedError + +from brainiak.funcalign.fastsrm import ( + FastSRM, _compute_and_save_corr_mat, _compute_and_save_subject_basis, + _compute_basis_subject_online, _reduced_space_compute_shared_response, + check_atlas, check_imgs, check_shared_response, create_temp_dir, fast_srm, + reduce_data, safe_load) def to_path(X, dirpath): @@ -106,8 +104,8 @@ def test_generated_data(): # We authorize different timeframes for different sessions # but they should be the same across subject - n_voxels = 100 - n_timeframes = [250, 245] + n_voxels = 10 + n_timeframes = [25, 24] n_subjects = 2 n_components = 3 # number of components used for SRM model n_sessions = len(n_timeframes) @@ -146,13 +144,13 @@ def test_check_atlas(): "which is neither np.ndarray or str")): check_atlas([]) - A = np.random.rand(100, 1000) - assert check_atlas(A) == (100, 1000) + A = np.random.rand(10, 100) + assert check_atlas(A) == (10, 100) with tempfile.TemporaryDirectory() as datadir: f = os.path.join(datadir, "atlas") np.save(f, A) - assert check_atlas(f + ".npy") == (100, 1000) + assert check_atlas(f + ".npy") == (10, 100) A = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 5]) assert check_atlas(A) == (5, 11) @@ -389,10 +387,10 @@ def test_check_shared(): def test_reduce_data_dummyatlases(): n_jobs = 1 with tempfile.TemporaryDirectory() as datadir: - for n_timeframes in ([250, 245], [250, 250]): + for n_timeframes in ([25, 24], [25, 25]): # We authorize different timeframes for different sessions # but they should be the same across subject - n_voxels = 100 + n_voxels = 10 n_subjects = 2 n_components = 3 # number of components used for SRM model n_sessions = len(n_timeframes) @@ -428,13 +426,13 @@ def test_reduce_data_dummyatlases(): def test_reduce_data_outputshapes(): n_jobs = 1 with tempfile.TemporaryDirectory() as datadir: - for n_timeframes in ([250, 245], [250, 250]): + for n_timeframes in ([25, 24], [25, 25]): # We authorize different timeframes for different sessions # but they should be the same across subject - n_voxels = 100 + n_voxels = 10 n_subjects = 2 n_components = 3 # number of components used for SRM model - n_supervoxels = 10 # number of components of the atlas + n_supervoxels = 5 # number of components of the atlas n_sessions = len(n_timeframes) np.random.seed(0) @@ -481,8 +479,8 @@ def test_reduced_data_srm(): # We authorize different timeframes for different sessions but # they should be the same across subject - n_voxels = 100 - n_timeframes = [250, 245] + n_voxels = 10 + n_timeframes = [25, 24] n_subjects = 5 n_components = 3 # number of components used for SRM model n_sessions = len(n_timeframes) @@ -545,8 +543,8 @@ def test_reduced_data_srm(): def test_compute_and_save(): with tempfile.TemporaryDirectory() as datadir: np.random.seed(0) - n_voxels = 100 - n_timeframes = [250, 245] + n_voxels = 10 + n_timeframes = [25, 24] n_subjects = 5 n_components = 3 # number of components used for SRM model @@ -570,8 +568,8 @@ def test_fastsrm_class(): # We authorize different timeframes for different sessions # but they should be the same across subject - n_voxels = 100 - n_timeframes = [250, 245] + n_voxels = 10 + n_timeframes = [25, 24] n_subjects = 5 n_components = 3 # number of components used for SRM model @@ -685,16 +683,13 @@ def apply_input_format(X, input_format): return XX, n_sessions -@pytest.mark.parametrize("input_format", - ["array", "list_of_list", "list_of_array"]) -@pytest.mark.parametrize("low_ram", [True, False]) -@pytest.mark.parametrize("tempdir", [True, False]) @pytest.mark.parametrize( - "atlas", [None, np.arange(1, n_voxels + 1), - np.eye(n_voxels)]) -@pytest.mark.parametrize("n_jobs", [1, 2]) -@pytest.mark.parametrize("n_timeframes", [[25, 25], [25, 24]]) -@pytest.mark.parametrize("aggregate", ["mean", None]) + "input_format, low_ram, tempdir, atlas, n_jobs, n_timeframes, aggregate", + [("array", True, True, None, 1, [25, 25], "mean"), + ("list_of_list", False, False, np.arange(1, n_voxels + 1), 1, [25, 24 + ], None), + ("list_of_array", True, False, np.eye(n_voxels), 1, [25, 25], None), + ("list_of_array", False, True, None, 1, [25, 24], "mean")]) def test_fastsrm_class_correctness(input_format, low_ram, tempdir, atlas, n_jobs, n_timeframes, aggregate): with tempfile.TemporaryDirectory() as datadir: @@ -759,16 +754,13 @@ def test_fastsrm_class_correctness(input_format, low_ram, tempdir, atlas, safe_load(srm.basis_list[-1])) -@pytest.mark.parametrize("input_format", - ["array", "list_of_list", "list_of_array"]) -@pytest.mark.parametrize("low_ram", [True, False]) -@pytest.mark.parametrize("tempdir", [True, False]) @pytest.mark.parametrize( - "atlas", [None, np.arange(1, n_voxels + 1), - np.eye(n_voxels)]) -@pytest.mark.parametrize("n_jobs", [1, 2]) -@pytest.mark.parametrize("n_timeframes", [[25, 25], [25, 24]]) -@pytest.mark.parametrize("aggregate", ["mean", None]) + "input_format, low_ram, tempdir, atlas, n_jobs, n_timeframes, aggregate", + [("array", True, True, None, 1, [25, 25], "mean"), + ("list_of_list", False, False, np.arange(1, n_voxels + 1), 1, [25, 24 + ], None), + ("list_of_array", True, False, np.eye(n_voxels), 1, [25, 25], None), + ("list_of_array", False, True, None, 1, [25, 24], "mean")]) def test_class_srm_inverse_transform(input_format, low_ram, tempdir, atlas, n_jobs, n_timeframes, aggregate): From 9377cfc5eb4982a2827fc92f96111c41824ec0d7 Mon Sep 17 00:00:00 2001 From: hrichard Date: Fri, 25 Oct 2019 16:57:55 +0200 Subject: [PATCH 24/24] update FastSRM example --- .../FastSRM_encoding_experiment.ipynb | 393 +++++++++++------- examples/funcalign/requirements.txt | 1 + 2 files changed, 241 insertions(+), 153 deletions(-) diff --git a/examples/funcalign/FastSRM_encoding_experiment.ipynb b/examples/funcalign/FastSRM_encoding_experiment.ipynb index d85128733..4f4afe0d1 100644 --- a/examples/funcalign/FastSRM_encoding_experiment.ipynb +++ b/examples/funcalign/FastSRM_encoding_experiment.ipynb @@ -4,15 +4,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Encoding experiment using FastSRM" + "# Encoding experiment using the fast shared response model (FastSRM)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We train the model on a train session learning how to transfer data between subjects' space and shared space.\n", - "On a test session we use all subjects but one to compute the shared response and use this shared response to predict data from the test subject. We use R2 score as a measure of performance." + "In this notebook we introduce some basic functionalities of FastSRM and compare its performance to another implementation of SRM (ProbSRM). We present an encoding experiment that shows how fmri data of train subjects can be used to predict fmri data of test subjects (after training).\n", + "\n", + "More precisely, let us assume we have 2 groups of subjects (train, test) exposed to 2 similar but different naturalistic stimuli (session 1 and session 2) while we record their brain activity using an fMRI scanner. \n", + "\n", + "Our experiment follows the following steps:\n", + "\n", + "- Align train subjects: We train an alignment model on session 1 using train subjects\n", + "- Align test subjects: Using data of test subjects during session 1 and the previously fitted model we add test subjects to the model\n", + "- Predict test subjects data from train subjects: We use the model to align train subjects during session 2. From the aligned data (shared response) we predict the data of test subjects during session 2.\n", + "- Measure performance: We report the R2 score between predicted and actual data." ] }, { @@ -26,16 +34,25 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll download a publicly available fMRI dataset and run SRM on these data. This dataset comprises fMRI data for 20 subjects listening to the spoken story Pie Man by Jim O'Grady (archived on the Princeton DataSpace). Note that we use 20 subjects to minimize computational demands for this tutorial and recommend larger sample sizes for publication. The gzipped data archive file is ~1.5 GB in size, and may take a couple minutes to download and unzip. The functional data were acquired with 3 x 3 x 4 mm voxels and 1.5 s TRs. Data were preprocessed using fMRIPrep (Esteban et al., 2018), including spatial normalization to MNI space (the T1-weighted ICBM 2009c Nonlinear Asymmetric template). The data were then smoothed to 6 mm FWHM using AFNI's 3dBlurToFWHM (Cox, 1996). The following confound variables were regressed out using 3dTproject: six head motion parameters (and their first derivatives), framewise displacement, six prinicipal components from an anatomical mask of cerebrospinal fluid (CSF) and white matter, sine/cosine bases for high-pass filtering (cutoff: 0.00714 Hz; 140 s), as well as a linear and quadratic trends. The anatomical template and a brain mask (i.e., excluding skull) are supplied as well. These have been resampled to match resolution of the functional images." + "We'll download a publicly available fMRI dataset and run SRM on these data. This dataset comprises fMRI data for 20 subjects listening to the spoken story Pie Man by Jim O'Grady (archived on the Princeton DataSpace). Note that we use 20 subjects to minimize computational demands for this tutorial and recommend larger sample sizes for publication. The gzipped data archive file is ~1.5 GB in size, and may take a couple minutes to download and unzip. The functional data were acquired with 3 x 3 x 4 mm voxels and 1.5 s TRs. Data were preprocessed using fMRIPrep (Esteban et al., 2018), including spatial normalization to MNI space (the T1-weighted ICBM 2009c Nonlinear Asymmetric template). The data were then smoothed to 6 mm FWHM using AFNI's 3dBlurToFWHM (Cox, 1996). The following confound variables were regressed out using 3dTproject: six head motion parameters (and their first derivatives), framewise displacement, six prinicipal components from an anatomical mask of cerebrospinal fluid (CSF) and white matter, sine/cosine bases for high-pass filtering (cutoff: 0.00714 Hz; 140 s), as well as a linear and quadratic trends. The anatomical template and a brain mask (i.e., excluding skull) are supplied as well. These have been resampled to match resolution of the functional images." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/hugo/Research/brainiak/venv/lib/python3.6/site-packages/sklearn/externals/joblib/__init__.py:15: DeprecationWarning: sklearn.externals.joblib is deprecated in 0.21 and will be removed in 0.23. Please import this functionality directly from joblib, which can be installed with: pip install joblib. If this warning is raised when loading pickled models, you may need to re-serialize those models with scikit-learn 0.21+.\n", + " warnings.warn(msg, category=DeprecationWarning)\n" + ] + } + ], "source": [ - "from urllib.request import urlretrieve\n", + "import wget\n", "from time import time\n", "from glob import glob\n", "from os.path import join\n", @@ -45,7 +62,9 @@ "import numpy as np\n", "from joblib import Parallel, delayed\n", "from nilearn.plotting import plot_stat_map\n", - "import matplotlib.pyplot as plt" + "import matplotlib.pyplot as plt\n", + "from IPython.display import clear_output\n", + "import tarfile" ] }, { @@ -57,17 +76,62 @@ "name": "stdout", "output_type": "stream", "text": [ - "Done in 643.28 seconds\n" + "Progress: [################################################################################] 100.0%\n" ] + }, + { + "data": { + "text/plain": [ + "'pieman_isc'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "# Download data tarball from Princeton DataSpace\n", + "# Download data tarball from Princeton DataSpace (about 1 Gb to download)\n", "t0 = time()\n", - "urlretrieve('https://dataspace.princeton.edu/jspui/bitstream/'\n", - " '88435/dsp01dz010s83s/6/pieman-isc-tutorial.tgz',\n", - " 'data_fastsrm.tgz');\n", - "!tar -xzf data_fastsrm.tgz\n", + "def update_progress(current, total, width=0):\n", + " bar_length = 80\n", + " progress = current / total\n", + " if isinstance(progress, int):\n", + " progress = float(progress)\n", + " if not isinstance(progress, float):\n", + " progress = 0\n", + " if progress < 0:\n", + " progress = 0\n", + " if progress >= 1:\n", + " progress = 1\n", + "\n", + " block = int(round(bar_length * progress))\n", + " clear_output(wait = True)\n", + " text = \"Progress: [{0}] {1:.1f}%\".format( \"#\" * block + \"-\" * (bar_length - block), progress * 100)\n", + " print(text)\n", + " \n", + "wget.download('https://dataspace.princeton.edu/jspui/bitstream/88435/dsp01dz010s83s/6/pieman-isc-tutorial.tgz', \n", + " 'pieman_isc', bar=update_progress)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done in 801.00 seconds\n" + ] + } + ], + "source": [ + "tar = tarfile.open(\"pieman_isc\", \"r:gz\")\n", + "tar.extractall()\n", + "tar.close()\n", "print(\"Done in %.2f seconds\" % (time() - t0))" ] }, @@ -77,12 +141,15 @@ "source": [ "# Step 1: Mask and save the data\n", "- We split our data into two sessions (in order to be able to perform our encoding experiment)\n", - "- We mask the data and save them into .npy file" + "- We mask the data and save them into .npy file\n", + "\n", + "### Note:\n", + "We use ``detrend=True`` and ``standardize=True`` in the ``NiftiMasker``. This is standard fMRI preprocessing and is needed for FastSRM to work." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -90,23 +157,23 @@ "output_type": "stream", "text": [ "[Parallel(n_jobs=4)]: Using backend LokyBackend with 4 concurrent workers.\n", - "[Parallel(n_jobs=4)]: Done 5 tasks | elapsed: 16.7s\n", - "[Parallel(n_jobs=4)]: Done 10 tasks | elapsed: 24.7s\n", - "[Parallel(n_jobs=4)]: Done 16 out of 20 | elapsed: 32.7s remaining: 8.2s\n" + "[Parallel(n_jobs=4)]: Done 5 tasks | elapsed: 15.1s\n", + "[Parallel(n_jobs=4)]: Done 10 tasks | elapsed: 22.4s\n", + "[Parallel(n_jobs=4)]: Done 16 out of 20 | elapsed: 29.8s remaining: 7.5s\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "Done in 40.33 seconds\n" + "Done in 37.37 seconds\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "[Parallel(n_jobs=4)]: Done 20 out of 20 | elapsed: 40.3s finished\n" + "[Parallel(n_jobs=4)]: Done 20 out of 20 | elapsed: 37.4s finished\n" ] } ], @@ -136,8 +203,9 @@ " standardize=True,\n", " smoothing_fwhm=6\n", " ).fit()\n", - " X_1 = masker.transform(I_1)\n", - " X_2 = masker.transform(I_2)\n", + " # Transpose the data to fit with SRM conventions\n", + " X_1 = masker.transform(I_1).T\n", + " X_2 = masker.transform(I_2).T\n", " # Save data\n", " np.save(func[:-7] + \"_session_1\", X_1)\n", " np.save(func[:-7] + \"_session_2\", X_2)\n", @@ -161,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -179,7 +247,7 @@ "output_type": "stream", "text": [ "\r", - "Downloaded 193985 of 193985 bytes (100.0%, 0.0s remaining) ...done. (0 seconds, 0 min)\n", + "Downloaded 193985 of 193985 bytes (100.0%, 0.0s remaining) ...done. (1 seconds, 0 min)\n", "Extracting data from pieman-isc-tutorial/basc_multiscale_2015/3cbcf0eeb3f666f55070aba1db9a758f/1861819..... done.\n" ] }, @@ -188,7 +256,7 @@ "output_type": "stream", "text": [ "pieman-isc-tutorial/basc_multiscale_2015/template_cambridge_basc_multiscale_nii_sym/template_cambridge_basc_multiscale_sym_scale444.nii.gz\n", - "Done in 26.51\n" + "Done in 24.46\n" ] } ], @@ -231,24 +299,30 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Note about input data\n", - "\n", - "- ProbSRM takes as input input images: a list of n_subjects np array of size (n_voxels, n_timeframes)\n", - "- FastSRM takes as input (1) input images: a matrix of npy files of size (number of subjects, number of sessions) each file should be loadable with np.load and represents a matrix of size (n_timeframes, n_voxels) (2) an atlas: np array of shape (n_components, n_voxels) in case of probabilistic atlas and simply (n_voxels) for a deterministic atlas" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Step 3: Fit of the model\n", + "# Step 3: Fit of the model and predict data of left-out subjects\n", "- Load data\n", - "- Train model on first session" + "- Train model on first session using train subjects \n", + "- Compute shared response on second session using train subjects\n", + "- Compute alignment for test subjects using session 1\n", + "- Predict data of test subjects during session 2 using the trained model\n", + "\n", + "### Note about input images\n", + "##### ProbSRM/DetSRM possible input\n", + "- imgs is a list of arrays where element i of the array is a numpy array of shape [n_voxels, n_timeframes] that contains the data of subject i\n", + "\n", + "##### FastSRM possible input\n", + "- imgs is a list of arrays where element i of the array is a numpy array of shape [n_voxels, n_timeframes] that contains the data of subject i\n", + "\n", + "- imgs is a list of list of arrays where element i, j of the array is a numpy array of shape [n_voxels, n_timeframes] that contains the data of subject i collected during session j.\n", + "\n", + "- imgs is an np array imgs, imgs[i, j] is a path to the data of subject i collected during session j. Data are loaded with numpy.load and expected shape is [n_voxels, n_timeframes] n_timeframes and n_voxels are assumed to be the same across subjects n_timeframes can vary across sessions. Each voxel’s timecourse is assumed to have mean 0 and variance 1\n", + "\n", + "=> So FastSRM can be used with very large dataset (even those where data cannot be hold in memory)" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -276,13 +350,13 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def load_and_concat(paths):\n", " \"\"\"\n", - " Take list of path and yields input data for ProbSRM\n", + " Take an array (n_subjects, n_sessions) of path and yields a list of arrays\n", " Parameters\n", " ----------\n", " paths\n", @@ -293,117 +367,141 @@ " X = []\n", " for i in range(len(paths)):\n", " X_i = np.concatenate([np.load(paths[i, j])\n", - " for j in range(len(paths[i]))], axis=0).T\n", + " for j in range(len(paths[i]))], axis=1)\n", " X.append(X_i)\n", " return X" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fitting FastSRM\n", - "Done in 5.49\n", - "Fitting ProbSRM\n", - "Done in 38.92\n" - ] - } - ], + "outputs": [], "source": [ "from brainiak.funcalign.fastsrm import FastSRM\n", "from brainiak.funcalign.srm import SRM\n", - "# We specify\n", - "# temp_dir: a temporary directory used to store spatial maps (this saves memory when number of subjects is big)\n", - "# (not really needed here but I'll use it anyway)\n", - "# n_jobs: the number of cores to use\n", - "# low_ram: in case you still have memory issues even when temp_dir is not None set this to True\n", - "# n_iter: can easily be set to 100 with no difference in fitting time \n", - "# but we set it to 10 to be fair with ProbSRM\n", "\n", "fastsrm = FastSRM(\n", - " atlas=np.load(atlas_path),\n", + " atlas=atlas_path, # the path to basc atlas (we could have used np.load(atlas_path) instead)\n", " n_components=20,\n", - " n_jobs=4,\n", - " n_iter=10,\n", - " temp_dir=data_dir,\n", - " low_ram=False\n", + " n_jobs=1, # Since we use a small dataset paralellization is counter-productive so we do not use it here\n", + " n_iter=10, \n", + " temp_dir=data_dir, # We will use the disk as if we had a small memory\n", + " low_ram=True, # Let's say I really have a small memory so I need low_ram mode\n", + " aggregate=\"mean\" # transform will return the mean of subject specific shared response\n", ")\n", + "\n", "probsrm = SRM(\n", - " n_iter=10,\n", - " features=20\n", + " n_iter=10, # same number of iterations\n", + " features=20 # same number of components\n", ")\n", "\n", - "print(\"Fitting FastSRM\")\n", - "t0 = time()\n", - "fastsrm.fit(files[:, :1])\n", - "print(\"Done in %.2f\" % (time() - t0))\n", - "\n", - "print(\"Fitting ProbSRM\")\n", - "train_files = load_and_concat(files[:, :1])\n", - "t0 = time()\n", - "probsrm.fit(train_files)\n", - "print(\"Done in %.2f\" % (time() - t0))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Step 4: Use the model to reconstruct data from a leftout subjects using others" + "models = [(\"probsrm\", probsrm), (\"fastsrm\", fastsrm)]" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running reconstruction experiment for probsrm\n", + "Done in 105.25\n", + "Running reconstruction experiment for fastsrm\n", + "Done in 48.39\n" + ] + } + ], "source": [ "from sklearn.model_selection import KFold\n", - "# List where we record test R2 scores per voxels of all subjects\n", - "var_e_fastsrm = []\n", - "var_e_probsrm = []\n", - "for subjects_train, subjects_test in KFold(n_splits=5,\n", - " shuffle=True\n", - " ).split(np.arange(len(subjects))):\n", - " shared_response_fastsrm = fastsrm.transform(\n", - " files[subjects_train, :][:, 1:],\n", - " subjects_indexes=subjects_train\n", - " )\n", - "\n", - " shared_response_probsrm = np.mean(\n", - " [load_and_concat(files[subjects_train, :][:, 1:])[i].T.dot(\n", - " probsrm.w_[subjects_train[i]]\n", - " ) for i in range(len(subjects_train))], axis=0)\n", - " \n", - " # In ProbSRM sessions are concatenated\n", - " Y_pred_probsrm = np.array([shared_response_probsrm.dot(probsrm.w_[s_test].T)\n", - " for s_test in subjects_test])\n", - "\n", - " # The reconstructed data with FastSRM is a matrix of size (n_test_subjects, n_test_sessions)\n", - " Y_pred_fastsrm = fastsrm.inverse_transform(\n", - " shared_response_fastsrm,\n", - " subjects_indexes=subjects_test\n", - " )\n", - " # Let us make fastSRM match the shape of ProbSRM\n", - " Y_pred_fastsrm = Y_pred_fastsrm[:, 0, :, :]\n", - "\n", - " # This is the true data we are trying to reconstruct\n", - " Y_true = np.array([np.load(file) for file in files[subjects_test, :][:, 1]])\n", - " \n", - " \n", - " for i in range(len(subjects_test)):\n", - " var_e = 1 - (Y_pred_fastsrm[i] - Y_true[i]).var(axis=0)\n", - " var_e_fastsrm.append(var_e)\n", "\n", - " \n", - " for i in range(len(subjects_test)):\n", - " var_e = 1 - (Y_pred_probsrm[i] - Y_true[i]).var(axis=0)\n", - " var_e_probsrm.append(var_e)" + "# List in which we record for each algo the R2 scores per voxels averaged across subjects\n", + "r2_mean = {}\n", + "for name, model in models:\n", + " print(\"Running reconstruction experiment for %s\" % name)\n", + " t0 = time()\n", + " # List in which we record for each subject the test R2 scores per voxels\n", + " r2_subjects = []\n", + "\n", + " # We divide all subjects into train subjects and test subjects\n", + " for subjects_train, subjects_test in KFold(n_splits=5,\n", + " shuffle=True\n", + " ).split(np.arange(len(subjects))):\n", + "\n", + " # First let us train the model on train subjects during session 1\n", + " # # For this we will use an input format that is supported by both FastSRM and SRM: a list of arrays\n", + " train_subjects_session_1 = load_and_concat(files[subjects_train, :][:, :1])\n", + " train_subjects_session_2 = load_and_concat(files[subjects_train, :][:, 1:])\n", + " test_subjects_session_1 = load_and_concat(files[subjects_test, :][:, :1])\n", + " test_subjects_session_2 = load_and_concat(files[subjects_test, :][:, 1:])\n", + " \n", + " n_subjects_train = len(subjects_train)\n", + " n_subjects_test = len(subjects_test)\n", + "\n", + " # # Let us fit the model on the first session\n", + " model.fit(train_subjects_session_1)\n", + "\n", + "\n", + " # Then let us compute the shared response on the second session\n", + "\n", + " # # With ProbSRM the transform method returns a list of subject-specific responses in shared space\n", + " # # so we need an additional step to aggregate them\n", + " if name == \"probsrm\":\n", + " shared_session_2 = model.transform(train_subjects_session_2)\n", + " shared_session_2 = np.mean(shared_session_2, axis=0)\n", + "\n", + " # # With FastSRM we can specify the desired behavior. \n", + " # # Because we specified aggregate=\"mean\" transform directly returns\n", + " # # the mean of subject-specific responses (aggregate=None would result \n", + " # # in the same behavior as in ProbSRM)\n", + " if name == \"fastsrm\":\n", + " shared_session_2 = model.transform(train_subjects_session_2)\n", + "\n", + " # Now we add test subjects to the model\n", + "\n", + " # # With ProbSRM we have a function transform subject that returns the basis for \n", + " # # one specific subject. We will save this in a list\n", + " if name == \"probsrm\":\n", + " list_basis_test_subjects = [model.transform_subject(x) for x in test_subjects_session_1]\n", + "\n", + " # # With FastSRM we have a function add subject that takes a list of subjects\n", + " # # new subjects are added to internal basis_list (that can be accessed using .basis_list\n", + " # # but this is usually not necessary)\n", + " # # With FastSRM we need to specify what is the shared response that is used to learn the alignment\n", + " if name == \"fastsrm\":\n", + " shared_session_1 = model.transform(train_subjects_session_1)\n", + " model.add_subjects(test_subjects_session_1, shared_session_1)\n", + " \n", + " # Then we try to reconstruct the data of test subjects during session 2\n", + "\n", + " # # ProbSRM does not provide an inverse transform so we need to implement this\n", + " # # (it is rather easy)\n", + " if name == \"probsrm\":\n", + " reconstructed_data_test_subjects_session_2 = [\n", + " list_basis_test_subjects[i].dot(shared_session_2)\n", + " for i in range(n_subjects_test)]\n", + "\n", + " # # FastSRM provides an inverse transform but we need to specify what to reconstruct\n", + " # # New subjects are added at the end of the list so we need to reconstruct the data of the last \n", + " # # n_subjects_test subjects\n", + " if name == \"fastsrm\":\n", + " reconstructed_data_test_subjects_session_2 = model.inverse_transform(\n", + " shared_session_2,\n", + " subjects_indexes=np.arange(n_subjects_train, n_subjects_train + n_subjects_test))\n", + " \n", + " # This is the true data we are trying to reconstruct ()\n", + " real_data_test_subjects_session_2 = np.array([np.load(file) for file in files[subjects_test, :][:, 1]])\n", + "\n", + " for i in range(n_subjects_test):\n", + " diff = reconstructed_data_test_subjects_session_2[i] - real_data_test_subjects_session_2[i]\n", + " r2 = 1 - diff.var(axis=1)\n", + " r2_subjects.append(r2)\n", + " \n", + " r2_mean[name] = np.mean(r2_subjects, axis=0)\n", + " print(\"Done in %.2f\" % (time() - t0))" ] }, { @@ -415,20 +513,20 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "R2 score FastSRM: 0.088\n", - "R2 score ProbSRM: 0.081\n" + "R2 score probsrm: 0.036\n", + "R2 score fastsrm: 0.039\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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d21OobArAKALMW8mtzn3QMGa2FZ4wkAMhDKptm42Be15I+3bI+rCNhzOYf0sYxt8suZljbV1u2tfCoV9qkvcP3GL6r5Kofon7oXYfnfPlJ6+5YOtcU1YQ8K1fbza75XLnuu0GbZs2YLXvva1uPvuu9Hd3Y0FCxZg5cqVyQ+ol19+uTbw/N3vfheXX3457rjjjhGp37BFEXzNa14DoPLDqBEHH3wwAOA3v/lNbpnHHHMMnnrqKSxevBi77bYbLrzwQqxYsQJHHXVUst9Xv/pVXHvttbj44otrnqOrr74ahx9+OM4555xaGbfddhve8Y534Ic//GFSn7333hsf//jHMWXKFFxwwQX4zne+g7e85S0AgA9/+MM455xzcNZZZ+GXv/wldtppJyxcuBDTp0/PrQMA/N//+39x22234ZhjjsF73vMefPGLX8Quu+yCBQsW4PTTT8drXvMaXHTRRfjNb36DCy64AI7jOM2ybNmyVlfBGaME29t1111bXBPHccYCDz30EObPn1+bS3z00UfjlltuSX5gsapn48aNI5rPd0h/YI0fPx4AsMcee2D58uV45JFHGsqjpk6digsuuAD33Xcffvazn+WWP3XqVPz1X/91zc26ceNGfOMb38A+++yDX/866vNvuukmfPrTcZ7APvvsg2OOOQbHH388vv71rwOoTKL9xS9+gX/+53+uzR0DKgEIDj74YDz7bCVM7DPPPIMf/vCHOOyww3DnnXfiLW95C+66666aTAEAbr755kxd6+sQWLVqVS3M8T333IMjjzwSixcvxh577IFSqTKy/Jd/+Zc44ogj/AfWKCJ4X0OwgDBBu9O54YYbahODP/ShD+Xs7Qw1YXL/pEmV0f0ddtih0e4dw4oVK3DiiSe2uhpOlYsvvrgWWGeoJamOYxGCpoTAT53CpZdeitNOO63V1Wh71qxZg7lzY3qH7u7u2lQl5rLLLsOXv/xlbNq0CffeO3JzyIfsB9bOO+9c+6IFAC+88AIWLFiATZs2mcd89atfxS677IK//uu/buocd999d6K5vvnmmzFu3DgsWLAg+YF12223JcctWLAA48aNq81JAIByuYybbroJn/zkJ5N9f/azn9V+XAHAj370I/T39+Mtb3kL7rzzTjz66KP4yEc+gvPOOw+33XYbfvrTn2Lr1q2ZutbXIfDf//3fSR2efvppDAwM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\n", "text/plain": [ "
" ] @@ -438,7 +536,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1gAAAC0CAYAAACJ8Y3MAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOyde5RcVZX/d+X9oCEkdBLpjvSE8BBQ0SEanXFUFgiTWTCoII9xEDIgkgBhlCXMOD95DCPCMECSDs8IhJlJwNcYfICiDA8HCYIJD6MhEDqmE9IEioQyLSSE/v1Rte/5nrrf3bc66e6qrt6ftZI6fZ/nnrvvuVVnf8/eua6uri5xHMdxHMdxHMdxdpsh1a6A4ziO4ziO4zhOveA/sBzHcRzHcRzHcXoJ/4HlOI7jOI7jOI7TSwzrycaTJ0+Wjo6OvqqLA0yaNEk2bdpU7Wo4juM4juM4jtMDcj0JcpHL5fqyLk4ZHn/EcRzHcRzHcQYWLhF0HMdxHMdxHMfpJXokEXT6l1zuHOnquqXa1XD6kJtvvllERM4998ewdC+y5ZNJafHifxYRkdNPPz3z+LncnqXScFi6L5QPKH3ugGXbklJX14OZ53AGLtdff72IiIwaNUpmz15cWtoIW2xOSjfddIaIiHzpS1/KPO7kktqhQxqSZQ1SSG1XkPFJuavrtQpr7dQjudz+Sbmr68XdOtb+htrmRVeFOI7TT7gHy3Ecx3Ecx3Ecp5foFQ/WpZdeKpdddlny96ZNm+TJJ5+Uf/7nf5Znn302WX7ggQfKBRdcIEceeaTst99+smnTJvnhD38ol156qWzdurXbc4wZM0a+9a1vydFHHy0TJkyQM844QxYvXtztPpUwffp0mTlzplx++eW7fay+4O677xYRkVdeeUUuuOCCKtfG2R1uu+02ERGZOHGiiIg0NDTIYYcdVlr7Y2OvNE1NTSIismTJEhGRJPDMP/7jPyYeiUmTJvVGlZ064o477hARkZEjR8qwYcWuf+rUqcmyLA466CARCXaXz+dFRGTEiBHypz/9SUREJkyYsMv1u+eee0RE5LXXXpPZs2fv8nGc2kU99mPGjIk+R40aFW3305/+NLGpN954I/o877zzUsedN2+eiIiMGzeu2/PfdNNNcu655+5q9Z06orW1VUSKfd+QIUVfw9tvvy0iIuecc07V6uXUD70S5OLSSy+VCy+8UI499lgREWlpaZErrrhC9tprL3nPe94jr7/+uoiIzJkzR8466yy5/fbb5ZlnnpGpU6fKlVdeKe3t7TJjxoxugzp85Stfkcsvv1xOP/102bBhg7z44ovy6quv9uRaKXPmzJHW1tYaDeDxdyIyAv5G6dY9/V4bZ9fJ5WaVSltg6Soo43KV8OGXhTVQVjlfkP1dd92Xkx9Y1335y8lyHbYoyFRyfJEgB0OJILIByi+IiEhX1xvGtk6tsif0b0fAcrzramGdsAyFpXkp/mj/7/++rvh32Q+s7154Yeq8G6G8Dcp6DpQINkienld5zeVdAxq0wQJIRxWUkI4h+//LggUikv6BdQwcl/28wp51R9mnCLc1EZEH3d7qiilgJ9r/5KH/mQT9j74VnzVsYAYca3vpE22K2Zd1LKd+6bU5WG+//bYsX75cRESWL18ubW1t8vjjj8uxxx4rS5cuFRGRpUuXysKFC5N9Hn74YWlvb5ef/exn8rGPfUweeeQR8/gHH3ywrF69Wr7//e/3VpV7lSFDhsjQoUNlxw7ri2rvcsMNN8iF5AuNU3ssW7asz88xevRoueGGG2TPPffM3tgZFHznO98REZHx48dnbNkzmpubRUSSvq6zs1MaGtJfmJ3BzaJFi0REZJ999umV46kd33TTTSJS9H5t27atu12cQc6tt94qo0eP3uX91eu/c+dOESna4Guv7dpc0TvuuCOxV+aFdXaN+++/X+bOnSs7d+6Us846Sy655JJo/c033ywLFy6UoUOHyh577CG33nqrHHLIIf1St17zYJ133nnS2BgmR48aNUr+9Kc/ycUXXyzXXHONecx99tlHNm/eLKeeemoihyvnpZdekpaWllRdDjroILnsssvkL/7iL2TChAny0ksvyW233Sbz589PvGHDhg2Tq666Sj73uc/JpEmT5LXXXpPly5fLySefLKeddprceeed0XEfeugh+eQnPylNTU1y3XXXySc+8QlpaGiQjRs3ypIlS+TrX/+6iBQflsMOO0yuvPJK+bd/+zc58MAD5cgjj5T9999f7rzzTvngBz8o1113nXzoQx+S559/XmbNmiWrV6+W1tZW+cxnPiOvvfaafO1rXzOvucinRGQs/I3jwepZQGllGPfr6vIcWtUml/tH+EvHzPAePpaUcPQ+eJtwHBfHYcuPKTLeGP3vEJUKoteKjfNiYAMcJHgazrE2tZd7FWqPXO5I+KsYpGK8PJcswVfLmNSW8d1Ha81Lc2qLqRLyIk4jdVlHji8SbBS9ZQjWiw1Zud3VNhNK3xWwp9nON03ecHifcVtmI2jDB0BZezHsA9ugrPaIP8vwDTuGbIvPwBtudzULBkkJhF6nGTykzObwDYhvSwbaSVPpE232aSir/eC5MIwVPiOqadnkdlYRO3fulAMPPFAeeOABaW5ulunTp8vSpUujH1BvvPFGMvB87733yo033ij3339/v9Svz6IIvvvd7xaR4o+j7vjIRz4iIiLPP/+8uc2nP/1pufLKK2Xq1Kly5plnJsubmppk9erV8t///d9SKBTk8MMPl8svv1xGjx4t3/zmN0VE5J/+6Z/k7/7u7+SSSy6Rl156SSZPniwzZ86UoUOHyo9//GO59tpr5aKLLpIZM2aISNB533XXXTJ69Gj54he/KFu2bJGpU6fKwQcfHNWrpaVFrrnmGrniiitk06ZN8tJLL8n++xcf8sWLF0tra6tcffXV8s1vflO++93vyhNPPCEvvviinHjiiTJr1iy566675NFHH5UNGzaI4zjO7jJ//vxqV8EZpKjtTZ48uco1cRxnMPDEE0/ItGnTkrnEp5xyiixbtiz6gYWqnm3btvXrdKBe/YE1dOhQERHZb7/9pLW1VVasWNGtPGr06NFy9dVXy0MPPSS/+c1vzO1WrlwpmzdvlkmTJiUyRBGRBx98UB58MISR/uUvfyljxoyRs88+O/mB9aEPfUiWLFkid911V7KdSmfefPNNaWtrExGJjqv7nXrqqfKjH/1IRIpyxnL22WcfOeqoo+Tpp59Orbv22muTc+ZyOfnJT34iDz30kPzLv/yLiBQN48QTT5TjjjsumfjrDFzUE6nBAnSCdr1z9913JxODP//5z1e5NoMPndw/YkRxruZee7EQ//XHokWL5Kyzzqp2NZwSN9xwQxJYp7clqY5jod+dNPBTvbBgwQI5//zzq12NmmfDhg0yZcqU5O/m5ubUd3kRkYULF8p1110n27dvj34z9DW99gNrn332Sb5oiYi8+uqrMn36dNm+3RIGiHzrW9+SiRMnyt/8zd/s0jlHjhyZeKje/e53J18yRIo/9nbu3CkrV66Uc889Vzo6OuT++++Pohp2x8qVK+Wqq66SCRMmyIMPPijr169PbdPe3k5/XImI/OIXv0jKL7xQDA6AN/aNN96QzZs3Z3QMIyQWKIRAB80lSRiGwNgOLvCQ/yh84erqSl+D0zuceuoNpRKKB1B0MJysD0KEQrRc75kVWXNE2adIPhLFoKhGhQko0EJRAhNrbaFl3Quf6C+eempSnvv3fy8iLt+qBrNnz4O/xqXKeTkoWbJGVidllMIwS4nleUUbQTlrlpRvuLE+nxHkwJowrlx89tly8dlnR+dwWU3/E8uyguXo3cWeBK2S9YwsCIpI6OXQ1tCusD9iM7JYhj9Lmor769VgIJZcLkTJ9Lxt1edWiAipdwn7FpQFos0pKA9F2aluy9+EsS3r27rTWI/f4BRm37j8ggsWygUXFOMVdHX9nhzB6Qlz5syROXPmyJIlS+TKK6/MjEB+7LHHdhtEb5999qlIZthrebC2bNkiRxxxhHz4wx+WL37xizJixAhZsmSJ6Y67+uqr5dOf/rSccMIJmTJCi6uvvlouuugiufXWW2XmzJlyxBFHyL/+67+KSAj7euWVV8rChQtl9uzZ8swzz8j69esrCnd+8skny5NPPinXX3+9/OEPf5AVK1bIkUceGW2j4bEZW7aEx1F/ZOIyXV4enrbatLS0yOjRo2WPPfaQyZMnyxlnnCF//OMfq12tPqOlpUV+/vOf93i/RYsWyaJFi+T+++/vNz3vQGHx4sU9TqEwmOxuV20OWbp0qSxdulT233//RJbs9IzBZHMivWN3SmtraxLm2ukZbne9g9tfz6hHu2tqaoqcH+3t7d06LU455RT5wQ9+kHncV199VZ588knzX6URzHvtB9bbb78tTz31lDzxxBNy2223yZw5c+QjH/mInHTSSaltL7zwQrnooovk9NNPl1/+8pe7fM6TTjpJFixYIP/+7/8uv/jFL+Spp56KvGgiIm+99ZZceuml8md/9mdywAEHyD333CPz5s2TY445pttjb9y4Uc4880yZMGGCzJgxQzZt2iT33ntvJH/oQXyQAcUPf/hD+eMf/ygrV66UFStWyFVXXVXtKjmDALc7p79xm3OqgdudUw3qze6mT58ua9askZdeekm2b98ud999txx//PHRNmvWBOXXj3/8YznggAPKD0PYKUWfpfWvMvosyMV//dd/ycUXXywXX3yxfPvb306Wn3baafIf//Ef8uUvfzmZ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\n", "text/plain": [ "
" ] @@ -448,30 +546,19 @@ } ], "source": [ - "# Average test R2 scores\n", - "var_e_fast = np.mean(var_e_fastsrm, axis=0)\n", - "var_e_prob = np.mean(var_e_probsrm, axis=0)\n", - "\n", "masker = NiftiMasker(\n", " mask_img=mask_fn).fit()\n", "\n", - "plot_stat_map(\n", - " masker.inverse_transform(var_e_fast),\n", - " display_mode=\"z\",\n", - " cut_coords=[0, 5, 10, 15, 20],\n", - " vmax=0.3\n", - ")\n", - "\n", - "plot_stat_map(\n", - " masker.inverse_transform(var_e_prob),\n", - " display_mode=\"z\",\n", - " cut_coords=[0, 5, 10, 15, 20],\n", - " vmax=0.3\n", - ")\n", - "\n", - "# R2 score in a ROI given by areas where ProbSRM performs the best\n", - "print(\"R2 score FastSRM: %.3f\" % np.mean(var_e_fast[var_e_prob > 0.05]))\n", - "print(\"R2 score ProbSRM: %.3f\" % np.mean(var_e_prob[var_e_prob > 0.05]))" + "for name in [\"probsrm\", \"fastsrm\"]:\n", + " # R2 score in a ROI given by areas where ProbSRM performs well\n", + " print(\"R2 score %s: %.3f\" % (name, np.mean(r2_mean[name][r2_mean[\"probsrm\"] > 0.01])))\n", + " plot_stat_map(\n", + " masker.inverse_transform(r2_mean[name]),\n", + " display_mode=\"z\",\n", + " cut_coords=[0, 5, 10, 15, 20],\n", + " vmax=0.3,\n", + " title=\"R2 %s\" % name\n", + " )" ] } ], @@ -491,7 +578,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.1" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/examples/funcalign/requirements.txt b/examples/funcalign/requirements.txt index bf4f33004..a658617cb 100644 --- a/examples/funcalign/requirements.txt +++ b/examples/funcalign/requirements.txt @@ -1,3 +1,4 @@ matplotlib nilearn notebook +wget