def test_with_files(): # Standard masking data = np.zeros((40, 40, 40, 2)) data[20, 20, 20] = 1 data_img = Nifti1Image(data, np.eye(4)) with testing.write_tmp_imgs(data_img) as filename: masker = NiftiMasker() masker.fit(filename) masker.transform(filename)
def test_high_variance_confounds(): rng = np.random.RandomState(42) img, mask, conf = _simu_img() hv_confounds = high_variance_confounds(img) masker1 = NiftiMasker(standardize=True, detrend=False, high_variance_confounds=False, mask_img=mask).fit() tseries1 = masker1.transform(img, confounds=[hv_confounds, conf]) masker2 = NiftiMasker(standardize=True, detrend=False, high_variance_confounds=True, mask_img=mask).fit() tseries2 = masker2.transform(img, confounds=conf) np.testing.assert_array_equal(tseries1, tseries2)
def test_joblib_cache(): from joblib import hash, Memory mask = np.zeros((40, 40, 40)) mask[20, 20, 20] = 1 mask_img = Nifti1Image(mask, np.eye(4)) with testing.write_tmp_imgs(mask_img, create_files=True) as filename: masker = NiftiMasker(mask_img=filename) masker.fit() mask_hash = hash(masker.mask_img_) get_data(masker.mask_img_) assert mask_hash == hash(masker.mask_img_) # Test a tricky issue with memmapped joblib.memory that makes # imgs return by inverse_transform impossible to save cachedir = mkdtemp() try: masker.memory = Memory(location=cachedir, mmap_mode='r', verbose=0) X = masker.transform(mask_img) # inverse_transform a first time, so that the result is cached out_img = masker.inverse_transform(X) out_img = masker.inverse_transform(X) out_img.to_filename(os.path.join(cachedir, 'test.nii')) finally: # enables to delete "filename" on windows del masker shutil.rmtree(cachedir, ignore_errors=True)
def test_5d(): mask = np.zeros((10, 10, 10)) mask[3:7, 3:7, 3:7] = 1 mask_img = Nifti1Image(mask, np.eye(4)) # Test that, in list of 4d images with last dimension=1, they are # considered as 3d rng = np.random.RandomState(42) data_5d = [rng.random_sample((10, 10, 10, 3)) for i in range(5)] data_5d = [nibabel.Nifti1Image(d, np.eye(4)) for d in data_5d] masker = NiftiMasker(mask_img=mask_img) masker.fit() with pytest.raises(DimensionError, match="Input data has incompatible dimensionality: " "Expected dimension is 4D and you provided " "a list of 4D images \\(5D\\)."): masker.transform(data_5d)
def test_mask_4d(): # Dummy mask mask = np.zeros((10, 10, 10), dtype=int) mask[3:7, 3:7, 3:7] = 1 mask_bool = mask.astype(bool) mask_img = Nifti1Image(mask, np.eye(4)) # Dummy data data = np.zeros((10, 10, 10, 5), dtype=int) data[..., 0] = 1 data[..., 1] = 2 data[..., 2] = 3 data_img_4d = Nifti1Image(data, np.eye(4)) data_imgs = [ index_img(data_img_4d, 0), index_img(data_img_4d, 1), index_img(data_img_4d, 2) ] # check whether transform is indeed selecting niimgs subset sample_mask = np.array([0, 2]) masker = NiftiMasker(mask_img=mask_img) masker.fit() data_trans = masker.transform(data_imgs, sample_mask=sample_mask) data_trans_img = index_img(data_img_4d, sample_mask) data_trans_direct = get_data(data_trans_img)[mask_bool, :] data_trans_direct = np.swapaxes(data_trans_direct, 0, 1) assert_array_equal(data_trans, data_trans_direct) masker = NiftiMasker(mask_img=mask_img) masker.fit() data_trans2 = masker.transform(data_img_4d, sample_mask=sample_mask) assert_array_equal(data_trans2, data_trans_direct) diff_sample_mask = np.array([2, 4]) data_trans_img_diff = index_img(data_img_4d, diff_sample_mask) data_trans_direct_diff = get_data(data_trans_img_diff)[mask_bool, :] data_trans_direct_diff = np.swapaxes(data_trans_direct_diff, 0, 1) masker = NiftiMasker(mask_img=mask_img) masker.fit() data_trans3 = masker.transform(data_img_4d, sample_mask=diff_sample_mask) assert_array_equal(data_trans3, data_trans_direct_diff)
def _confounds_regression(standardize_signal=True, standardize_confounds=True): rng = np.random.RandomState(42) img, mask, conf = _simu_img() masker = NiftiMasker(standardize=standardize_signal, standardize_confounds=standardize_confounds, detrend=False, mask_img=mask).fit() tseries = masker.transform(img, confounds=conf) if standardize_confounds: conf = StandardScaler(with_std=False).fit_transform(conf) cov_mat = _cov_conf(tseries, conf) return np.sum(np.abs(cov_mat))
def test_multi_pca_score(): shape = (6, 8, 10, 5) affine = np.eye(4) rng = np.random.RandomState(0) # Create a "multi-subject" dataset imgs = [] for i in range(8): this_img = rng.normal(size=shape) imgs.append(nibabel.Nifti1Image(this_img, affine)) mask_img = nibabel.Nifti1Image(np.ones(shape[:3], dtype=np.int8), affine) # Assert that score is between zero and one multi_pca = MultiPCA(mask=mask_img, random_state=0, memory_level=0, n_components=3) multi_pca.fit(imgs) s = multi_pca.score(imgs) assert np.all(s <= 1) assert np.all(0 <= s) # Assert that score does not fail with single subject data multi_pca = MultiPCA(mask=mask_img, random_state=0, memory_level=0, n_components=3) multi_pca.fit(imgs[0]) s = multi_pca.score(imgs[0]) assert isinstance(s, float) assert(0. <= s <= 1.) # Assert that score is one for n_components == n_sample # in single subject configuration multi_pca = MultiPCA(mask=mask_img, random_state=0, memory_level=0, n_components=5) multi_pca.fit(imgs[0]) s = multi_pca.score(imgs[0]) assert_almost_equal(s, 1., 1) # Per component score multi_pca = MultiPCA(mask=mask_img, random_state=0, memory_level=0, n_components=5) multi_pca.fit(imgs[0]) masker = NiftiMasker(mask_img).fit() s = multi_pca._raw_score(masker.transform(imgs[0]), per_component=True) assert s.shape == (5,) assert np.all(s <= 1) assert np.all(0 <= s)
def test_nifti_labels_masker_with_mask(): shape = (13, 11, 12) affine = np.eye(4) fmri_img, mask_img = generate_random_img(shape, affine=affine, length=3) labels_img = data_gen.generate_labeled_regions(shape, affine=affine, n_regions=7) masker = NiftiLabelsMasker(labels_img, resampling_target=None, mask_img=mask_img) signals = masker.fit().transform(fmri_img) bg_masker = NiftiMasker(mask_img).fit() masked_labels = bg_masker.inverse_transform( bg_masker.transform(labels_img)) masked_masker = NiftiLabelsMasker(masked_labels, resampling_target=None, mask_img=mask_img) masked_signals = masked_masker.fit().transform(fmri_img) assert np.allclose(signals, masked_signals)
def test_4d_single_scan(): mask = np.zeros((10, 10, 10)) mask[3:7, 3:7, 3:7] = 1 mask_img = Nifti1Image(mask, np.eye(4)) # Test that, in list of 4d images with last dimension=1, they are # considered as 3d rng = np.random.RandomState(42) data_5d = [rng.random_sample((10, 10, 10, 1)) for i in range(5)] data_4d = [d[..., 0] for d in data_5d] data_5d = [nibabel.Nifti1Image(d, np.eye(4)) for d in data_5d] data_4d = [nibabel.Nifti1Image(d, np.eye(4)) for d in data_4d] masker = NiftiMasker(mask_img=mask_img) masker.fit() data_trans_5d = masker.transform(data_5d) data_trans_4d = masker.transform(data_4d) assert_array_equal(data_trans_4d, data_trans_5d)
def test_matrix_orientation(): """Test if processing is performed along the correct axis.""" # the "step" kind generate heavyside-like signals for each voxel. # all signals being identical, standardizing along the wrong axis # would leave a null signal. Along the correct axis, the step remains. fmri, mask = data_gen.generate_fake_fmri(shape=(40, 41, 42), kind="step") masker = NiftiMasker(mask_img=mask, standardize=True, detrend=True) timeseries = masker.fit_transform(fmri) assert (timeseries.shape[0] == fmri.shape[3]) assert (timeseries.shape[1] == get_data(mask).sum()) std = timeseries.std(axis=0) assert (std.shape[0] == timeseries.shape[1]) # paranoid assert (not np.any(std < 0.1)) # Test inverse transform masker = NiftiMasker(mask_img=mask, standardize=False, detrend=False) masker.fit() timeseries = masker.transform(fmri) recovered = masker.inverse_transform(timeseries) np.testing.assert_array_almost_equal(get_data(recovered), get_data(fmri))
def test_rena_clustering(): data_img, mask_img = generate_fake_fmri(shape=(10, 11, 12), length=5) data = get_data(data_img) mask = get_data(mask_img) X = np.empty((data.shape[3], int(mask.sum()))) for i in range(data.shape[3]): X[i, :] = np.copy(data[:, :, :, i])[get_data(mask_img) != 0] nifti_masker = NiftiMasker(mask_img=mask_img).fit() n_voxels = nifti_masker.transform(data_img).shape[1] rena = ReNA(mask_img, n_clusters=10) X_red = rena.fit_transform(X) X_compress = rena.inverse_transform(X_red) assert 10 == rena.n_clusters_ assert X.shape == X_compress.shape memory = Memory(location=None) rena = ReNA(mask_img, n_clusters=-2, memory=memory) pytest.raises(ValueError, rena.fit, X) rena = ReNA(mask_img, n_clusters=10, scaling=True) X_red = rena.fit_transform(X) X_compress = rena.inverse_transform(X_red) for n_iter in [-2, 0]: rena = ReNA(mask_img, n_iter=n_iter, memory=memory) pytest.raises(ValueError, rena.fit, X) for n_clusters in [1, 2, 4, 8]: rena = ReNA(mask_img, n_clusters=n_clusters, n_iter=1, memory=memory).fit(X) assert n_clusters != rena.n_clusters_ del n_voxels, X_red, X_compress
def test_plot_img_comparison(): """Tests for plot_img_comparision.""" fig, axes = plt.subplots(2, 1) axes = axes.ravel() kwargs = {"shape": (3, 2, 4), "length": 5} query_images, mask_img = generate_fake_fmri( rand_gen=np.random.RandomState(0), **kwargs) # plot_img_comparison doesn't handle 4d images ATM query_images = list(iter_img(query_images)) target_images, _ = generate_fake_fmri(rand_gen=np.random.RandomState(1), **kwargs) target_images = list(iter_img(target_images)) target_images[0] = query_images[0] masker = NiftiMasker(mask_img).fit() correlations = plot_img_comparison(target_images, query_images, masker, axes=axes, src_label="query") assert len(correlations) == len(query_images) assert correlations[0] == pytest.approx(1.) ax_0, ax_1 = axes # 5 scatterplots assert len(ax_0.collections) == 5 assert len(ax_0.collections[0].get_edgecolors() == masker.transform( target_images[0]).ravel().shape[0]) assert ax_0.get_ylabel() == "query" assert ax_0.get_xlabel() == "image set 1" # 5 regression lines assert len(ax_0.lines) == 5 assert ax_0.lines[0].get_linestyle() == "--" assert ax_1.get_title() == "Histogram of imgs values" assert len(ax_1.patches) == 5 * 2 * 128 correlations_1 = plot_img_comparison(target_images, query_images, masker, plot_hist=False) assert np.allclose(correlations, correlations_1)
def test_auto_mask(): # This mostly a smoke test data = np.zeros((9, 9, 9)) data[3:-3, 3:-3, 3:-3] = 10 img = Nifti1Image(data, np.eye(4)) masker = NiftiMasker() # Smoke test the fit masker.fit(img) # Smoke test the transform # With a 4D img masker.transform([ img, ]) # With a 3D img masker.transform(img) # check exception when transform() called without prior fit() masker2 = NiftiMasker(mask_img=img) with pytest.raises(ValueError, match='has not been fitted. '): masker2.transform(img)
class FirstLevelModel(BaseGLM): """ Implementation of the General Linear Model for single session fMRI data. Parameters ---------- t_r : float This parameter indicates repetition times of the experimental runs. In seconds. It is necessary to correctly consider times in the design matrix. This parameter is also passed to :func:`nilearn.signal.clean`. Please see the related documentation for details. slice_time_ref : float, optional This parameter indicates the time of the reference slice used in the slice timing preprocessing step of the experimental runs. It is expressed as a percentage of the t_r (time repetition), so it can have values between 0. and 1. Default=0. %(hrf_model)s Default='glover'. drift_model : string, optional This parameter specifies the desired drift model for the design matrices. It can be 'polynomial', 'cosine' or None. Default='cosine'. high_pass : float, optional This parameter specifies the cut frequency of the high-pass filter in Hz for the design matrices. Used only if drift_model is 'cosine'. Default=0.01. drift_order : int, optional This parameter specifies the order of the drift model (in case it is polynomial) for the design matrices. Default=1. fir_delays : array of shape(n_onsets) or list, optional In case of FIR design, yields the array of delays used in the FIR model, in scans. Default=[0]. min_onset : float, optional This parameter specifies the minimal onset relative to the design (in seconds). Events that start before (slice_time_ref * t_r + min_onset) are not considered. Default=-24. mask_img : Niimg-like, NiftiMasker object or False, optional Mask to be used on data. If an instance of masker is passed, then its mask will be used. If no mask is given, it will be computed automatically by a NiftiMasker with default parameters. If False is given then the data will not be masked. target_affine : 3x3 or 4x4 matrix, optional This parameter is passed to nilearn.image.resample_img. Please see the related documentation for details. target_shape : 3-tuple of integers, optional This parameter is passed to nilearn.image.resample_img. Please see the related documentation for details. %(smoothing_fwhm)s memory : string, optional Path to the directory used to cache the masking process and the glm fit. By default, no caching is done. Creates instance of joblib.Memory. memory_level : integer, optional Rough estimator of the amount of memory used by caching. Higher value means more memory for caching. standardize : boolean, optional If standardize is True, the time-series are centered and normed: their variance is put to 1 in the time dimension. Default=False. signal_scaling : False, int or (int, int), optional If not False, fMRI signals are scaled to the mean value of scaling_axis given, which can be 0, 1 or (0, 1). 0 refers to mean scaling each voxel with respect to time, 1 refers to mean scaling each time point with respect to all voxels & (0, 1) refers to scaling with respect to voxels and time, which is known as grand mean scaling. Incompatible with standardize (standardize=False is enforced when signal_scaling is not False). Default=0. noise_model : {'ar1', 'ols'}, optional The temporal variance model. Default='ar1'. verbose : integer, optional Indicate the level of verbosity. By default, nothing is printed. If 0 prints nothing. If 1 prints progress by computation of each run. If 2 prints timing details of masker and GLM. If 3 prints masker computation details. Default=0. n_jobs : integer, optional The number of CPUs to use to do the computation. -1 means 'all CPUs', -2 'all CPUs but one', and so on. Default=1. minimize_memory : boolean, optional Gets rid of some variables on the model fit results that are not necessary for contrast computation and would only be useful for further inspection of model details. This has an important impact on memory consumption. Default=True. subject_label : string, optional This id will be used to identify a `FirstLevelModel` when passed to a `SecondLevelModel` object. Attributes ---------- labels_ : array of shape (n_voxels,), a map of values on voxels used to identify the corresponding model results_ : dict, with keys corresponding to the different labels values. Values are SimpleRegressionResults corresponding to the voxels, if minimize_memory is True, RegressionResults if minimize_memory is False Notes ----- This class is experimental. It may change in any future release of Nilearn. """ def __init__(self, t_r=None, slice_time_ref=0., hrf_model='glover', drift_model='cosine', high_pass=.01, drift_order=1, fir_delays=[0], min_onset=-24, mask_img=None, target_affine=None, target_shape=None, smoothing_fwhm=None, memory=Memory(None), memory_level=1, standardize=False, signal_scaling=0, noise_model='ar1', verbose=0, n_jobs=1, minimize_memory=True, subject_label=None): # design matrix parameters self.t_r = t_r self.slice_time_ref = slice_time_ref self.hrf_model = hrf_model self.drift_model = drift_model self.high_pass = high_pass self.drift_order = drift_order self.fir_delays = fir_delays self.min_onset = min_onset # glm parameters self.mask_img = mask_img self.target_affine = target_affine self.target_shape = target_shape self.smoothing_fwhm = smoothing_fwhm if isinstance(memory, str): self.memory = Memory(memory) else: self.memory = memory self.memory_level = memory_level self.standardize = standardize if signal_scaling is False: self.signal_scaling = signal_scaling elif signal_scaling in [0, 1, (0, 1)]: self.signal_scaling = signal_scaling self.standardize = False else: raise ValueError('signal_scaling must be "False", "0", "1"' ' or "(0, 1)"') self.noise_model = noise_model self.verbose = verbose self.n_jobs = n_jobs self.minimize_memory = minimize_memory # attributes self.labels_ = None self.results_ = None self.subject_label = subject_label @property def scaling_axis(self): warn(DeprecationWarning( "Deprecated. `scaling_axis` will be removed in 0.11.0. " "Please use `signal_scaling` instead." )) return self.signal_scaling def fit(self, run_imgs, events=None, confounds=None, sample_masks=None, design_matrices=None, bins=100): """Fit the GLM For each run: 1. create design matrix X 2. do a masker job: fMRI_data -> Y 3. fit regression to (Y, X) Parameters ---------- run_imgs : Niimg-like object or list of Niimg-like objects, Data on which the GLM will be fitted. If this is a list, the affine is considered the same for all. events : pandas Dataframe or string or list of pandas DataFrames \ or strings, optional fMRI events used to build design matrices. One events object expected per run_img. Ignored in case designs is not None. If string, then a path to a csv file is expected. confounds : pandas Dataframe, numpy array or string or list of pandas DataFrames, numpy arrays or strings, optional Each column in a DataFrame corresponds to a confound variable to be included in the regression model of the respective run_img. The number of rows must match the number of volumes in the respective run_img. Ignored in case designs is not None. If string, then a path to a csv file is expected. sample_masks : array_like, or list of array_like, optional shape of array: (number of scans - number of volumes removed, ) Indices of retained volumes. Masks the niimgs along time/fourth dimension to perform scrubbing (remove volumes with high motion) and/or remove non-steady-state volumes. Default=None. .. versionadded:: 0.9.2.dev design_matrices : pandas DataFrame or \ list of pandas DataFrames, optional Design matrices that will be used to fit the GLM. If given it takes precedence over events and confounds. bins : int, optional Maximum number of discrete bins for the AR coef histogram. If an autoregressive model with order greater than one is specified then adaptive quantification is performed and the coefficients will be clustered via K-means with `bins` number of clusters. Default=100. """ # Initialize masker_ to None such that attribute exists self.masker_ = None # Raise a warning if both design_matrices and confounds are provided if design_matrices is not None and \ (confounds is not None or events is not None): warn( 'If design matrices are supplied, ' 'confounds and events will be ignored.' ) # Local import to prevent circular imports from nilearn.maskers import NiftiMasker # noqa # Check arguments # Check imgs type if events is not None: _check_events_file_uses_tab_separators(events_files=events) if not isinstance(run_imgs, (list, tuple)): run_imgs = [run_imgs] if design_matrices is None: if events is None: raise ValueError('events or design matrices must be provided') if self.t_r is None: raise ValueError('t_r not given to FirstLevelModel object' ' to compute design from events') else: design_matrices = _check_run_tables(run_imgs, design_matrices, 'design_matrices') # Check that number of events and confound files match number of runs # Also check that events and confound files can be loaded as DataFrame if events is not None: events = _check_run_tables(run_imgs, events, 'events') if confounds is not None: confounds = _check_run_tables(run_imgs, confounds, 'confounds') if sample_masks is not None: sample_masks = _check_run_sample_masks(len(run_imgs), sample_masks) # Learn the mask if self.mask_img is False: # We create a dummy mask to preserve functionality of api ref_img = check_niimg(run_imgs[0]) self.mask_img = Nifti1Image(np.ones(ref_img.shape[:3]), ref_img.affine) if not isinstance(self.mask_img, NiftiMasker): self.masker_ = NiftiMasker(mask_img=self.mask_img, smoothing_fwhm=self.smoothing_fwhm, target_affine=self.target_affine, standardize=self.standardize, mask_strategy='epi', t_r=self.t_r, memory=self.memory, verbose=max(0, self.verbose - 2), target_shape=self.target_shape, memory_level=self.memory_level ) self.masker_.fit(run_imgs[0]) else: # Make sure masker has been fitted otherwise no attribute mask_img_ self.mask_img._check_fitted() if self.mask_img.mask_img_ is None and self.masker_ is None: self.masker_ = clone(self.mask_img) for param_name in ['target_affine', 'target_shape', 'smoothing_fwhm', 't_r', 'memory', 'memory_level']: our_param = getattr(self, param_name) if our_param is None: continue if getattr(self.masker_, param_name) is not None: warn('Parameter %s of the masker' ' overridden' % param_name) setattr(self.masker_, param_name, our_param) self.masker_.fit(run_imgs[0]) else: self.masker_ = self.mask_img # For each run fit the model and keep only the regression results. self.labels_, self.results_, self.design_matrices_ = [], [], [] n_runs = len(run_imgs) t0 = time.time() for run_idx, run_img in enumerate(run_imgs): # Report progress if self.verbose > 0: percent = float(run_idx) / n_runs percent = round(percent * 100, 2) dt = time.time() - t0 # We use a max to avoid a division by zero if run_idx == 0: remaining = 'go take a coffee, a big one' else: remaining = (100. - percent) / max(0.01, percent) * dt remaining = '%i seconds remaining' % remaining sys.stderr.write( "Computing run %d out of %d runs (%s)\n" % (run_idx + 1, n_runs, remaining)) # Build the experimental design for the glm run_img = check_niimg(run_img, ensure_ndim=4) if design_matrices is None: n_scans = get_data(run_img).shape[3] if confounds is not None: confounds_matrix = confounds[run_idx].values if confounds_matrix.shape[0] != n_scans: raise ValueError('Rows in confounds does not match' 'n_scans in run_img at index %d' % (run_idx,)) confounds_names = confounds[run_idx].columns.tolist() else: confounds_matrix = None confounds_names = None start_time = self.slice_time_ref * self.t_r end_time = (n_scans - 1 + self.slice_time_ref) * self.t_r frame_times = np.linspace(start_time, end_time, n_scans) design = make_first_level_design_matrix(frame_times, events[run_idx], self.hrf_model, self.drift_model, self.high_pass, self.drift_order, self.fir_delays, confounds_matrix, confounds_names, self.min_onset ) else: design = design_matrices[run_idx] if sample_masks is not None: sample_mask = sample_masks[run_idx] design = design.iloc[sample_mask, :] else: sample_mask = None self.design_matrices_.append(design) # Mask and prepare data for GLM if self.verbose > 1: t_masking = time.time() sys.stderr.write('Starting masker computation \r') Y = self.masker_.transform(run_img, sample_mask=sample_mask) del run_img # Delete unmasked image to save memory if self.verbose > 1: t_masking = time.time() - t_masking sys.stderr.write('Masker took %d seconds \n' % t_masking) if self.signal_scaling is not False: # noqa Y, _ = mean_scaling(Y, self.signal_scaling) if self.memory: mem_glm = self.memory.cache(run_glm, ignore=['n_jobs']) else: mem_glm = run_glm # compute GLM if self.verbose > 1: t_glm = time.time() sys.stderr.write('Performing GLM computation\r') labels, results = mem_glm(Y, design.values, noise_model=self.noise_model, bins=bins, n_jobs=self.n_jobs) if self.verbose > 1: t_glm = time.time() - t_glm sys.stderr.write('GLM took %d seconds \n' % t_glm) self.labels_.append(labels) # We save memory if inspecting model details is not necessary if self.minimize_memory: for key in results: results[key] = SimpleRegressionResults(results[key]) self.results_.append(results) del Y # Report progress if self.verbose > 0: sys.stderr.write("\nComputation of %d runs done in %i seconds\n\n" % (n_runs, time.time() - t0)) return self def compute_contrast(self, contrast_def, stat_type=None, output_type='z_score'): """Generate different outputs corresponding to the contrasts provided e.g. z_map, t_map, effects and variance. In multi-session case, outputs the fixed effects map. Parameters ---------- contrast_def : str or array of shape (n_col) or list of (string or array of shape (n_col)) where ``n_col`` is the number of columns of the design matrix, (one array per run). If only one array is provided when there are several runs, it will be assumed that the same contrast is desired for all runs. The string can be a formula compatible with `pandas.DataFrame.eval`. Basically one can use the name of the conditions as they appear in the design matrix of the fitted model combined with operators +- and combined with numbers with operators +-`*`/. stat_type : {'t', 'F'}, optional Type of the contrast. output_type : str, optional Type of the output map. Can be 'z_score', 'stat', 'p_value', 'effect_size', 'effect_variance' or 'all'. Default='z_score'. Returns ------- output : Nifti1Image or dict The desired output image(s). If ``output_type == 'all'``, then the output is a dictionary of images, keyed by the type of image. """ if self.labels_ is None or self.results_ is None: raise ValueError('The model has not been fit yet') if isinstance(contrast_def, (np.ndarray, str)): con_vals = [contrast_def] elif isinstance(contrast_def, (list, tuple)): con_vals = contrast_def else: raise ValueError('contrast_def must be an array or str or list of' ' (array or str)') n_runs = len(self.labels_) n_contrasts = len(con_vals) if n_contrasts == 1 and n_runs > 1: warn('One contrast given, assuming it for all %d runs' % n_runs) con_vals = con_vals * n_runs elif n_contrasts != n_runs: raise ValueError('%d contrasts given, while there are %d runs' % (n_contrasts, n_runs)) # Translate formulas to vectors for cidx, (con, design_mat) in enumerate(zip(con_vals, self.design_matrices_) ): design_columns = design_mat.columns.tolist() if isinstance(con, str): con_vals[cidx] = expression_to_contrast_vector( con, design_columns) valid_types = ['z_score', 'stat', 'p_value', 'effect_size', 'effect_variance'] valid_types.append('all') # ensuring 'all' is the final entry. if output_type not in valid_types: raise ValueError( 'output_type must be one of {}'.format(valid_types)) contrast = _compute_fixed_effect_contrast(self.labels_, self.results_, con_vals, stat_type) output_types = (valid_types[:-1] if output_type == 'all' else [output_type]) outputs = {} for output_type_ in output_types: estimate_ = getattr(contrast, output_type_)() # Prepare the returned images output = self.masker_.inverse_transform(estimate_) contrast_name = str(con_vals) output.header['descrip'] = ( '%s of contrast %s' % (output_type_, contrast_name)) outputs[output_type_] = output return outputs if output_type == 'all' else output def _get_voxelwise_model_attribute(self, attribute, result_as_time_series): """Transform RegressionResults instances within a dictionary (whose keys represent the autoregressive coefficient under the 'ar1' noise model or only 0.0 under 'ols' noise_model and values are the RegressionResults instances) into input nifti space. Parameters ---------- attribute : str an attribute of a RegressionResults instance. possible values include: residuals, normalized_residuals, predicted, SSE, r_square, MSE. result_as_time_series : bool whether the RegressionResult attribute has a value per timepoint of the input nifti image. Returns ------- output : list A list of Nifti1Image(s). """ # check if valid attribute is being accessed. all_attributes = dict(vars(RegressionResults)).keys() possible_attributes = [prop for prop in all_attributes if '__' not in prop ] if attribute not in possible_attributes: msg = ("attribute must be one of: " "{attr}".format(attr=possible_attributes) ) raise ValueError(msg) if self.minimize_memory: raise ValueError( 'To access voxelwise attributes like ' 'R-squared, residuals, and predictions, ' 'the `FirstLevelModel`-object needs to store ' 'there attributes. ' 'To do so, set `minimize_memory` to `False` ' 'when initializing the `FirstLevelModel`-object.') if self.labels_ is None or self.results_ is None: raise ValueError('The model has not been fit yet') output = [] for design_matrix, labels, results in zip(self.design_matrices_, self.labels_, self.results_ ): if result_as_time_series: voxelwise_attribute = np.zeros((design_matrix.shape[0], len(labels)) ) else: voxelwise_attribute = np.zeros((1, len(labels))) for label_ in results: label_mask = labels == label_ voxelwise_attribute[:, label_mask] = getattr(results[label_], attribute) output.append(self.masker_.inverse_transform(voxelwise_attribute)) return output
def non_parametric_inference( second_level_input, confounds=None, design_matrix=None, second_level_contrast=None, first_level_contrast=None, mask=None, smoothing_fwhm=None, model_intercept=True, n_perm=10000, two_sided_test=False, random_state=None, n_jobs=1, verbose=0, threshold=None, ): """Generate p-values corresponding to the contrasts provided based on permutation testing. This function is a light wrapper around :func:`~nilearn.mass_univariate.permuted_ols`, with additional steps to ensure compatibility with the :mod:`~nilearn.glm.second_level` module. Parameters ---------- second_level_input : :obj:`pandas.DataFrame` or :obj:`list` of Niimg-like \ objects If a pandas DataFrame, then they have to contain subject_label, map_name and effects_map_path. It can contain multiple maps that would be selected during contrast estimation with the argument first_level_contrast of the compute_contrast function. The DataFrame will be sorted based on the subject_label column to avoid order inconsistencies when extracting the maps. So the rows of the automatically computed design matrix, if not provided, will correspond to the sorted subject_label column. If list of Niimg-like objects then this is taken literally as Y for the model fit and design_matrix must be provided. confounds : :obj:`pandas.DataFrame`, optional Must contain a subject_label column. All other columns are considered as confounds and included in the model. If design_matrix is provided then this argument is ignored. The resulting second level design matrix uses the same column names as in the given DataFrame for confounds. At least two columns are expected, "subject_label" and at least one confound. design_matrix : :obj:`pandas.DataFrame`, optional Design matrix to fit the GLM. The number of rows in the design matrix must agree with the number of maps derived from second_level_input. Ensure that the order of maps given by a second_level_input list of Niimgs matches the order of the rows in the design matrix. second_level_contrast : :obj:`str` or array of shape (n_col), optional Where ``n_col`` is the number of columns of the design matrix. The default (None) is accepted if the design matrix has a single column, in which case the only possible contrast array((1)) is applied; when the design matrix has multiple columns, an error is raised. first_level_contrast : :obj:`str`, optional In case a pandas DataFrame was provided as second_level_input this is the map name to extract from the pandas dataframe map_name column. It has to be a 't' contrast. .. versionadded:: 0.9.0 mask : Niimg-like, :obj:`~nilearn.maskers.NiftiMasker` or \ :obj:`~nilearn.maskers.MultiNiftiMasker` object, optional Mask to be used on data. If an instance of masker is passed, then its mask will be used. If no mask is given, it will be computed automatically by a MultiNiftiMasker with default parameters. Automatic mask computation assumes first level imgs have already been masked. %(smoothing_fwhm)s model_intercept : :obj:`bool`, optional If True, a constant column is added to the confounding variates unless the tested variate is already the intercept. Default=True. n_perm : :obj:`int`, optional Number of permutations to perform. Permutations are costly but the more are performed, the more precision one gets in the p-values estimation. Default=10000. two_sided_test : :obj:`bool`, optional If True, performs an unsigned t-test. Both positive and negative effects are considered; the null hypothesis is that the effect is zero. If False, only positive effects are considered as relevant. The null hypothesis is that the effect is zero or negative. Default=False. random_state : :obj:`int` or None, optional Seed for random number generator, to have the same permutations in each computing units. n_jobs : :obj:`int`, optional Number of parallel workers. If -1 is provided, all CPUs are used. A negative number indicates that all the CPUs except (abs(n_jobs) - 1) ones will be used. Default=1. verbose : :obj:`int`, optional Verbosity level (0 means no message). Default=0. threshold : None or :obj:`float`, optional Cluster-forming threshold in p-scale. This is only used for cluster-level inference. If None, no cluster-level inference will be performed. Default=None. .. warning:: Performing cluster-level inference will increase the computation time of the permutation procedure. .. versionadded:: 0.9.2.dev Returns ------- neg_log10_vfwe_pvals_img : :class:`~nibabel.nifti1.Nifti1Image` The image which contains negative logarithm of the voxel-level FWER-corrected p-values. .. note:: This is returned if ``threshold`` is None (the default). outputs : :obj:`dict` Output images, organized in a dictionary. Each image is 3D/4D, with the potential fourth dimension corresponding to the regressors. .. note:: This is returned if ``threshold`` is not None. .. versionadded:: 0.9.2.dev Here are the keys: =============== ======================================================= key description =============== ======================================================= t T-statistics associated with the significance test of the n_regressors explanatory variates against the n_descriptors target variates. logp_max_t Negative log10 family-wise error rate-corrected p-values corrected based on the distribution of maximum t-statistics from permutations. logp_max_size Negative log10 family-wise error rate-corrected p-values corrected based on the distribution of maximum cluster sizes from permutations. This map is generated through cluster-level methods, so the values in the map describe the significance of clusters, rather than individual voxels. Returned only if ``threshold`` is not None. logp_max_mass Negative log10 family-wise error rate-corrected p-values corrected based on the distribution of maximum cluster masses from permutations. This map is generated through cluster-level methods, so the values in the map describe the significance of clusters, rather than individual voxels. Returned only if ``threshold`` is not None. ============= ======================================================= """ _check_second_level_input(second_level_input, design_matrix, flm_object=False, df_object=True) _check_confounds(confounds) _check_design_matrix(design_matrix) if isinstance(second_level_input, pd.DataFrame): second_level_input = _sort_input_dataframe(second_level_input) sample_map, _ = _process_second_level_input(second_level_input) # Report progress t0 = time.time() if verbose > 0: sys.stderr.write("Fitting second level model...") # Learn the mask. Assume the first level imgs have been masked. if not isinstance(mask, NiftiMasker): masker = NiftiMasker(mask_img=mask, smoothing_fwhm=smoothing_fwhm, memory=Memory(None), verbose=max(0, verbose - 1), memory_level=1) else: masker = clone(mask) if smoothing_fwhm is not None: if getattr(masker, 'smoothing_fwhm') is not None: warn('Parameter smoothing_fwhm of the masker overridden') setattr(masker, 'smoothing_fwhm', smoothing_fwhm) masker.fit(sample_map) # Report progress if verbose > 0: sys.stderr.write("\nComputation of second level model done in " "%i seconds\n" % (time.time() - t0)) # Check and obtain the contrast contrast = _get_contrast(second_level_contrast, design_matrix) # Get effect_maps effect_maps = _infer_effect_maps(second_level_input, first_level_contrast) # Check design matrix and effect maps agree on number of rows _check_effect_maps(effect_maps, design_matrix) # Obtain tested_var if contrast in design_matrix.columns.tolist(): tested_var = np.asarray(design_matrix[contrast]) # Mask data target_vars = masker.transform(effect_maps) # Perform massively univariate analysis with permuted OLS outputs = permuted_ols( tested_var, target_vars, model_intercept=model_intercept, n_perm=n_perm, two_sided_test=two_sided_test, random_state=random_state, n_jobs=n_jobs, verbose=max(0, verbose - 1), masker=masker, threshold=threshold, output_type='dict', ) neg_log10_vfwe_pvals = outputs['logp_max_t'] neg_log10_vfwe_pvals_img = masker.inverse_transform( np.ravel(neg_log10_vfwe_pvals), ) if threshold is not None: # Original t-statistics t_img = masker.inverse_transform(np.ravel(outputs['t'])) # Cluster size-based p-values neg_log10_csfwe_pvals_img = masker.inverse_transform( np.ravel(outputs['logp_max_size']), ) # Cluster mass-based p-values neg_log10_cmfwe_pvals_img = masker.inverse_transform( np.ravel(outputs['logp_max_mass']), ) out = { 't': t_img, 'logp_max_t': neg_log10_vfwe_pvals_img, 'logp_max_size': neg_log10_csfwe_pvals_img, 'logp_max_mass': neg_log10_cmfwe_pvals_img, } return out else: return neg_log10_vfwe_pvals_img
class SecondLevelModel(BaseGLM): """ Implementation of the General Linear Model for multiple subject fMRI data Parameters ---------- mask_img : Niimg-like, NiftiMasker or MultiNiftiMasker object, optional Mask to be used on data. If an instance of masker is passed, then its mask will be used. If no mask is given, it will be computed automatically by a MultiNiftiMasker with default parameters. Automatic mask computation assumes first level imgs have already been masked. target_affine : 3x3 or 4x4 matrix, optional This parameter is passed to :func:`nilearn.image.resample_img`. Please see the related documentation for details. target_shape : 3-tuple of integers, optional This parameter is passed to :func:`nilearn.image.resample_img`. Please see the related documentation for details. %(smoothing_fwhm)s memory : string, optional Path to the directory used to cache the masking process and the glm fit. By default, no caching is done. Creates instance of joblib.Memory. memory_level : integer, optional Rough estimator of the amount of memory used by caching. Higher value means more memory for caching. Default=1. verbose : integer, optional Indicate the level of verbosity. By default, nothing is printed. If 0 prints nothing. If 1 prints final computation time. If 2 prints masker computation details. Default=0. n_jobs : integer, optional The number of CPUs to use to do the computation. -1 means 'all CPUs', -2 'all CPUs but one', and so on. Default=1. minimize_memory : boolean, optional Gets rid of some variables on the model fit results that are not necessary for contrast computation and would only be useful for further inspection of model details. This has an important impact on memory consumption. Default=True. Notes ----- This class is experimental. It may change in any future release of Nilearn. """ def __init__(self, mask_img=None, target_affine=None, target_shape=None, smoothing_fwhm=None, memory=Memory(None), memory_level=1, verbose=0, n_jobs=1, minimize_memory=True): self.mask_img = mask_img self.target_affine = target_affine self.target_shape = target_shape self.smoothing_fwhm = smoothing_fwhm if isinstance(memory, str): self.memory = Memory(memory) else: self.memory = memory self.memory_level = memory_level self.verbose = verbose self.n_jobs = n_jobs self.minimize_memory = minimize_memory self.second_level_input_ = None self.confounds_ = None self.labels_ = None self.results_ = None def fit(self, second_level_input, confounds=None, design_matrix=None): """ Fit the second-level GLM 1. create design matrix 2. do a masker job: fMRI_data -> Y 3. fit regression to (Y, X) Parameters ---------- second_level_input: list of `FirstLevelModel` objects or pandas DataFrame or list of Niimg-like objects. Giving FirstLevelModel objects will allow to easily compute the second level contrast of arbitrary first level contrasts thanks to the first_level_contrast argument of the compute_contrast method. Effect size images will be computed for each model to contrast at the second level. If a pandas DataFrame, then they have to contain subject_label, map_name and effects_map_path. It can contain multiple maps that would be selected during contrast estimation with the argument first_level_contrast of the compute_contrast function. The DataFrame will be sorted based on the subject_label column to avoid order inconsistencies when extracting the maps. So the rows of the automatically computed design matrix, if not provided, will correspond to the sorted subject_label column. If list of Niimg-like objects then this is taken literally as Y for the model fit and design_matrix must be provided. confounds : pandas DataFrame, optional Must contain a subject_label column. All other columns are considered as confounds and included in the model. If design_matrix is provided then this argument is ignored. The resulting second level design matrix uses the same column names as in the given DataFrame for confounds. At least two columns are expected, "subject_label" and at least one confound. design_matrix : pandas DataFrame, optional Design matrix to fit the GLM. The number of rows in the design matrix must agree with the number of maps derived from second_level_input. Ensure that the order of maps given by a second_level_input list of Niimgs matches the order of the rows in the design matrix. """ # check second_level_input _check_second_level_input(second_level_input, design_matrix, confounds=confounds) # check confounds _check_confounds(confounds) # check design matrix _check_design_matrix(design_matrix) if isinstance(second_level_input, pd.DataFrame): second_level_input = _sort_input_dataframe(second_level_input) self.second_level_input_ = second_level_input self.confounds_ = confounds sample_map, subjects_label = _process_second_level_input( second_level_input) # Report progress t0 = time.time() if self.verbose > 0: sys.stderr.write("Fitting second level model. " "Take a deep breath\r") # Create and set design matrix, if not given if design_matrix is None: design_matrix = make_second_level_design_matrix( subjects_label, confounds) self.design_matrix_ = design_matrix # Learn the mask. Assume the first level imgs have been masked. if not isinstance(self.mask_img, NiftiMasker): self.masker_ = NiftiMasker(mask_img=self.mask_img, target_affine=self.target_affine, target_shape=self.target_shape, smoothing_fwhm=self.smoothing_fwhm, memory=self.memory, verbose=max(0, self.verbose - 1), memory_level=self.memory_level) else: self.masker_ = clone(self.mask_img) for param_name in ['smoothing_fwhm', 'memory', 'memory_level']: our_param = getattr(self, param_name) if our_param is None: continue if getattr(self.masker_, param_name) is not None: warn('Parameter %s of the masker overridden' % param_name) setattr(self.masker_, param_name, our_param) self.masker_.fit(sample_map) # Report progress if self.verbose > 0: sys.stderr.write("\nComputation of second level model done in " "%i seconds\n" % (time.time() - t0)) return self def compute_contrast(self, second_level_contrast=None, first_level_contrast=None, second_level_stat_type=None, output_type='z_score'): """Generate different outputs corresponding to the contrasts provided e.g. z_map, t_map, effects and variance. Parameters ---------- second_level_contrast : str or array of shape (n_col), optional Where ``n_col`` is the number of columns of the design matrix. The string can be a formula compatible with `pandas.DataFrame.eval`. Basically one can use the name of the conditions as they appear in the design matrix of the fitted model combined with operators +- and combined with numbers with operators +-`*`/. The default (None) is accepted if the design matrix has a single column, in which case the only possible contrast array((1)) is applied; when the design matrix has multiple columns, an error is raised. first_level_contrast : str or array of shape (n_col) with respect to FirstLevelModel, optional In case a list of FirstLevelModel was provided as second_level_input, we have to provide a contrast to apply to the first level models to get the corresponding list of images desired, that would be tested at the second level. In case a pandas DataFrame was provided as second_level_input this is the map name to extract from the pandas dataframe map_name column. It has to be a 't' contrast. second_level_stat_type : {'t', 'F'}, optional Type of the second level contrast output_type : str, optional Type of the output map. Can be 'z_score', 'stat', 'p_value', 'effect_size', 'effect_variance' or 'all'. Default='z-score'. Returns ------- output_image : Nifti1Image The desired output image(s). If ``output_type == 'all'``, then the output is a dictionary of images, keyed by the type of image. """ if self.second_level_input_ is None: raise ValueError('The model has not been fit yet') # check first_level_contrast _check_first_level_contrast(self.second_level_input_, first_level_contrast) # check contrast and obtain con_val con_val = _get_con_val(second_level_contrast, self.design_matrix_) # check output type # 'all' is assumed to be the final entry; # if adding more, place before 'all' valid_types = [ 'z_score', 'stat', 'p_value', 'effect_size', 'effect_variance', 'all' ] _check_output_type(output_type, valid_types) # Get effect_maps appropriate for chosen contrast effect_maps = _infer_effect_maps(self.second_level_input_, first_level_contrast) # Check design matrix X and effect maps Y agree on number of rows _check_effect_maps(effect_maps, self.design_matrix_) # Fit an Ordinary Least Squares regression for parametric statistics Y = self.masker_.transform(effect_maps) if self.memory: mem_glm = self.memory.cache(run_glm, ignore=['n_jobs']) else: mem_glm = run_glm labels, results = mem_glm(Y, self.design_matrix_.values, n_jobs=self.n_jobs, noise_model='ols') # We save memory if inspecting model details is not necessary if self.minimize_memory: for key in results: results[key] = SimpleRegressionResults(results[key]) self.labels_ = labels self.results_ = results # We compute contrast object if self.memory: mem_contrast = self.memory.cache(compute_contrast) else: mem_contrast = compute_contrast contrast = mem_contrast(self.labels_, self.results_, con_val, second_level_stat_type) output_types = \ valid_types[:-1] if output_type == 'all' else [output_type] outputs = {} for output_type_ in output_types: # We get desired output from contrast object estimate_ = getattr(contrast, output_type_)() # Prepare the returned images output = self.masker_.inverse_transform(estimate_) contrast_name = str(con_val) output.header['descrip'] = ('%s of contrast %s' % (output_type, contrast_name)) outputs[output_type_] = output return outputs if output_type == 'all' else output def _get_voxelwise_model_attribute(self, attribute, result_as_time_series): """Transform RegressionResults instances within a dictionary (whose keys represent the autoregressive coefficient under the 'ar1' noise model or only 0.0 under 'ols' noise_model and values are the RegressionResults instances) into input nifti space. Parameters ---------- attribute : str an attribute of a RegressionResults instance. possible values include: 'residuals', 'normalized_residuals', 'predicted', SSE, r_square, MSE. result_as_time_series : bool whether the RegressionResult attribute has a value per timepoint of the input nifti image. Returns ------- output : list A list of Nifti1Image(s). """ # check if valid attribute is being accessed. all_attributes = dict(vars(RegressionResults)).keys() possible_attributes = [ prop for prop in all_attributes if '__' not in prop ] if attribute not in possible_attributes: msg = ("attribute must be one of: " "{attr}".format(attr=possible_attributes)) raise ValueError(msg) if self.minimize_memory: raise ValueError( 'To access voxelwise attributes like ' 'R-squared, residuals, and predictions, ' 'the `SecondLevelModel`-object needs to store ' 'there attributes. ' 'To do so, set `minimize_memory` to `False` ' 'when initializing the `SecondLevelModel`-object.') if self.labels_ is None or self.results_ is None: raise ValueError("The model has no results. This could be " "because the model has not been fitted yet " "or because no contrast has been computed " "already.") if result_as_time_series: voxelwise_attribute = np.zeros( (self.design_matrix_.shape[0], len(self.labels_))) else: voxelwise_attribute = np.zeros((1, len(self.labels_))) for label_ in self.results_: label_mask = self.labels_ == label_ voxelwise_attribute[:, label_mask] = getattr(self.results_[label_], attribute) return self.masker_.inverse_transform(voxelwise_attribute)
def compute_fixed_effects(contrast_imgs, variance_imgs, mask=None, precision_weighted=False): """Compute the fixed effects, given images of effects and variance Parameters ---------- contrast_imgs : list of Nifti1Images or strings The input contrast images. variance_imgs : list of Nifti1Images or strings The input variance images. mask : Nifti1Image or NiftiMasker instance or None, optional Mask image. If None, it is recomputed from contrast_imgs. precision_weighted : Bool, optional Whether fixed effects estimates should be weighted by inverse variance or not. Default=False. Returns ------- fixed_fx_contrast_img : Nifti1Image The fixed effects contrast computed within the mask. fixed_fx_variance_img : Nifti1Image The fixed effects variance computed within the mask. fixed_fx_t_img : Nifti1Image The fixed effects t-test computed within the mask. Notes ----- This function is experimental. It may change in any future release of Nilearn. """ if len(contrast_imgs) != len(variance_imgs): raise ValueError( 'The number of contrast images (%d) ' 'differs from the number of variance images (%d). ' % (len(contrast_imgs), len(variance_imgs)) ) if isinstance(mask, NiftiMasker): masker = mask.fit() elif mask is None: masker = NiftiMasker().fit(contrast_imgs) else: masker = NiftiMasker(mask_img=mask).fit() variances = masker.transform(variance_imgs) contrasts = masker.transform(contrast_imgs) (fixed_fx_contrast, fixed_fx_variance, fixed_fx_t) = _compute_fixed_effects_params( contrasts, variances, precision_weighted) fixed_fx_contrast_img = masker.inverse_transform(fixed_fx_contrast) fixed_fx_variance_img = masker.inverse_transform(fixed_fx_variance) fixed_fx_t_img = masker.inverse_transform(fixed_fx_t) return fixed_fx_contrast_img, fixed_fx_variance_img, fixed_fx_t_img
masker = NiftiMasker( mask_img=mask_img, memory='nilearn_cache', memory_level=1) masker = masker.fit() # Images may fail to be transformed, and are of different shapes, # so we need to transform one-by-one and keep track of failures. X = [] is_usable = np.ones((len(images),), dtype=bool) for index, image_path in enumerate(images): # load image and remove nan and inf values. # applying smooth_img to an image with fwhm=None simply cleans up # non-finite values but otherwise doesn't modify the image. image = smooth_img(image_path, fwhm=None) try: X.append(masker.transform(image)) except Exception as e: meta = nv_data['images_meta'][index] print("Failed to mask/reshape image: id: {0}; " "name: '{1}'; collection: {2}; error: {3}".format( meta.get('id'), meta.get('name'), meta.get('collection_id'), e)) is_usable[index] = False # Now reshape list into 2D matrix, and remove failed images from terms X = np.vstack(X) term_weights = term_weights[is_usable, :] ###################################################################### # Run ICA and map components to terms
def threshold_stats_img(stat_img=None, mask_img=None, alpha=.001, threshold=3., height_control='fpr', cluster_threshold=0, two_sided=True): """ Compute the required threshold level and return the thresholded map Parameters ---------- stat_img : Niimg-like object or None, optional Statistical image (presumably in z scale) whenever height_control is 'fpr' or None, stat_img=None is acceptable. If it is 'fdr' or 'bonferroni', an error is raised if stat_img is None. mask_img : Niimg-like object, optional, Mask image alpha : float or list, optional Number controlling the thresholding (either a p-value or q-value). Its actual meaning depends on the height_control parameter. This function translates alpha to a z-scale threshold. Default=0.001. threshold : float, optional Desired threshold in z-scale. This is used only if height_control is None. Default=3.0. height_control : string, or None optional False positive control meaning of cluster forming threshold: None|'fpr'|'fdr'|'bonferroni' Default='fpr'. cluster_threshold : float, optional cluster size threshold. In the returned thresholded map, sets of connected voxels (`clusters`) with size smaller than this number will be removed. Default=0. two_sided : Bool, optional Whether the thresholding should yield both positive and negative part of the maps. In that case, alpha is corrected by a factor of 2. Default=True. Returns ------- thresholded_map : Nifti1Image, The stat_map thresholded at the prescribed voxel- and cluster-level. threshold : float The voxel-level threshold used actually. Notes ----- If the input image is not z-scaled (i.e. some z-transformed statistic) the computed threshold is not rigorous and likely meaningless This function is experimental. It may change in any future release of Nilearn. See also -------- nilearn.image.threshold_img : Apply an explicit voxel-level (and optionally cluster-level) threshold without correction. """ height_control_methods = [ 'fpr', 'fdr', 'bonferroni', 'all-resolution-inference', None ] if height_control not in height_control_methods: raise ValueError("height control should be one of {0}", height_control_methods) # if two-sided, correct alpha by a factor of 2 alpha_ = alpha / 2 if two_sided else alpha # if height_control is 'fpr' or None, we don't need to look at the data # to compute the threshold if height_control == 'fpr': threshold = norm.isf(alpha_) # In this case, and if stat_img is None, we return if stat_img is None: if height_control in ['fpr', None]: return None, threshold else: raise ValueError('Map_threshold requires stat_img not to be None' 'when the height_control procedure ' 'is "bonferroni" or "fdr"') if mask_img is None: masker = NiftiMasker(mask_strategy='background').fit(stat_img) else: masker = NiftiMasker(mask_img=mask_img).fit() stats = np.ravel(masker.transform(stat_img)) n_voxels = np.size(stats) # Thresholding if two_sided: # replace stats by their absolute value stats = np.abs(stats) if height_control == 'fdr': threshold = fdr_threshold(stats, alpha_) elif height_control == 'bonferroni': threshold = norm.isf(alpha_ / n_voxels) # Apply cluster-extent thresholding with new cluster-defining threshold stat_img = threshold_img( img=stat_img, threshold=threshold, cluster_threshold=cluster_threshold, two_sided=two_sided, mask_img=mask_img, copy=True, ) return stat_img, threshold
mean_func_img = mean_img(func_filename) plot_roi(mask_img, mean_func_img, display_mode='y', cut_coords=4, title="Mask") ########################################################################### # Visualize the mask using the 'generate_report' method # This report can be displayed in a Jupyter Notebook, # opened in-browser using the .open_in_browser() method, # or saved to a file using the .save_as_html(output_filepath) method. report = nifti_masker.generate_report() report ########################################################################### # Preprocess data with the NiftiMasker nifti_masker.fit(func_filename) fmri_masked = nifti_masker.transform(func_filename) # fmri_masked is now a 2D matrix, (n_voxels x n_time_points) ########################################################################### # Run an algorithm from sklearn.decomposition import FastICA n_components = 10 ica = FastICA(n_components=n_components, random_state=42) components_masked = ica.fit_transform(fmri_masked.T).T ########################################################################### # Reverse masking, and display the corresponding map components = nifti_masker.inverse_transform(components_masked) # Visualize results from nilearn.plotting import plot_stat_map, show
def cluster_level_inference(stat_img, mask_img=None, threshold=3., alpha=.05, verbose=False): """ Report the proportion of active voxels for all clusters defined by the input threshold. This implements the method described in [1]_. Parameters ---------- stat_img : Niimg-like object or None, optional statistical image (presumably in z scale) mask_img : Niimg-like object, optional, mask image threshold : list of floats, optional Cluster-forming threshold in z-scale. Default=3.0. alpha : float or list, optional Level of control on the true positive rate, aka true dsicovery proportion. Default=0.05. verbose : bool, optional Verbosity mode. Default=False. Returns ------- proportion_true_discoveries_img : Nifti1Image The statistical map that gives the true positive. Notes ----- This function is experimental. It may change in any future release of Nilearn. References ---------- .. [1] Rosenblatt JD, Finos L, Weeda WD, Solari A, Goeman JJ. All-Resolutions Inference for brain imaging. Neuroimage. 2018 Nov 1;181:786-796. doi: 10.1016/j.neuroimage.2018.07.060 """ if not isinstance(threshold, list): threshold = [threshold] if mask_img is None: masker = NiftiMasker(mask_strategy='background').fit(stat_img) else: masker = NiftiMasker(mask_img=mask_img).fit() stats = np.ravel(masker.transform(stat_img)) hommel_value = _compute_hommel_value(stats, alpha, verbose=verbose) # embed it back to 3D grid stat_map = get_data(masker.inverse_transform(stats)) # Extract connected components above threshold proportion_true_discoveries_img = math_img('0. * img', img=stat_img) proportion_true_discoveries = masker.transform( proportion_true_discoveries_img).ravel() for threshold_ in sorted(threshold): label_map, n_labels = label(stat_map > threshold_) labels = label_map[get_data(masker.mask_img_) > 0] for label_ in range(1, n_labels + 1): # get the z-vals in the cluster cluster_vals = stats[labels == label_] proportion = _true_positive_fraction(cluster_vals, hommel_value, alpha) proportion_true_discoveries[labels == label_] = proportion proportion_true_discoveries_img = masker.inverse_transform( proportion_true_discoveries) return proportion_true_discoveries_img
def non_parametric_inference(second_level_input, confounds=None, design_matrix=None, second_level_contrast=None, first_level_contrast=None, mask=None, smoothing_fwhm=None, model_intercept=True, n_perm=10000, two_sided_test=False, random_state=None, n_jobs=1, verbose=0): """Generate p-values corresponding to the contrasts provided based on permutation testing. This function reuses the 'permuted_ols' function Nilearn. Parameters ---------- second_level_input : pandas DataFrame or list of Niimg-like objects. If a pandas DataFrame, then they have to contain subject_label, map_name and effects_map_path. It can contain multiple maps that would be selected during contrast estimation with the argument first_level_contrast of the compute_contrast function. The DataFrame will be sorted based on the subject_label column to avoid order inconsistencies when extracting the maps. So the rows of the automatically computed design matrix, if not provided, will correspond to the sorted subject_label column. If list of Niimg-like objects then this is taken literally as Y for the model fit and design_matrix must be provided. confounds : pandas DataFrame, optional Must contain a subject_label column. All other columns are considered as confounds and included in the model. If design_matrix is provided then this argument is ignored. The resulting second level design matrix uses the same column names as in the given DataFrame for confounds. At least two columns are expected, "subject_label" and at least one confound. design_matrix : pandas DataFrame, optional Design matrix to fit the GLM. The number of rows in the design matrix must agree with the number of maps derived from second_level_input. Ensure that the order of maps given by a second_level_input list of Niimgs matches the order of the rows in the design matrix. second_level_contrast : str or array of shape (n_col), optional Where ``n_col`` is the number of columns of the design matrix. The default (None) is accepted if the design matrix has a single column, in which case the only possible contrast array((1)) is applied; when the design matrix has multiple columns, an error is raised. first_level_contrast : str, optional In case a pandas DataFrame was provided as second_level_input this is the map name to extract from the pandas dataframe map_name column. It has to be a 't' contrast. .. versionadded:: 0.8.2 mask : Niimg-like, NiftiMasker or MultiNiftiMasker object, optional Mask to be used on data. If an instance of masker is passed, then its mask will be used. If no mask is given, it will be computed automatically by a MultiNiftiMasker with default parameters. Automatic mask computation assumes first level imgs have already been masked. %(smoothing_fwhm)s model_intercept : bool, optional If True, a constant column is added to the confounding variates unless the tested variate is already the intercept. Default=True. n_perm : int, optional Number of permutations to perform. Permutations are costly but the more are performed, the more precision one gets in the p-values estimation. Default=10000. two_sided_test : boolean, optional If True, performs an unsigned t-test. Both positive and negative effects are considered; the null hypothesis is that the effect is zero. If False, only positive effects are considered as relevant. The null hypothesis is that the effect is zero or negative. Default=False. random_state : int or None, optional Seed for random number generator, to have the same permutations in each computing units. n_jobs : int, optional Number of parallel workers. If -1 is provided, all CPUs are used. A negative number indicates that all the CPUs except (abs(n_jobs) - 1) ones will be used. Default=1. verbose : int, optional Verbosity level (0 means no message). Default=0. Returns ------- neg_log_corrected_pvals_img : Nifti1Image The image which contains negative logarithm of the corrected p-values. """ _check_second_level_input(second_level_input, design_matrix, flm_object=False, df_object=True) _check_confounds(confounds) _check_design_matrix(design_matrix) if isinstance(second_level_input, pd.DataFrame): second_level_input = _sort_input_dataframe(second_level_input) sample_map, _ = _process_second_level_input(second_level_input) # Report progress t0 = time.time() if verbose > 0: sys.stderr.write("Fitting second level model...") # Learn the mask. Assume the first level imgs have been masked. if not isinstance(mask, NiftiMasker): masker = NiftiMasker(mask_img=mask, smoothing_fwhm=smoothing_fwhm, memory=Memory(None), verbose=max(0, verbose - 1), memory_level=1) else: masker = clone(mask) if smoothing_fwhm is not None: if getattr(masker, 'smoothing_fwhm') is not None: warn('Parameter smoothing_fwhm of the masker overridden') setattr(masker, 'smoothing_fwhm', smoothing_fwhm) masker.fit(sample_map) # Report progress if verbose > 0: sys.stderr.write("\nComputation of second level model done in " "%i seconds\n" % (time.time() - t0)) # Check and obtain the contrast contrast = _get_contrast(second_level_contrast, design_matrix) # Get effect_maps effect_maps = _infer_effect_maps(second_level_input, first_level_contrast) # Check design matrix and effect maps agree on number of rows _check_effect_maps(effect_maps, design_matrix) # Obtain tested_var if contrast in design_matrix.columns.tolist(): tested_var = np.asarray(design_matrix[contrast]) # Mask data target_vars = masker.transform(effect_maps) # Perform massively univariate analysis with permuted OLS neg_log_pvals_permuted_ols, _, _ = permuted_ols( tested_var, target_vars, model_intercept=model_intercept, n_perm=n_perm, two_sided_test=two_sided_test, random_state=random_state, n_jobs=n_jobs, verbose=max(0, verbose - 1)) neg_log_corrected_pvals_img = masker.inverse_transform( np.ravel(neg_log_pvals_permuted_ols)) return neg_log_corrected_pvals_img