def test_fit_and_transform_modality_with_perf(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') func_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') template_file = os.path.join(tst.tmpdir, 'template.nii.gz') crop_and_oblique(anat_file, template_file) registrator = TemplateRegistrator(template_file, 400, output_dir=tst.tmpdir, use_rats_tool=False, verbose=False, registration_kind='affine') registrator.fit_anat(anat_file) assert_raises_regex( ValueError, 'has not been perf fitted', registrator.transform_modality_like, func_file, 'perf') func_img = nibabel.load(func_file) m0_img = nibabel.Nifti1Image(func_img.get_data()[..., 0], func_img.affine) m0_file = os.path.join(tst.tmpdir, 'm0.nii.gz') m0_img.to_filename(m0_file) # test fit_modality for perf registrator.fit_modality(m0_file, 'perf') assert_true(_check_same_fov(nibabel.load(registrator.registered_perf_), nibabel.load(template_file))) # test transform_modality for perf m0_like_file = os.path.join(tst.tmpdir, 'm0_like.nii.gz') empty_img_like(m0_file, m0_like_file) transformed_file = registrator.transform_modality_like(m0_like_file, 'perf') assert_true(_check_same_fov(nibabel.load(transformed_file), nibabel.load(template_file)))
def test_fit_anat_and_transform_anat_like(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') template_file = os.path.join(tst.tmpdir, 'template.nii.gz') # Create template crop_and_oblique(anat_file, template_file) registrator = TemplateRegistrator(template_file, 400, output_dir=tst.tmpdir, use_rats_tool=False, verbose=False, registration_kind='affine') assert_raises_regex( ValueError, 'has not been anat fitted', registrator.transform_anat_like, anat_file) # test fit_anat registrator.fit_anat(anat_file) assert_true(_check_same_fov(nibabel.load(registrator.registered_anat_), nibabel.load(template_file))) # test transform_anat_like anat_like_file = os.path.join(tst.tmpdir, 'anat_like.nii.gz') empty_img_like(anat_file, anat_like_file) registrator.fit_anat(anat_file) transformed_file = registrator.transform_anat_like(anat_like_file) assert_true(_check_same_fov(nibabel.load(transformed_file), nibabel.load(template_file)))
def test_fit_transform_and_inverse_modality_with_func(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') func_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') template_file = os.path.join(tst.tmpdir, 'template.nii.gz') crop_and_oblique(anat_file, template_file) registrator = TemplateRegistrator(template_file, 400, output_dir=tst.tmpdir, use_rats_tool=False, verbose=False, registration_kind='affine') registrator.fit_anat(anat_file) assert_raises_regex( ValueError, "Only 'func' and 'perf' ", registrator.fit_modality, func_file, 'diffusion') assert_raises_regex( ValueError, "'t_r' is needed for slice ", registrator.fit_modality, func_file, 'func') assert_raises_regex( ValueError, 'has not been func fitted', registrator.transform_modality_like, func_file, 'func') # test fit_modality for func registrator.fit_modality(func_file, 'func', slice_timing=False) registered_func_img = nibabel.load(registrator.registered_func_) template_img = nibabel.load(template_file) np.testing.assert_array_almost_equal(registered_func_img.affine, template_img.affine) np.testing.assert_array_equal(registered_func_img.shape[:-1], template_img.shape) # test transform_modality for func func_like_file = os.path.join(tst.tmpdir, 'func_like.nii.gz') empty_img_like(func_file, func_like_file) transformed_file = registrator.transform_modality_like(func_like_file, 'func') transformed_img = nibabel.load(transformed_file) assert_true(_check_same_fov(transformed_img, nibabel.load(template_file))) # test transform then inverse transform brings back to the original image inverse_transformed_file = registrator.inverse_transform_towards_modality( transformed_file, 'func') inverse_transformed_img = nibabel.load(inverse_transformed_file) func_like_img = nibabel.load(func_like_file) assert_true(_check_same_fov(inverse_transformed_img, func_like_img)) np.testing.assert_array_equal(inverse_transformed_img.get_data(), func_like_img.get_data()) # test inverse transform then transform brings back to the original image transformed_file2 = registrator.transform_modality_like( inverse_transformed_file, 'func') transformed_img2 = nibabel.load(transformed_file2) assert_true(_check_same_fov(transformed_img2, transformed_img)) np.testing.assert_array_equal(transformed_img2.get_data(), transformed_img.get_data())
def test_check_same_fov(): affine_a = np.eye(4) affine_b = np.eye(4) * 2 shape_a = (2, 2, 2) shape_b = (3, 3, 3) shape_a_affine_a = nibabel.Nifti1Image(np.empty(shape_a), affine_a) shape_a_affine_a_2 = nibabel.Nifti1Image(np.empty(shape_a), affine_a) shape_a_affine_b = nibabel.Nifti1Image(np.empty(shape_a), affine_b) shape_b_affine_a = nibabel.Nifti1Image(np.empty(shape_b), affine_a) shape_b_affine_b = nibabel.Nifti1Image(np.empty(shape_b), affine_b) niimg_conversions._check_same_fov(a=shape_a_affine_a, b=shape_a_affine_a_2, raise_error=True) assert_raises_regex( ValueError, "[ac] and [ac] do not have the same affine", niimg_conversions._check_same_fov, a=shape_a_affine_a, b=shape_a_affine_a_2, c=shape_a_affine_b, raise_error=True, ) assert_raises_regex( ValueError, "[ab] and [ab] do not have the same shape", niimg_conversions._check_same_fov, a=shape_a_affine_a, b=shape_b_affine_a, raise_error=True, ) assert_raises_regex( ValueError, "[ab] and [ab] do not have the same affine", niimg_conversions._check_same_fov, a=shape_b_affine_b, b=shape_a_affine_a, raise_error=True, ) assert_raises_regex( ValueError, "[ab] and [ab] do not have the same shape", niimg_conversions._check_same_fov, a=shape_b_affine_b, b=shape_a_affine_a, raise_error=True, )
def filter_and_mask(imgs, mask_img_, parameters, memory_level=0, memory=Memory(cachedir=None), verbose=0, confounds=None, copy=True): imgs = _utils.check_niimg(imgs, atleast_4d=True, ensure_ndim=4) # Check whether resampling is truly necessary. If so, crop mask # as small as possible in order to speed up the process if not _check_same_fov(imgs, mask_img_): parameters = copy_object(parameters) # now we can crop mask_img_ = image.crop_img(mask_img_, copy=False) parameters['target_shape'] = mask_img_.shape parameters['target_affine'] = mask_img_.get_affine() data, affine = filter_and_extract(imgs, _ExtractionFunctor(mask_img_), parameters, memory_level=memory_level, memory=memory, verbose=verbose, confounds=confounds, copy=copy) # For _later_: missing value removal or imputing of missing data # (i.e. we want to get rid of NaNs, if smoothing must be done # earlier) # Optionally: 'doctor_nan', remove voxels with NaNs, other option # for later: some form of imputation return data, affine
def test_afni_unifize(): in_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') unbiased_file = bias_correction.afni_unifize(in_file, write_dir=tst.tmpdir, verbose=False) assert_true( _check_same_fov(nibabel.load(unbiased_file), nibabel.load(in_file)))
def test_check_same_fov(): affine_a = np.eye(4) affine_b = np.eye(4) * 2 shape_a = (2, 2, 2) shape_b = (3, 3, 3) shape_a_affine_a = nibabel.Nifti1Image(np.empty(shape_a), affine_a) shape_a_affine_a_2 = nibabel.Nifti1Image(np.empty(shape_a), affine_a) shape_a_affine_b = nibabel.Nifti1Image(np.empty(shape_a), affine_b) shape_b_affine_a = nibabel.Nifti1Image(np.empty(shape_b), affine_a) shape_b_affine_b = nibabel.Nifti1Image(np.empty(shape_b), affine_b) niimg_conversions._check_same_fov(a=shape_a_affine_a, b=shape_a_affine_a_2, raise_error=True) assert_raises_regex(ValueError, '[ac] and [ac] do not have the same affine', niimg_conversions._check_same_fov, a=shape_a_affine_a, b=shape_a_affine_a_2, c=shape_a_affine_b, raise_error=True) assert_raises_regex(ValueError, '[ab] and [ab] do not have the same shape', niimg_conversions._check_same_fov, a=shape_a_affine_a, b=shape_b_affine_a, raise_error=True) assert_raises_regex(ValueError, '[ab] and [ab] do not have the same affine', niimg_conversions._check_same_fov, a=shape_b_affine_b, b=shape_a_affine_a, raise_error=True) assert_raises_regex(ValueError, '[ab] and [ab] do not have the same shape', niimg_conversions._check_same_fov, a=shape_b_affine_b, b=shape_a_affine_a, raise_error=True)
def test_compute_histo_brain_mask(): head_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') brain_mask_file = brain_mask.compute_histo_brain_mask( head_file, 400, write_dir=tst.tmpdir, verbose=False) assert_true(os.path.isfile(brain_mask_file)) assert_true(_check_same_fov(nibabel.load(brain_mask_file), nibabel.load(head_file)))
def compute_confounds(imgs, mask_img, n_confounds=5, get_randomized_svd=False, compute_not_mask=False): """ """ confounds = [] if not isinstance(imgs, collections.Iterable) or \ isinstance(imgs, _basestring): imgs = [imgs, ] img = _utils.check_niimg_4d(imgs[0]) shape = img.shape[:3] affine = get_affine(img) if isinstance(mask_img, _basestring): mask_img = _utils.check_niimg_3d(mask_img) if not _check_same_fov(img, mask_img): mask_img = resample_img( mask_img, target_shape=shape, target_affine=affine, interpolation='nearest') if compute_not_mask: print("Non mask based confounds extraction") not_mask_data = np.logical_not(mask_img.get_data().astype(np.int)) whole_brain_mask = masking.compute_multi_epi_mask(imgs) not_mask = np.logical_and(not_mask_data, whole_brain_mask.get_data()) mask_img = new_img_like(img, not_mask.astype(np.int), affine) for img in imgs: print("[Confounds Extraction] {0}".format(img)) img = _utils.check_niimg_4d(img) print("[Confounds Extraction] high ariance confounds computation]") high_variance = high_variance_confounds(img, mask_img=mask_img, n_confounds=n_confounds) if compute_not_mask and get_randomized_svd: signals = masking.apply_mask(img, mask_img) non_constant = np.any(np.diff(signals, axis=0) != 0, axis=0) signals = signals[:, non_constant] signals = signal.clean(signals, detrend=True) print("[Confounds Extraction] Randomized SVD computation") U, s, V = randomized_svd(signals, n_components=n_confounds, random_state=0) if high_variance is not None: confound_ = np.hstack((U, high_variance)) else: confound_ = U else: confound_ = high_variance confounds.append(confound_) return confounds
def test_coregister(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') func_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') mean_func_file = os.path.join(tst.tmpdir, 'mean_func.nii.gz') mean_img(func_file).to_filename(mean_func_file) bunch = func.coregister(anat_file, mean_func_file, tst.tmpdir, slice_timing=False, verbose=False, reorient_only=True) assert_true( _check_same_fov(nibabel.load(bunch.coreg_func_), nibabel.load(bunch.coreg_anat_))) assert_true(_check_same_obliquity(bunch.coreg_anat_, bunch.coreg_func_)) assert_true(os.path.isfile(bunch.coreg_transform_)) assert_less(0, len(bunch.coreg_warps_)) assert_true(bunch.coreg_warps_[-1] is None) # Last slice in functional # is without signal for warp_file in bunch.coreg_warps_[:-1]: assert_true(os.path.isfile(warp_file)) # Check environement variables setting assert_raises_regex(RuntimeError, "3dcopy", func.coregister, anat_file, mean_func_file, tst.tmpdir, slice_timing=False, verbose=False, reorient_only=True, AFNI_DECONFLICT='NO') # Check caching does not change the paths bunch2 = func.coregister(anat_file, mean_func_file, tst.tmpdir, slice_timing=False, verbose=False, caching=True, reorient_only=True, AFNI_DECONFLICT='OVERWRITE') assert_equal(bunch.coreg_func_, bunch2.coreg_func_) assert_equal(bunch.coreg_anat_, bunch2.coreg_anat_) assert_equal(bunch.coreg_transform_, bunch2.coreg_transform_) for warp_file, warp_file2 in zip(bunch.coreg_warps_, bunch2.coreg_warps_): assert_equal(warp_file, warp_file2)
def extract_confounds(imgs, mask_img, n_confounds=10): """To extract confounds on list of subjects See nilearn.image.high_variance_confounds for technical details. Parameters ---------- imgs : list of Nifti images, either str or nibabel.Nifti1Image Functional images on which confounds should be extracted mask_img : str or nibabel.Nifti1Image Mask image with binary values. This image is imposed on each functional image for confounds extraction.. n_confounds : int, optional By default, 10 high variance confounds are extracted. Otherwise, confounds as specified are extracted. Returns ------- confounds : list of Numpy arrays. Each numpy array is a confound corresponding to imgs provided. Each numpy array will have shape (n_timepoints, n_confounds) """ confounds = [] if not isinstance(imgs, collections.Iterable) or \ isinstance(imgs, _basestring): imgs = [imgs, ] img = _utils.check_niimg_4d(imgs[0]) shape = img.shape[:3] affine = img.affine if isinstance(mask_img, _basestring): mask_img = _utils.check_niimg_3d(mask_img) if not _check_same_fov(img, mask_img): mask_img = resample_img( mask_img, target_shape=shape, target_affine=affine, interpolation='nearest') for img in imgs: print("[Confounds Extraction] Image selected {0}".format(img)) img = _utils.check_niimg_4d(img) print("Extracting high variance confounds") high_variance = high_variance_confounds(img, mask_img=mask_img, n_confounds=n_confounds) confounds.append(high_variance) return confounds
def test_warp(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') func_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') func_file0 = os.path.join(tst.tmpdir, 'mean_func.nii.gz') func_img0 = index_img(func_file, 0) func_img0.to_filename(func_file0) registered_anat_oblique_file, mat_file =\ base._warp(anat_file, func_file0, write_dir=tst.tmpdir, caching=False, verbose=False) assert_true( _check_same_fov(nibabel.load(registered_anat_oblique_file), func_img0)) assert_true(os.path.isfile(mat_file))
def test_coregister_fmri_session(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') func_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') animal_session = FMRISession(anat=anat_file, func=func_file, animal_id='test_coreg_dir') func.coregister_fmri_session(animal_session, 1., tst.tmpdir, 400, slice_timing=False, verbose=False, use_rats_tool=False) assert_true( _check_same_fov(nibabel.load(animal_session.coreg_func_), nibabel.load(animal_session.coreg_anat_))) assert_true( _check_same_obliquity(animal_session.coreg_anat_, animal_session.coreg_func_)) assert_true(os.path.isfile(animal_session.coreg_transform_)) assert_equal(os.path.join(tst.tmpdir, 'test_coreg_dir'), animal_session.output_dir_) # Check environement variables setting current_dir = os.getcwd() # coregister_fmri_session changes the directory assert_raises_regex(RuntimeError, "already exists", func.coregister_fmri_session, animal_session, 1., tst.tmpdir, 400, slice_timing=False, use_rats_tool=False, AFNI_DECONFLICT='NO') os.chdir(current_dir)
def _preprocess_input(self, dataset): """Preprocess inputs to the Estimator from the Dataset as needed.""" masker = self.masker or dataset.masker mask_img = masker.mask_img or masker.labels_img if isinstance(mask_img, str): mask_img = nb.load(mask_img) # Ensure that protected values are not included among _required_inputs assert "aggressive_mask" not in self._required_inputs.keys( ), "This is a protected name." # A dictionary to collect masked image data, to be further reduced by the aggressive mask. temp_image_inputs = {} for name, (type_, _) in self._required_inputs.items(): if type_ == "image": # If no resampling is requested, check if resampling is required if not self.resample: check_imgs = { img: nb.load(img) for img in self.inputs_[name] } _check_same_fov(**check_imgs, reference_masker=mask_img, raise_error=True) imgs = list(check_imgs.values()) else: # resampling will only occur if shape/affines are different # making this harmless if all img shapes/affines are the same as the reference imgs = [ resample_to_img(nb.load(img), mask_img, **self._resample_kwargs) for img in self.inputs_[name] ] # input to NiFtiLabelsMasker must be 4d img4d = concat_imgs(imgs, ensure_ndim=4) # Mask required input images using either the dataset's mask or the estimator's. temp_arr = masker.transform(img4d) # An intermediate step to mask out bad voxels. # Can be dropped once PyMARE is able to handle masked arrays or missing data. nonzero_voxels_bool = np.all(temp_arr != 0, axis=0) nonnan_voxels_bool = np.all(~np.isnan(temp_arr), axis=0) good_voxels_bool = np.logical_and(nonzero_voxels_bool, nonnan_voxels_bool) data = masker.transform(img4d) temp_image_inputs[name] = data if "aggressive_mask" not in self.inputs_.keys(): self.inputs_["aggressive_mask"] = good_voxels_bool else: # Remove any voxels that are bad in any image-based inputs self.inputs_["aggressive_mask"] = np.logical_or( self.inputs_["aggressive_mask"], good_voxels_bool, ) elif type_ == "coordinates": # Try to load existing MA maps if hasattr(self, "kernel_transformer"): self.kernel_transformer._infer_names( affine=md5(mask_img.affine).hexdigest()) if self.kernel_transformer.image_type in dataset.images.columns: files = dataset.get_images( ids=self.inputs_["id"], imtype=self.kernel_transformer.image_type, ) if all(f is not None for f in files): self.inputs_["ma_maps"] = files # Calculate IJK matrix indices for target mask # Mask space is assumed to be the same as the Dataset's space # These indices are used directly by any KernelTransformer xyz = self.inputs_["coordinates"][["x", "y", "z"]].values ijk = mm2vox(xyz, mask_img.affine) self.inputs_["coordinates"][["i", "j", "k"]] = ijk # Further reduce image-based inputs to remove "bad" voxels # (voxels with zeros or NaNs in any studies) if "aggressive_mask" in self.inputs_.keys(): n_bad_voxels = (self.inputs_["aggressive_mask"].size - self.inputs_["aggressive_mask"].sum()) if n_bad_voxels: LGR.warning( f"Masking out {n_bad_voxels} additional voxels. " "The updated masker is available in the Estimator.masker attribute." ) for name, raw_masked_data in temp_image_inputs.items(): self.inputs_[name] = raw_masked_data[:, self. inputs_["aggressive_mask"]]
def cluster_stats(stat_img, mask_img, threshold, height_control='fpr', cluster_th=0, nulls=None): """ Return a list of clusters, each cluster being represented by a dictionary. Clusters are sorted by descending size order. Within each cluster, local maxima are sorted by descending statical value Parameters ---------- stat_img: Niimg-like object, statsitical image (presumably in z scale) mask_img: Niimg-like object, mask image threshold: float, cluster forming threshold (either a p-value or z-scale value) height_control: string false positive control meaning of cluster forming threshold: 'fpr'|'fdr'|'bonferroni'|'none' cluster_th: int or float, cluster size threshold nulls: dictionary, statistics of the null distribution Notes ----- If there is no cluster, an empty list is returned """ if nulls is None: nulls = {} # Masking mask_img, stat_img = check_niimg(mask_img), check_niimg(stat_img) if not _check_same_fov(mask_img, stat_img): raise ValueError('mask_img and stat_img do not have the same fov') mask = mask_img.get_data().astype(np.bool) affine = mask_img.get_affine() stat_map = stat_img.get_data() * mask n_voxels = mask.sum() # Thresholding if height_control == 'fpr': z_th = norm.isf(threshold) elif height_control == 'fdr': z_th = fdr_threshold(stat_map[mask], threshold) elif height_control == 'bonferroni': z_th = norm.isf(threshold / n_voxels) else: # Brute-force thresholding z_th = threshold p_th = norm.sf(z_th) # General info info = { 'n_voxels': n_voxels, 'threshold_z': z_th, 'threshold_p': p_th, 'threshold_pcorr': np.minimum(1, p_th * n_voxels) } above_th = stat_map > z_th above_values = stat_map * above_th if (above_th == 0).all(): return [], info # Extract connected components above threshold labels, n_labels = label(above_th) # Extract the local maxima anove the threshold maxima_mask = (above_values == np.maximum(z_th, maximum_filter(above_values, 3))) x, y, z = np.array(np.where(maxima_mask)) maxima_coords = np.array(coord_transform(x, y, z, affine)).T maxima_labels = labels[maxima_mask] maxima_values = above_values[maxima_mask] # FDR-corrected p-values max_fdr_p_values = fdr_p_values(stat_map[mask])[maxima_mask[mask]] # Default "nulls" if not 'zmax' in nulls: nulls['zmax'] = 'bonferroni' if not 'smax' in nulls: nulls['smax'] = None if not 's' in nulls: nulls['s'] = None # Make list of clusters, each cluster being a dictionary clusters = [] for k in range(n_labels): cluster_size = np.sum(labels == k + 1) if cluster_size >= cluster_th: # get the position of the maxima that belong to that cluster in_cluster = maxima_labels == k + 1 # sort the maxima by decreasing statistical value max_vals = maxima_values[in_cluster] sorted_ = max_vals.argsort()[::-1] # Report significance levels in each cluster z_score = max_vals[sorted_] p_values = norm.sf(z_score) # Voxel-level corrected p-values fwer_p_value = None if nulls['zmax'] == 'bonferroni': fwer_p_value = np.minimum(1, p_values * n_voxels) elif isinstance(nulls['zmax'], np.ndarray): fwer_p_value = empirical_p_value(clusters['z_score'], nulls['zmax']) # Cluster-level p-values (corrected) cluster_fwer_p_value = None if isinstance(nulls['smax'], np.ndarray): cluster_fwer_p_value = empirical_p_value( cluster_size, nulls['smax']) # Cluster-level p-values (uncorrected) cluster_p_value = None if isinstance(nulls['s'], np.ndarray): cluster_p_value = empirical_p_value(cluster_size, nulls['s']) # write all this into the cluster structure clusters.append({ 'size': cluster_size, 'maxima': maxima_coords[in_cluster][sorted_], 'z_score': z_score, 'fdr_p_value': max_fdr_p_values[in_cluster][sorted_], 'p_value': p_values, 'fwer_p_value': fwer_p_value, 'cluster_fwer_p_value': cluster_fwer_p_value, 'cluster_p_value': cluster_p_value }) # Sort clusters by descending size order order = np.argsort(-np.array([cluster['size'] for cluster in clusters])) clusters = [clusters[i] for i in order] return clusters, info
# ------------------------------------------------- collection_terms = {'id': 503} image_terms = {'not_mni': False} emotion_data = fetch_neurovault(max_images=None, image_terms=image_terms, collection_terms=collection_terms) n_images = len(emotion_data.images) input_images = [] y = [] groups = [] ref_img = load_img(emotion_data.images[0]) for index in range(n_images): img = emotion_data.images[index] if not _check_same_fov(ref_img, load_img(img)): img = resample_to_img(source_img=img, target_img=ref_img) input_images.append(img) target = emotion_data.images_meta[index]['Rating'] subject_id = emotion_data.images_meta[index]['SubjectID'] y.append(target) groups.append(subject_id) y = np.ravel(y) groups = np.ravel(groups) ###################################################################### # Grid search 'alpha' for prediction # --------------------------------- X_sc = StandardScaler() # RidgeCV ridge = RidgeCV(alphas=np.logspace(-3., 3., 10))
def test_ants_n4(): in_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') unbiased_file = bias_correction.ants_n4(in_file, write_dir=tst.tmpdir) assert_true( _check_same_fov(nibabel.load(unbiased_file), nibabel.load(in_file)))
def test_coregistrator(): anat_file = os.path.join(os.path.dirname(testing_data.__file__), 'anat.nii.gz') func_file = os.path.join(os.path.dirname(testing_data.__file__), 'func.nii.gz') registrator = Coregistrator(output_dir=tst.tmpdir, use_rats_tool=False, verbose=False) assert_raises_regex(ValueError, 'has not been anat fitted. ', registrator.fit_modality, func_file, 'func') registrator.fit_anat(anat_file) assert_equal(registrator.anat_, anat_file) assert_true(registrator._anat_brain_mask is None) assert_raises_regex(ValueError, "Only 'func' and 'perf' ", registrator.fit_modality, func_file, 'diffusion') assert_raises_regex(ValueError, "'t_r' is needed for slice ", registrator.fit_modality, func_file, 'func') assert_raises_regex(ValueError, '`brain_volume` must be ', registrator.fit_modality, func_file, 'func', slice_timing=False, prior_rigid_body_registration=True) # without rigid body registration registrator.fit_modality(func_file, 'func', slice_timing=False) func_img = nibabel.load(func_file) assert_true( _check_same_fov(nibabel.load(registrator.undistorted_func_), func_img)) np.testing.assert_array_almost_equal( nibabel.load(registrator.anat_in_func_space_).affine, func_img.affine) np.testing.assert_array_equal( nibabel.load(registrator.anat_in_func_space_).shape, func_img.shape[:-1]) # test transform_modality_like on an image with oriented as the functional func_like_img = nibabel.Nifti1Image(np.zeros(func_img.shape[:-1]), func_img.affine) func_like_file = os.path.join(tst.tmpdir, 'func_like.nii.gz') func_like_img.to_filename(func_like_file) transformed_file = registrator.transform_modality_like( func_like_file, 'func') transformed_img = nibabel.load(transformed_file) np.testing.assert_array_almost_equal(transformed_img.affine, func_img.affine) np.testing.assert_array_equal(transformed_img.shape, func_img.shape[:-1]) # Similarly with rigid body registration registrator = Coregistrator(output_dir=tst.tmpdir, use_rats_tool=False, verbose=False, brain_volume=400) registrator.fit_anat(anat_file) # Provide manual brain mask mean_func_file = os.path.join(tst.tmpdir, 'mean_func.nii.gz') image.mean_img(func_img).to_filename(mean_func_file) func_brain_mask = compute_histo_brain_mask(mean_func_file, 400, tst.tmpdir, opening=2) registrator.fit_modality(func_file, 'func', slice_timing=False, prior_rigid_body_registration=True, brain_mask_file=func_brain_mask) assert_true( _check_same_fov(nibabel.load(registrator.undistorted_func_), func_img)) np.testing.assert_array_almost_equal( nibabel.load(registrator.anat_in_func_space_).affine, func_img.affine) np.testing.assert_array_equal( nibabel.load(registrator.anat_in_func_space_).shape, func_img.shape[:-1]) func_like_img = nibabel.Nifti1Image(np.zeros(func_img.shape[:-1]), func_img.affine) func_like_file = os.path.join(tst.tmpdir, 'func_like.nii.gz') func_like_img.to_filename(func_like_file) transformed_file = registrator.transform_modality_like( func_like_file, 'func') transformed_img = nibabel.load(transformed_file) np.testing.assert_array_almost_equal(transformed_img.affine, func_img.affine) np.testing.assert_array_equal(transformed_img.shape, func_img.shape[:-1]) # Similarly with perf m0_img = nibabel.Nifti1Image(func_img.get_data()[..., 0], func_img.affine) m0_file = os.path.join(tst.tmpdir, 'm0.nii.gz') m0_img.to_filename(m0_file) registrator.fit_modality(m0_file, 'perf') assert_true( _check_same_fov(nibabel.load(registrator.undistorted_perf_), m0_img)) assert_true( _check_same_fov(nibabel.load(registrator.anat_in_perf_space_), m0_img)) m0_like_img = nibabel.Nifti1Image(np.zeros(m0_img.shape), m0_img.affine) m0_like_file = os.path.join(tst.tmpdir, 'm0_like.nii.gz') m0_like_img.to_filename(m0_like_file) transformed_file = registrator.transform_modality_like( m0_like_file, 'perf') transformed_img = nibabel.load(transformed_file) assert_true(_check_same_fov(transformed_img, m0_img))
def _make_parcellation(imgs, clustering_index, clustering, n_pieces, masker, smoothing_fwhm=5, verbose=0): """Convenience function to use nilearn Parcellation class in our pipeline. It is used to find local regions of the brain in which alignment will be later applied. For alignment computational efficiency, regions should be of hundreds of voxels. Parameters ---------- imgs: Niimgs data to cluster clustering_index: list of integers Clustering is performed on a subset of the data chosen randomly in timeframes. This index carries this subset. clustering: string or 3D Niimg In : {'kmeans', 'ward', 'rena'}, passed to nilearn Parcellations class. If you aim for speed, choose k-means (and check kmeans_smoothing_fwhm parameter) If you want spatially connected and/or reproducible regions use 'ward' If you want balanced clusters (especially from timeseries) used 'hierarchical_kmeans' For 'rena', need nilearn > 0.5.2 If 3D Niimg, image used as predefined clustering, n_pieces is ignored n_pieces: int number of different labels masker: instance of NiftiMasker or MultiNiftiMasker Masker to be used on the data. For more information see: http://nilearn.github.io/manipulating_images/masker_objects.html smoothing_fwhm: None or int By default 5mm smoothing will be applied before kmeans clustering to have more compact clusters (but this will not change the data later). To disable this option, this parameter should be None. Returns ------- labels : list of ints (len n_features) Parcellation of features in clusters """ # check if clustering is provided if type(clustering) == nib.nifti1.Nifti1Image or os.path.isfile( clustering): _check_same_fov(masker.mask_img_, clustering) labels = _apply_mask_fmri(clustering, masker.mask_img_).astype(int) # otherwise check it's needed, if not return 1 everywhere elif n_pieces == 1: labels = np.ones(int(masker.mask_img_.get_fdata().sum()), dtype=np.int8) # otherwise check requested clustering method elif clustering == "hierarchical_kmeans" and n_pieces > 1: imgs_subset = index_img(imgs, clustering_index) if smoothing_fwhm is not None: X = masker.transform(smooth_img(imgs_subset, smoothing_fwhm)) else: X = masker.transform(imgs_subset) labels = _hierarchical_k_means( X.T, n_clusters=n_pieces, verbose=verbose) + 1 elif clustering in ['kmeans', 'ward', 'rena'] and n_pieces > 1: imgs_subset = index_img(imgs, clustering_index) if clustering == "kmeans" and smoothing_fwhm is not None: images_to_parcel = smooth_img(imgs_subset, smoothing_fwhm) else: images_to_parcel = imgs_subset try: parcellation = Parcellations(method=clustering, n_parcels=n_pieces, mask=masker, scaling=False, n_iter=20, verbose=verbose) except TypeError: if clustering == "rena" and (version.parse(nilearn.__version__) <= version.parse("0.5.2")): raise InputError(( 'ReNA algorithm is only available in Nilearn version > 0.5.2. \ Your version is {}. If you want to use ReNA, please run "pip install nilearn --upgrade"' .format(nilearn.__version__))) else: parcellation = Parcellations(method=clustering, n_parcels=n_pieces, mask=masker, verbose=verbose) parcellation.fit(images_to_parcel) labels = _apply_mask_fmri(parcellation.labels_img_, masker.mask_img_).astype(int) else: raise InputError(( 'Clustering should be "kmeans", "ward", "rena", "hierarchical_kmeans", \ or a 3D Niimg, and n_pieces should be an integer ≥ 1')) if verbose > 0: unique_labels, counts = np.unique(labels, return_counts=True) print("The alignment will be applied on parcels of sizes {}".format( counts)) # raise warning if some parcels are bigger than 1000 voxels _check_labels(labels) return labels
def _preprocess_input(self, dataset): """Preprocess inputs to the Estimator from the Dataset as needed.""" masker = self.masker or dataset.masker mask_img = masker.mask_img or masker.labels_img if isinstance(mask_img, str): mask_img = nib.load(mask_img) # Ensure that protected values are not included among _required_inputs assert "aggressive_mask" not in self._required_inputs.keys( ), "This is a protected name." if "aggressive_mask" in self.inputs_.keys(): LGR.warning("Removing existing 'aggressive_mask' from Estimator.") self.inputs_.pop("aggressive_mask") # A dictionary to collect masked image data, to be further reduced by the aggressive mask. temp_image_inputs = {} for name, (type_, _) in self._required_inputs.items(): if type_ == "image": # If no resampling is requested, check if resampling is required if not self.resample: check_imgs = { img: nib.load(img) for img in self.inputs_[name] } _check_same_fov(**check_imgs, reference_masker=mask_img, raise_error=True) imgs = list(check_imgs.values()) else: # resampling will only occur if shape/affines are different # making this harmless if all img shapes/affines are the same as the reference imgs = [ resample_to_img(nib.load(img), mask_img, **self._resample_kwargs) for img in self.inputs_[name] ] # input to NiFtiLabelsMasker must be 4d img4d = concat_imgs(imgs, ensure_ndim=4) # Mask required input images using either the dataset's mask or the estimator's. temp_arr = masker.transform(img4d) # An intermediate step to mask out bad voxels. # Can be dropped once PyMARE is able to handle masked arrays or missing data. nonzero_voxels_bool = np.all(temp_arr != 0, axis=0) nonnan_voxels_bool = np.all(~np.isnan(temp_arr), axis=0) good_voxels_bool = np.logical_and(nonzero_voxels_bool, nonnan_voxels_bool) data = masker.transform(img4d) temp_image_inputs[name] = data if "aggressive_mask" not in self.inputs_.keys(): self.inputs_["aggressive_mask"] = good_voxels_bool else: # Remove any voxels that are bad in any image-based inputs self.inputs_["aggressive_mask"] = np.logical_or( self.inputs_["aggressive_mask"], good_voxels_bool, ) # Further reduce image-based inputs to remove "bad" voxels # (voxels with zeros or NaNs in any studies) if "aggressive_mask" in self.inputs_.keys(): n_bad_voxels = (self.inputs_["aggressive_mask"].size - self.inputs_["aggressive_mask"].sum()) if n_bad_voxels: LGR.warning( f"Masking out {n_bad_voxels} additional voxels. " "The updated masker is available in the Estimator.masker attribute." ) for name, raw_masked_data in temp_image_inputs.items(): self.inputs_[ name] = raw_masked_data[:, self.inputs_["aggressive_mask"]]
def cluster_stats(stat_img, mask_img, threshold, height_control='fpr', cluster_th=0, nulls=None): """ Return a list of clusters, each cluster being represented by a dictionary. Clusters are sorted by descending size order. Within each cluster, local maxima are sorted by descending statical value Parameters ---------- stat_img: Niimg-like object, statsitical image (presumably in z scale) mask_img: Niimg-like object, mask image threshold: float, cluster forming threshold (either a p-value or z-scale value) height_control: string false positive control meaning of cluster forming threshold: 'fpr'|'fdr'|'bonferroni'|'none' cluster_th: int or float, cluster size threshold nulls: dictionary, statistics of the null distribution Notes ----- If there is no cluster, an empty list is returned """ if nulls is None: nulls = {} # Masking mask_img, stat_img = check_niimg(mask_img), check_niimg(stat_img) if not _check_same_fov(mask_img, stat_img): raise ValueError('mask_img and stat_img do not have the same fov') mask = mask_img.get_data().astype(np.bool) affine = mask_img.get_affine() stat_map = stat_img.get_data() * mask n_voxels = mask.sum() # Thresholding if height_control == 'fpr': z_th = norm.isf(threshold) elif height_control == 'fdr': z_th = fdr_threshold(stat_map[mask], threshold) elif height_control == 'bonferroni': z_th = norm.isf(threshold / n_voxels) else: # Brute-force thresholding z_th = threshold p_th = norm.sf(z_th) # General info info = {'n_voxels': n_voxels, 'threshold_z': z_th, 'threshold_p': p_th, 'threshold_pcorr': np.minimum(1, p_th * n_voxels)} above_th = stat_map > z_th above_values = stat_map * above_th if (above_th == 0).all(): return [], info # Extract connected components above threshold labels, n_labels = label(above_th) # Extract the local maxima anove the threshold maxima_mask = (above_values == np.maximum(z_th, maximum_filter(above_values, 3))) x, y, z = np.array(np.where(maxima_mask)) maxima_coords = np.array(coord_transform(x, y, z, affine)).T maxima_labels = labels[maxima_mask] maxima_values = above_values[maxima_mask] # FDR-corrected p-values max_fdr_p_values = fdr_p_values(stat_map[mask])[maxima_mask[mask]] # Default "nulls" if not 'zmax' in nulls: nulls['zmax'] = 'bonferroni' if not 'smax' in nulls: nulls['smax'] = None if not 's' in nulls: nulls['s'] = None # Make list of clusters, each cluster being a dictionary clusters = [] for k in range(n_labels): cluster_size = np.sum(labels == k + 1) if cluster_size >= cluster_th: # get the position of the maxima that belong to that cluster in_cluster = maxima_labels == k + 1 # sort the maxima by decreasing statistical value max_vals = maxima_values[in_cluster] sorted_ = max_vals.argsort()[::-1] # Report significance levels in each cluster z_score = max_vals[sorted_] p_values = norm.sf(z_score) # Voxel-level corrected p-values fwer_p_value = None if nulls['zmax'] == 'bonferroni': fwer_p_value = np.minimum(1, p_values * n_voxels) elif isinstance(nulls['zmax'], np.ndarray): fwer_p_value = empirical_p_value( clusters['z_score'], nulls['zmax']) # Cluster-level p-values (corrected) cluster_fwer_p_value = None if isinstance(nulls['smax'], np.ndarray): cluster_fwer_p_value = empirical_p_value( cluster_size, nulls['smax']) # Cluster-level p-values (uncorrected) cluster_p_value = None if isinstance(nulls['s'], np.ndarray): cluster_p_value = empirical_p_value( cluster_size, nulls['s']) # write all this into the cluster structure clusters.append({ 'size': cluster_size, 'maxima': maxima_coords[in_cluster][sorted_], 'z_score': z_score, 'fdr_p_value': max_fdr_p_values[in_cluster][sorted_], 'p_value': p_values, 'fwer_p_value': fwer_p_value, 'cluster_fwer_p_value': cluster_fwer_p_value, 'cluster_p_value': cluster_p_value }) # Sort clusters by descending size order order = np.argsort(- np.array([cluster['size'] for cluster in clusters])) clusters = [clusters[i] for i in order] return clusters, info
def filter_and_mask(imgs, mask_img_, parameters, memory_level=0, memory=Memory(cachedir=None), verbose=0, confounds=None, copy=True, sample_mask=None): # If we have a string (filename), we won't need to copy, as # there will be no side effect if isinstance(imgs, _basestring): copy = False if verbose > 0: class_name = enclosing_scope_name(stack_level=2) mask_img_ = _utils.check_niimg_3d(mask_img_) imgs = _utils.check_niimg(imgs, atleast_4d=True) if sample_mask is not None: imgs = image.index_img(imgs, sample_mask) # Resampling: allows the user to change the affine, the shape or both if verbose > 1: print("[%s] Resampling" % class_name) # Check whether resampling is truly necessary. If so, crop mask # as small as possible in order to speed up the process if not _check_same_fov(imgs, mask_img_): # now we can crop mask_img_ = image.crop_img(mask_img_, copy=False) imgs = cache(image.resample_img, memory, func_memory_level=2, memory_level=memory_level, ignore=['copy'])( imgs, target_affine=mask_img_.get_affine(), target_shape=mask_img_.shape, copy=copy) # Load data (if filenames are given, load them) if verbose > 0: print("[%s] Loading data from %s" % ( class_name, _utils._repr_niimgs(imgs)[:200])) # Get series from data with optional smoothing if verbose > 1: print("[%s] Masking and smoothing" % class_name) data = masking.apply_mask(imgs, mask_img_, smoothing_fwhm=parameters['smoothing_fwhm']) # Temporal # ======== # Detrending (optional) # Filtering # Confounds removing (from csv file or numpy array) # Normalizing if verbose > 1: print("[%s] Cleaning signal" % class_name) if not 'sessions' in parameters or parameters['sessions'] is None: clean_memory_level = 2 if (parameters['high_pass'] is not None and parameters['low_pass'] is not None): clean_memory_level = 4 data = cache(signal.clean, memory, func_memory_level=clean_memory_level, memory_level=memory_level)( data, confounds=confounds, low_pass=parameters['low_pass'], high_pass=parameters['high_pass'], t_r=parameters['t_r'], detrend=parameters['detrend'], standardize=parameters['standardize']) else: sessions = parameters['sessions'] if not len(sessions) == len(data): raise ValueError(('The length of the session vector (%i) ' 'does not match the length of the data (%i)') % (len(sessions), len(data))) for s in np.unique(sessions): if confounds is not None: confounds = confounds[sessions == s] data[sessions == s, :] = \ cache(signal.clean, memory, func_memory_level=2, memory_level=memory_level)( data[sessions == s, :], confounds=confounds, low_pass=parameters['low_pass'], high_pass=parameters['high_pass'], t_r=parameters['t_r'], detrend=parameters['detrend'], standardize=parameters['standardize'] ) # For _later_: missing value removal or imputing of missing data # (i.e. we want to get rid of NaNs, if smoothing must be done # earlier) # Optionally: 'doctor_nan', remove voxels with NaNs, other option # for later: some form of imputation return data, imgs.get_affine()
def filter_and_mask(imgs, mask_img_, parameters, memory_level=0, memory=Memory(cachedir=None), verbose=0, confounds=None, copy=True, sample_mask=None): # If we have a string (filename), we won't need to copy, as # there will be no side effect if isinstance(imgs, _basestring): copy = False if verbose > 0: class_name = enclosing_scope_name(stack_level=2) mask_img_ = _utils.check_niimg_3d(mask_img_) imgs = _utils.check_niimg(imgs, atleast_4d=True, ensure_ndim=4) if sample_mask is not None: imgs = image.index_img(imgs, sample_mask) # Resampling: allows the user to change the affine, the shape or both if verbose > 1: print("[%s] Resampling" % class_name) # Check whether resampling is truly necessary. If so, crop mask # as small as possible in order to speed up the process if not _check_same_fov(imgs, mask_img_): # now we can crop mask_img_ = image.crop_img(mask_img_, copy=False) imgs = cache(image.resample_img, memory, func_memory_level=2, memory_level=memory_level, ignore=['copy'])( imgs, target_affine=mask_img_.get_affine(), target_shape=mask_img_.shape, copy=copy) # Load data (if filenames are given, load them) if verbose > 0: print("[%s] Loading data from %s" % ( class_name, _utils._repr_niimgs(imgs)[:200])) # Get series from data with optional smoothing if verbose > 1: print("[%s] Masking and smoothing" % class_name) data = masking.apply_mask(imgs, mask_img_, smoothing_fwhm=parameters['smoothing_fwhm']) # Temporal # ======== # Detrending (optional) # Filtering # Confounds removing (from csv file or numpy array) # Normalizing if verbose > 1: print("[%s] Cleaning signal" % class_name) if 'sessions' not in parameters or parameters['sessions'] is None: clean_memory_level = 2 if (parameters['high_pass'] is not None and parameters['low_pass'] is not None): clean_memory_level = 4 data = cache(signal.clean, memory, func_memory_level=clean_memory_level, memory_level=memory_level)( data, confounds=confounds, low_pass=parameters['low_pass'], high_pass=parameters['high_pass'], t_r=parameters['t_r'], detrend=parameters['detrend'], standardize=parameters['standardize']) else: sessions = parameters['sessions'] if not len(sessions) == len(data): raise ValueError(('The length of the session vector (%i) ' 'does not match the length of the data (%i)') % (len(sessions), len(data))) for s in np.unique(sessions): if confounds is not None: confounds = confounds[sessions == s] data[sessions == s, :] = \ cache(signal.clean, memory, func_memory_level=2, memory_level=memory_level)( data[sessions == s, :], confounds=confounds, low_pass=parameters['low_pass'], high_pass=parameters['high_pass'], t_r=parameters['t_r'], detrend=parameters['detrend'], standardize=parameters['standardize'] ) # For _later_: missing value removal or imputing of missing data # (i.e. we want to get rid of NaNs, if smoothing must be done # earlier) # Optionally: 'doctor_nan', remove voxels with NaNs, other option # for later: some form of imputation return data, imgs.get_affine()