def preprocess_brain_image(image, truncate_intensity=(0.01, 0.99), brain_extraction_modality=None, template_transform_type=None, template="biobank", do_bias_correction=True, return_bias_field=False, do_denoising=True, intensity_matching_type=None, reference_image=None, intensity_normalization_type=None, antsxnet_cache_directory=None, verbose=True): """ Basic preprocessing pipeline for T1-weighted brain MRI Standard preprocessing steps that have been previously described in various papers including the cortical thickness pipeline: https://www.ncbi.nlm.nih.gov/pubmed/24879923 Arguments --------- image : ANTsImage input image truncate_intensity : 2-length tuple Defines the quantile threshold for truncating the image intensity brain_extraction_modality : string or None Perform brain extraction using antspynet tools. One of "t1", "t1v0", "t1nobrainer", "t1combined", "flair", "t2", "bold", "fa", "t1infant", "t2infant", or None. template_transform_type : string See details in help for ants.registration. Typically "Rigid" or "Affine". template : ANTs image (not skull-stripped) Alternatively, one can specify the default "biobank" or "croppedMni152" to download and use premade templates. do_bias_correction : boolean Perform N4 bias field correction. return_bias_field : boolean If True, return bias field as an additional output *without* bias correcting the preprocessed image. do_denoising : boolean Perform non-local means denoising. intensity_matching_type : string Either "regression" or "histogram". Only is performed if reference_image is not None. reference_image : ANTs image Reference image for intensity matching. intensity_normalization_type : string Either rescale the intensities to [0,1] (i.e., "01") or zero-mean, unit variance (i.e., "0mean"). If None normalization is not performed. antsxnet_cache_directory : string Destination directory for storing the downloaded template and model weights. Since these can be resused, if is None, these data will be downloaded to a ~/.keras/ANTsXNet/. verbose : boolean Print progress to the screen. Returns ------- Dictionary with preprocessing information ANTs image (i.e., source_image) matched to the (reference_image). Example ------- >>> import ants >>> image = ants.image_read(ants.get_ants_data('r16')) >>> preprocessed_image = preprocess_brain_image(image, do_brain_extraction=False) """ from ..utilities import brain_extraction from ..utilities import regression_match_image from ..utilities import get_antsxnet_data preprocessed_image = ants.image_clone(image) if antsxnet_cache_directory == None: antsxnet_cache_directory = "ANTsXNet" # Truncate intensity if truncate_intensity is not None: quantiles = (image.quantile(truncate_intensity[0]), image.quantile(truncate_intensity[1])) if verbose == True: print("Preprocessing: truncate intensities ( low =", quantiles[0], ", high =", quantiles[1], ").") preprocessed_image[image < quantiles[0]] = quantiles[0] preprocessed_image[image > quantiles[1]] = quantiles[1] # Brain extraction mask = None if brain_extraction_modality is not None: if verbose == True: print("Preprocessing: brain extraction.") probability_mask = brain_extraction(preprocessed_image, modality=brain_extraction_modality, antsxnet_cache_directory=antsxnet_cache_directory, verbose=verbose) mask = ants.threshold_image(probability_mask, 0.5, 1, 1, 0) mask = ants.morphology(mask,"close",6).iMath_fill_holes() # Template normalization transforms = None if template_transform_type is not None: template_image = None if isinstance(template, str): template_file_name_path = get_antsxnet_data(template, antsxnet_cache_directory=antsxnet_cache_directory) template_image = ants.image_read(template_file_name_path) else: template_image = template if mask is None: registration = ants.registration(fixed=template_image, moving=preprocessed_image, type_of_transform=template_transform_type, verbose=verbose) preprocessed_image = registration['warpedmovout'] transforms = dict(fwdtransforms=registration['fwdtransforms'], invtransforms=registration['invtransforms']) else: template_probability_mask = brain_extraction(template_image, modality=brain_extraction_modality, antsxnet_cache_directory=antsxnet_cache_directory, verbose=verbose) template_mask = ants.threshold_image(template_probability_mask, 0.5, 1, 1, 0) template_brain_image = template_mask * template_image preprocessed_brain_image = preprocessed_image * mask registration = ants.registration(fixed=template_brain_image, moving=preprocessed_brain_image, type_of_transform=template_transform_type, verbose=verbose) transforms = dict(fwdtransforms=registration['fwdtransforms'], invtransforms=registration['invtransforms']) preprocessed_image = ants.apply_transforms(fixed = template_image, moving = preprocessed_image, transformlist=registration['fwdtransforms'], interpolator="linear", verbose=verbose) mask = ants.apply_transforms(fixed = template_image, moving = mask, transformlist=registration['fwdtransforms'], interpolator="genericLabel", verbose=verbose) # Do bias correction bias_field = None if do_bias_correction == True: if verbose == True: print("Preprocessing: brain correction.") n4_output = None if mask is None: n4_output = ants.n4_bias_field_correction(preprocessed_image, shrink_factor=4, return_bias_field=return_bias_field, verbose=verbose) else: n4_output = ants.n4_bias_field_correction(preprocessed_image, mask, shrink_factor=4, return_bias_field=return_bias_field, verbose=verbose) if return_bias_field == True: bias_field = n4_output else: preprocessed_image = n4_output # Denoising if do_denoising == True: if verbose == True: print("Preprocessing: denoising.") if mask is None: preprocessed_image = ants.denoise_image(preprocessed_image, shrink_factor=1) else: preprocessed_image = ants.denoise_image(preprocessed_image, mask, shrink_factor=1) # Image matching if reference_image is not None and intensity_matching_type is not None: if verbose == True: print("Preprocessing: intensity matching.") if intensity_matching_type == "regression": preprocessed_image = regression_match_image(preprocessed_image, reference_image) elif intensity_matching_type == "histogram": preprocessed_image = ants.histogram_match_image(preprocessed_image, reference_image) else: raise ValueError("Unrecognized intensity_matching_type.") # Intensity normalization if intensity_normalization_type is not None: if verbose == True: print("Preprocessing: intensity normalization.") if intensity_normalization_type == "01": preprocessed_image = (preprocessed_image - preprocessed_image.min())/(preprocessed_image.max() - preprocessed_image.min()) elif intensity_normalization_type == "0mean": preprocessed_image = (preprocessed_image - preprocessed_image.mean())/preprocessed_image.std() else: raise ValueError("Unrecognized intensity_normalization_type.") return_dict = {'preprocessed_image' : preprocessed_image} if mask is not None: return_dict['brain_mask'] = mask if bias_field is not None: return_dict['bias_field'] = bias_field if transforms is not None: return_dict['template_transforms'] = transforms return(return_dict)
def test_morphology(self): fi = ants.image_read(ants.get_ants_data("r16")) mask = ants.get_mask(fi) dilated_ball = ants.morphology(mask, operation="dilate", radius=3, mtype="binary", shape="ball") eroded_box = ants.morphology(mask, operation="erode", radius=3, mtype="binary", shape="box") opened_annulus = ants.morphology( mask, operation="open", radius=5, mtype="binary", shape="annulus", thickness=2, ) ## --- ## mtype = "binary" mask = mask.clone() for operation in {"dilate", "erode", "open", "close"}: for shape in {"ball", "box", "cross", "annulus", "polygon"}: morph = ants.morphology(mask, operation=operation, radius=3, mtype=mtype, shape=shape) self.assertTrue( isinstance(morph, ants.core.ants_image.ANTsImage)) # invalid operation with self.assertRaises(Exception): ants.morphology(mask, operation="invalid-operation", radius=3, mtype=mtype, shape=shape) mtype = "grayscale" img = ants.image_read(ants.get_ants_data("r16")) for operation in {"dilate", "erode", "open", "close"}: morph = ants.morphology(img, operation=operation, radius=3, mtype=mtype) self.assertTrue(isinstance(morph, ants.core.ants_image.ANTsImage)) # invalid operation with self.assertRaises(Exception): ants.morphology(img, operation="invalid-operation", radius=3, mtype="grayscale") # invalid morphology type with self.assertRaises(Exception): ants.morphology(img, operation="dilate", radius=3, mtype="invalid-morphology") # invalid shape with self.assertRaises(Exception): ants.morphology( mask, operation="dilate", radius=3, mtype="binary", shape="invalid-shape", )
def test_morphology(self): fi = ants.image_read(ants.get_ants_data('r16')) mask = fi > fi.mean() dilated_ball = ants.morphology(mask, operation='dilate', radius=3, mtype='binary', shape='ball') eroded_box = ants.morphology(mask, operation='erode', radius=3, mtype='binary', shape='box') opened_annulus = ants.morphology(mask, operation='open', radius=5, mtype='binary', shape='annulus', thickness=2) ## --- ## mtype = 'binary' mask = mask.clone() for operation in {'dilate', 'erode', 'open', 'close'}: for shape in {'ball', 'box', 'cross', 'annulus', 'polygon'}: morph = ants.morphology(mask, operation=operation, radius=3, mtype=mtype, shape=shape) self.assertTrue( isinstance(morph, ants.core.ants_image.ANTsImage)) # invalid operation with self.assertRaises(Exception): ants.morphology(mask, operation='invalid-operation', radius=3, mtype=mtype, shape=shape) mtype = 'grayscale' img = ants.image_read(ants.get_ants_data('r16')) for operation in {'dilate', 'erode', 'open', 'close'}: morph = ants.morphology(img, operation=operation, radius=3, mtype=mtype) self.assertTrue(isinstance(morph, ants.core.ants_image.ANTsImage)) # invalid operation with self.assertRaises(Exception): ants.morphology(img, operation='invalid-operation', radius=3, mtype='grayscale') #invalid morphology type with self.assertRaises(Exception): ants.morphology(img, operation='dilate', radius=3, mtype='invalid-morphology') # invalid shape with self.assertRaises(Exception): ants.morphology(mask, operation='dilate', radius=3, mtype='binary', shape='invalid-shape')
def brain_extraction(image, modality, antsxnet_cache_directory=None, verbose=False): """ Perform brain extraction using U-net and ANTs-based training data. "NoBrainer" is also possible where brain extraction uses U-net and FreeSurfer training data ported from the https://github.com/neuronets/nobrainer-models Arguments --------- image : ANTsImage input image (or list of images for multi-modal scenarios). modality : string Modality image type. Options include: * "t1": T1-weighted MRI---ANTs-trained. Previous versions are specified as "t1.v0", "t1.v1". * "t1nobrainer": T1-weighted MRI---FreeSurfer-trained: h/t Satra Ghosh and Jakub Kaczmarzyk. * "t1combined": Brian's combination of "t1" and "t1nobrainer". One can also specify "t1combined[X]" where X is the morphological radius. X = 12 by default. * "flair": FLAIR MRI. Previous versions are specified as "flair.v0". * "t2": T2 MRI. Previous versions are specified as "t2.v0". * "t2star": T2Star MRI. * "bold": 3-D mean BOLD MRI. Previous versions are specified as "bold.v0". * "fa": fractional anisotropy. Previous versions are specified as "fa.v0". * "t1t2infant": Combined T1-w/T2-w infant MRI h/t Martin Styner. * "t1infant": T1-w infant MRI h/t Martin Styner. * "t2infant": T2-w infant MRI h/t Martin Styner. antsxnet_cache_directory : string Destination directory for storing the downloaded template and model weights. Since these can be resused, if is None, these data will be downloaded to a ~/.keras/ANTsXNet/. verbose : boolean Print progress to the screen. Returns ------- ANTs probability brain mask image. Example ------- >>> probability_brain_mask = brain_extraction(brain_image, modality="t1") """ from ..architectures import create_unet_model_3d from ..utilities import get_pretrained_network from ..utilities import get_antsxnet_data from ..architectures import create_nobrainer_unet_model_3d from ..utilities import decode_unet classes = ("background", "brain") number_of_classification_labels = len(classes) channel_size = 1 if isinstance(image, list): channel_size = len(image) if antsxnet_cache_directory == None: antsxnet_cache_directory = "ANTsXNet" input_images = list() if channel_size == 1: input_images.append(image) else: input_images = image if input_images[0].dimension != 3: raise ValueError("Image dimension must be 3.") if "t1combined" in modality: # Need to change with voxel resolution morphological_radius = 12 if '[' in modality and ']' in modality: morphological_radius = int(modality.split("[")[1].split("]")[0]) brain_extraction_t1 = brain_extraction( image, modality="t1", antsxnet_cache_directory=antsxnet_cache_directory, verbose=verbose) brain_mask = ants.iMath_get_largest_component( ants.threshold_image(brain_extraction_t1, 0.5, 10000)) brain_mask = ants.morphology(brain_mask, "close", morphological_radius).iMath_fill_holes() brain_extraction_t1nobrainer = brain_extraction( image * ants.iMath_MD(brain_mask, radius=morphological_radius), modality="t1nobrainer", antsxnet_cache_directory=antsxnet_cache_directory, verbose=verbose) brain_extraction_combined = ants.iMath_fill_holes( ants.iMath_get_largest_component(brain_extraction_t1nobrainer * brain_mask)) brain_extraction_combined = brain_extraction_combined + ants.iMath_ME( brain_mask, morphological_radius) + brain_mask return (brain_extraction_combined) if modality != "t1nobrainer": ##################### # # ANTs-based # ##################### weights_file_name_prefix = None is_standard_network = False if modality == "t1.v0": weights_file_name_prefix = "brainExtraction" elif modality == "t1.v1": weights_file_name_prefix = "brainExtractionT1v1" is_standard_network = True elif modality == "t1": weights_file_name_prefix = "brainExtractionRobustT1" is_standard_network = True elif modality == "t2.v0": weights_file_name_prefix = "brainExtractionT2" elif modality == "t2": weights_file_name_prefix = "brainExtractionRobustT2" is_standard_network = True elif modality == "t2star": weights_file_name_prefix = "brainExtractionRobustT2Star" is_standard_network = True elif modality == "flair.v0": weights_file_name_prefix = "brainExtractionFLAIR" elif modality == "flair": weights_file_name_prefix = "brainExtractionRobustFLAIR" is_standard_network = True elif modality == "bold.v0": weights_file_name_prefix = "brainExtractionBOLD" elif modality == "bold": weights_file_name_prefix = "brainExtractionRobustBOLD" is_standard_network = True elif modality == "fa.v0": weights_file_name_prefix = "brainExtractionFA" elif modality == "fa": weights_file_name_prefix = "brainExtractionRobustFA" is_standard_network = True elif modality == "t1t2infant": weights_file_name_prefix = "brainExtractionInfantT1T2" elif modality == "t1infant": weights_file_name_prefix = "brainExtractionInfantT1" elif modality == "t2infant": weights_file_name_prefix = "brainExtractionInfantT2" else: raise ValueError("Unknown modality type.") if verbose == True: print("Brain extraction: retrieving model weights.") weights_file_name = get_pretrained_network( weights_file_name_prefix, antsxnet_cache_directory=antsxnet_cache_directory) if verbose == True: print("Brain extraction: retrieving template.") reorient_template_file_name_path = get_antsxnet_data( "S_template3", antsxnet_cache_directory=antsxnet_cache_directory) reorient_template = ants.image_read(reorient_template_file_name_path) if is_standard_network and modality != "t1.v1": ants.set_spacing(reorient_template, (1.5, 1.5, 1.5)) resampled_image_size = reorient_template.shape number_of_filters = (8, 16, 32, 64) mode = "classification" if is_standard_network: number_of_filters = (16, 32, 64, 128) number_of_classification_labels = 1 mode = "sigmoid" unet_model = create_unet_model_3d( (*resampled_image_size, channel_size), number_of_outputs=number_of_classification_labels, mode=mode, number_of_filters=number_of_filters, dropout_rate=0.0, convolution_kernel_size=3, deconvolution_kernel_size=2, weight_decay=1e-5) unet_model.load_weights(weights_file_name) if verbose == True: print("Brain extraction: normalizing image to the template.") center_of_mass_template = ants.get_center_of_mass(reorient_template) center_of_mass_image = ants.get_center_of_mass(input_images[0]) translation = np.asarray(center_of_mass_image) - np.asarray( center_of_mass_template) xfrm = ants.create_ants_transform( transform_type="Euler3DTransform", center=np.asarray(center_of_mass_template), translation=translation) batchX = np.zeros((1, *resampled_image_size, channel_size)) for i in range(len(input_images)): warped_image = ants.apply_ants_transform_to_image( xfrm, input_images[i], reorient_template) if is_standard_network and modality != "t1.v1": batchX[0, :, :, :, i] = (ants.iMath(warped_image, "Normalize")).numpy() else: warped_array = warped_image.numpy() batchX[0, :, :, :, i] = (warped_array - warped_array.mean()) / warped_array.std() if verbose == True: print("Brain extraction: prediction and decoding.") predicted_data = unet_model.predict(batchX, verbose=verbose) probability_images_array = decode_unet(predicted_data, reorient_template) if verbose == True: print( "Brain extraction: renormalize probability mask to native space." ) xfrm_inv = xfrm.invert() probability_image = xfrm_inv.apply_to_image( probability_images_array[0][number_of_classification_labels - 1], input_images[0]) return (probability_image) else: ##################### # # NoBrainer # ##################### if verbose == True: print("NoBrainer: generating network.") model = create_nobrainer_unet_model_3d((None, None, None, 1)) weights_file_name = get_pretrained_network( "brainExtractionNoBrainer", antsxnet_cache_directory=antsxnet_cache_directory) model.load_weights(weights_file_name) if verbose == True: print( "NoBrainer: preprocessing (intensity truncation and resampling)." ) image_array = image.numpy() image_robust_range = np.quantile( image_array[np.where(image_array != 0)], (0.02, 0.98)) threshold_value = 0.10 * (image_robust_range[1] - image_robust_range[0] ) + image_robust_range[0] thresholded_mask = ants.threshold_image(image, -10000, threshold_value, 0, 1) thresholded_image = image * thresholded_mask image_resampled = ants.resample_image(thresholded_image, (256, 256, 256), use_voxels=True) image_array = np.expand_dims(image_resampled.numpy(), axis=0) image_array = np.expand_dims(image_array, axis=-1) if verbose == True: print("NoBrainer: predicting mask.") brain_mask_array = np.squeeze( model.predict(image_array, verbose=verbose)) brain_mask_resampled = ants.copy_image_info( image_resampled, ants.from_numpy(brain_mask_array)) brain_mask_image = ants.resample_image(brain_mask_resampled, image.shape, use_voxels=True, interp_type=1) spacing = ants.get_spacing(image) spacing_product = spacing[0] * spacing[1] * spacing[2] minimum_brain_volume = round(649933.7 / spacing_product) brain_mask_labeled = ants.label_clusters(brain_mask_image, minimum_brain_volume) return (brain_mask_labeled)