def boundaries( atlas, boundaries_out_path, atlas_labels=False, atlas_scale=None, transformation_matrix=None, ): """ Generate the boundary image, which is the border between each segmentation region. Useful for overlaying on the raw image to assess the registration and segmentation :param atlas: The registered atlas :param boundaries_out_path: Path to save the boundary image :param atlas_labels: If True, keep the numerical values of the atlas for the labels :param atlas_scale: Image scaling so that the resulting nifti can be processed using other tools. :param transformation_matrix: Transformation matrix so that the resulting nifti can be processed using other tools. """ boundaries_image = find_boundaries(atlas, mode="inner") if atlas_labels: boundaries_image = boundaries_image * atlas boundaries_image = scale_and_convert_to_16_bits(boundaries_image) logging.debug("Saving segmentation boundary image") brainio.to_nii( boundaries_image, boundaries_out_path, scale=atlas_scale, affine_transform=transformation_matrix, )
def extract(self, image, voxel_size=10): self.logging.info("Processing " + self.img_filepath) self.logging.info("Gaussian filtering with kernel size: {}".format( self.gaussian_kernel)) # Gaussian filter kernel_shape = [ self.gaussian_kernel, self.gaussian_kernel, self.gaussian_kernel_z, ] image = gaussian_filter(image, kernel_shape) self.logging.info("Filtering completed") # Thresholding if self.threshold_type.lower() == "otsu": thresh = threshold_otsu(image) self.logging.info("Thresholding with {} threshold type".format( self.threshold_type)) elif (self.threshold_type.lower() == "percentile" or self.threshold_type.lower() == "perc"): thresh = np.percentile(image.ravel(), self.percentile_threshold) self.logging.info("Thresholding with {} threshold type. " "{}th percentile [{}]".format( self.threshold_type, self.percentile_threshold, thresh)) else: raise ValueError("Unrecognised thresholding type: " + self.threshold_type) binary = image > thresh binary = keep_n_largest_objects(binary) # Save thresholded image if not os.path.isfile(self.thresholded_savepath) or self.overwrite: self.logging.info("Saving thresholded image to {}".format( self.thresholded_savepath)) brainio.to_nii(binary.astype(np.int16), self.thresholded_savepath) binary = reorient_image(binary, invert_axes=[ 2, ], orientation="coronal") # apply marching cubes self.logging.info("Extracting surface from thresholded image") verts, faces, normals, values = measure.marching_cubes_lewiner( binary, 0, step_size=1) # Scale to atlas spacing if voxel_size is not 1: verts = verts * voxel_size # Save image to .obj self.logging.info(" Saving .obj at {}".format(self.obj_path)) faces = faces + 1 marching_cubes_to_obj((verts, faces, normals, values), self.obj_path)
def test_nii_to_tiff(tmpdir, start_array): nii_path = os.path.join(str(tmpdir), "test_array.nii.gz") tiff_path = os.path.join(str(tmpdir), "test_array.tiff") brainio.to_nii(start_array, nii_path) brainio.nii_to_tiff(nii_path, tiff_path) test_array = brainio.load_img_stack(tiff_path) assert (test_array == start_array).all()
def save_brain(image, source_image_path, output_path): registration_config = source_custom_config_amap() atlas_scale, transformation_matrix = get_transform_space_params( registration_config, source_image_path) brainio.to_nii( image.astype(np.int16), str(output_path), scale=atlas_scale, affine_transform=transformation_matrix, )
def save(self, dest_path): """ Save self.target_brain to dest_path as a nifty image. The scale (zooms of the output nifty image) is copied from the atlas brain. :param str dest_path: Where to save the image on the filesystem """ atlas_pix_sizes = self.atlas.pix_sizes transformation_matrix = ( self.atlas.make_atlas_scale_transformation_matrix()) brainio.to_nii( self.target_brain, dest_path, scale=( atlas_pix_sizes["x"] / 1000, atlas_pix_sizes["y"] / 1000, atlas_pix_sizes["z"] / 1000, ), affine_transform=transformation_matrix, )
def generate_region_volume(structure_names, atlas_path, output_path, atlas_config, glass=False): structure_csv_file = get_structures_path() reference_structures_table = pd.read_csv(structure_csv_file) # ensure all names are valid for indv_structure_name in structure_names: try: get_substructures(indv_structure_name, reference_structures_table) except IndexError: raise ValueError( f"Brain region: '{indv_structure_name}' cannot be found " f"in file: {structure_csv_file}. Please choose " f"another structure.") print(f"Loading atlas from: {atlas_path}") atlas = brainio.load_nii(atlas_path, as_array=False) atlas_scale = atlas.header.get_zooms() atlas = atlas.get_data() transformation_matrix = brain_tools.get_transformation_matrix(atlas_config) if len(structure_names) > 1: # Initialise an image to add each subimage to. final_image = np.zeros_like(atlas) for indv_structure_name in structure_names: print(f"Analysing brain region: {indv_structure_name}") substructures = get_substructures(indv_structure_name, reference_structures_table) print("This includes structures:") indv_substructure_names = substructures["name"].values for indv_substructure_name in indv_substructure_names: print(indv_substructure_name) list_vals = substructures["id"].values print("Generating image with specified regions \n") sub_image = np.isin(atlas, list_vals) if glass: print("Generating glass brain") sub_image = sk_segmentation.find_boundaries(sub_image) # If multiple structures, add them together if len(structure_names) > 1: final_image = np.logical_or(final_image, sub_image) else: final_image = sub_image print("Converting image to 16 bit") final_image = tools.scale_and_convert_to_16_bits(final_image) print("Saving image") brainio.to_nii( final_image, output_path, scale=atlas_scale, affine_transform=transformation_matrix, ) print(f"Saved image at: {output_path}")
def test_to_nii(tmpdir, start_array): # Also tests load_nii folder = str(tmpdir) nii_path = os.path.join(folder, "test_array.nii") brainio.to_nii(start_array, nii_path) assert (brainio.load_nii(nii_path).get_data() == start_array).all()
def transform_rois( roi_file, source_image_filename, destination_image_filename, control_point_file, output_filename, temp_output_filename, log_file_path, error_file_path, roi_reference_image=None, selem_size=15, nii_scale=None, transformation_matrix=None, debug=False, print_value_round_decimals=2, z_filter_padding=2, ): """ Using a source image (e.g. downsampled stack), transform an ImageJ zipped collection of ROIs into the coordinate space of a destination image (e.g. an atlas), using the inverse control point file from an existing niftyreg registration :param roi_file: .zip collection of ImageJ ROIs :param source_image_filename: Image that the ROIs are defined in :param destination_image_filename: Image in the destination coordinate space :param control_point_file: Transformation from source to destination :param output_filename: output filename for the resulting nifti file :param temp_output_filename: Temporary file for registration :param log_file_path: Path to save niftyreg logs :param error_file_path: Path to save niftyreg errors :param roi_reference_image: Image on which the ROIs are defined (if not the downsampled image in the registration directory) :param selem_size: Structure element size for closing :param nii_scale: Scaling to correctly save the temporary nifti image :param transformation_matrix: Affine transform for the temporary nifti image :param print_value_round_decimals: How many decimal places to round values printed to console. :param z_filter_padding: Size of the filter in z when correcting for unlabled slices. :param debug: If True, don't delete temporary files """ print("Loading ROIs") rois = read_roi_zip(roi_file) number_rois = len(rois) print(f"{number_rois} rois found") x = [] y = [] z = [] for key in rois: for position in range(0, len(rois[key]["x"])): x.append(rois[key]["x"][position]) y.append(rois[key]["y"][position]) z.append(rois[key]["position"]) print("Loading downsampled image image") downsampled_source_image = brainio.load_any(str(source_image_filename)) print(f"Source image size: " f"x:{downsampled_source_image.shape[0]}, " f"y:{downsampled_source_image.shape[1]}, " f"y:{downsampled_source_image.shape[2]}") downsampled_source_image[:] = 0 if roi_reference_image is not None: print("Reference image flag used. Loading reference image") reference_image_shape = brainio.get_size_image_from_file_paths( roi_reference_image) print(f"Reference image shape is " f"x:{reference_image_shape['x']}, " f"y:{reference_image_shape['y']}, " f"z:{reference_image_shape['z']}") x_downsample_factor = (reference_image_shape["x"] / downsampled_source_image.shape[0]) y_downsample_factor = (reference_image_shape["y"] / downsampled_source_image.shape[1]) z_downsample_factor = (reference_image_shape["z"] / downsampled_source_image.shape[2]) print(f"ROIs will be downsampled by a factor of " f"x:{round(x_downsample_factor, print_value_round_decimals)}, " f"y:{round(y_downsample_factor, print_value_round_decimals)}, " f"z:{round(z_downsample_factor, print_value_round_decimals)}") # TODO: optimise this print("Creating temporary ROI image") for position in range(0, len(x)): if roi_reference_image is None: downsampled_source_image[x[position], y[position], z[position]] = 1 else: x_scale = int(round(x[position] / x_downsample_factor)) y_scale = int(round(y[position] / y_downsample_factor)) z_scale = int(round(z[position] / z_downsample_factor)) downsampled_source_image[x_scale, y_scale, z_scale] = 1 print("Cleaning up ROI image") # TODO speed this up - parallelise? selem = morphology.selem.square(selem_size) for plane in tqdm(range(0, downsampled_source_image.shape[2])): tmp = morphology.binary.binary_closing(downsampled_source_image[:, :, plane], selem=selem) tmp = morphology.convex_hull_object(tmp) downsampled_source_image[:, :, plane] = morphology.binary.binary_closing( tmp, selem=selem) if roi_reference_image is not None: if z_downsample_factor < 1: print("ROI was defined at a lower z-resolution than the atlas. " "Correcting with a maximum filter") z_filter_size = int(round(1 / z_scale)) + z_filter_padding downsampled_source_image = maximum_filter1d( downsampled_source_image, z_filter_size, axis=2) print(f"Saving temporary ROI image at: {temp_output_filename}") brainio.to_nii( downsampled_source_image, str(temp_output_filename), scale=nii_scale, affine_transform=transformation_matrix, ) print("Preparing ROI registration") nifty_reg_binaries_folder = get_niftyreg_binaries() program_path = get_binary(nifty_reg_binaries_folder, PROGRAM_NAME) reg_cmd = prepare_segmentation_cmd( program_path, temp_output_filename, output_filename, destination_image_filename, control_point_file, ) print("Running ROI registration") try: safe_execute_command(reg_cmd, log_file_path, error_file_path) except SafeExecuteCommandError as err: raise RegistrationError("ROI registration failed; {}".format(err)) print(f"Registered ROI image can be found at {output_filename}") if not debug: print("Deleting temporary files") remove(temp_output_filename) remove(log_file_path) remove(error_file_path)
def heatmap( args, target_size, raw_image_shape, raw_image_bin_sizes, smoothing=10, mask=True, atlas=None, cells_only=True, convert_16bit=True, atlas_scale=None, transformation_matrix=None, ): """ :param args: :param target_size: Size of the final heatmap :param raw_image_shape: Size of the raw data (coordinate space of the cells) :param raw_image_bin_sizes: List/tuple of the sizes of the bins in the raw data space :param smoothing: Smoothing kernel size, in the target image space :param atlas: :param mask: :param cells_only: :param convert_16bit: :param atlas_scale: Image scaling so that the resulting nifti can be processed using other tools. :param transformation_matrix: Transformation matrix so that the resulting nifti can be processed using other tools. """ # TODO: compare the smoothing effects of gaussian filtering, and upsampling target_size = tools.convert_shape_dict_to_array_shape(target_size, type="fiji") raw_image_shape = tools.convert_shape_dict_to_array_shape(raw_image_shape, type="fiji") cells_array = fig_tools.get_cell_location_array( args.paths.classification_out_file, cells_only=cells_only) bins = fig_tools.get_bins(raw_image_shape, raw_image_bin_sizes) logging.debug("Generating heatmap (3D histogram)") heatmap_array, _ = np.histogramdd(cells_array, bins=bins) logging.debug("Resizing heatmap to the size of the target image") heatmap_array = resize(heatmap_array, target_size, order=0) if smoothing is not None: logging.debug("Applying Gaussian smoothing with a kernel sigma of: " "{}".format(smoothing)) heatmap_array = gaussian(heatmap_array, sigma=smoothing) if mask: logging.debug("Masking image based on registered atlas") # copy, otherwise it's modified, which affects later figure generation atlas_for_mask = np.copy(atlas) heatmap_array = img_tools.mask_image_threshold(heatmap_array, atlas_for_mask) if convert_16bit: logging.debug("Converting to 16 bit") heatmap_array = tools.scale_and_convert_to_16_bits(heatmap_array) logging.debug("Saving heatmap image") brainio.to_nii( heatmap_array, args.paths.heatmap, scale=atlas_scale, affine_transform=transformation_matrix, )
def main( registration_config, target_brain_path, registration_output_folder, x_pixel_um=0.02, y_pixel_um=0.02, z_pixel_um=0.05, orientation="coronal", flip_x=False, flip_y=False, flip_z=False, rotation="x0y0z0", affine_n_steps=6, affine_use_n_steps=5, freeform_n_steps=6, freeform_use_n_steps=4, bending_energy_weight=0.95, grid_spacing=-10, smoothing_sigma_reference=-1.0, smoothing_sigma_floating=-1.0, histogram_n_bins_floating=128, histogram_n_bins_reference=128, n_free_cpus=2, sort_input_file=False, save_downsampled=True, additional_images_downsample=None, boundaries=True, debug=False, ): """ The main function that will perform the library calls and register the atlas to the brain given on the CLI :param registration_config: :param target_brain_path: :param registration_output_folder: :param filtered_brain_path: :param x_pixel_um: :param y_pixel_um: :param z_pixel_um: :param orientation: :param flip_x: :param flip_y: :param flip_z: :param n_free_cpus: :param sort_input_file: :param save_downsampled: :param additional_images_downsample: dict of {image_name: image_to_be_downsampled} :return: """ n_processes = get_num_processes(min_free_cpu_cores=n_free_cpus) load_parallel = n_processes > 1 paths = Paths(registration_output_folder) atlas = RegistrationAtlas(registration_config, dest_folder=Path(registration_output_folder)) run = Run(paths, atlas, boundaries=boundaries, debug=debug) if run.preprocess: logging.info("Preprocessing data for registration") logging.info("Loading data") brain = BrainProcessor( atlas.pix_sizes, target_brain_path, registration_output_folder, x_pixel_um, y_pixel_um, z_pixel_um, original_orientation=orientation, load_parallel=load_parallel, sort_input_file=sort_input_file, n_free_cpus=n_free_cpus, ) for element in ["atlas", "brain", "hemispheres"]: key = f"{element}_name" logging.debug(f"Transforming atlas file: {element}") nii_img = atlas.get_nii_from_element(key) data = np.asanyarray(nii_img.dataobj) logging.debug("Reorienting to sample orientation") data = np.transpose(data, transpositions[brain.original_orientation]) data = np.swapaxes(data, 0, 1) logging.debug("Reorientating to nifti orientation") data = flip_multiple(data, flips[orientation]) logging.debug("Flipping to nifti orientation") data = flip_multiple(data, [flip_x, flip_y, flip_z]) logging.debug("Rotating to sample orientation") data = rotate_multiple(data, rotation) new_img = nb.Nifti1Image(data, nii_img.affine, nii_img.header) brainio.to_nii(new_img, atlas.get_dest_path(key)) if save_downsampled: brain.target_brain = brain.target_brain.astype(np.uint16, copy=False) logging.info("Saving downsampled image") brain.save(paths.downsampled_brain_path) brain.filter() logging.info("Saving filtered image") brain.save(paths.tmp__downsampled_filtered) del brain if additional_images_downsample: for name, image in additional_images_downsample.items(): if not check_downsampled(registration_output_folder, name): save_downsampled_image( image, name, registration_output_folder, atlas, x_pixel_um=x_pixel_um, y_pixel_um=y_pixel_um, z_pixel_um=z_pixel_um, orientation=orientation, n_free_cpus=n_free_cpus, sort_input_file=sort_input_file, load_parallel=load_parallel, ) else: logging.info(f"Image: {name} already downsampled, skipping.") if run.register: logging.info("Registering") if any([ run.affine, run.freeform, run.segment, run.hemispheres, run.inverse_transform, ]): registration_params = RegistrationParams( registration_config, affine_n_steps=affine_n_steps, affine_use_n_steps=affine_use_n_steps, freeform_n_steps=freeform_n_steps, freeform_use_n_steps=freeform_use_n_steps, bending_energy_weight=bending_energy_weight, grid_spacing=grid_spacing, smoothing_sigma_reference=smoothing_sigma_reference, smoothing_sigma_floating=smoothing_sigma_floating, histogram_n_bins_floating=histogram_n_bins_floating, histogram_n_bins_reference=histogram_n_bins_reference, ) brain_reg = BrainRegistration( registration_config, paths, registration_params, n_processes=n_processes, ) if run.affine: logging.info("Starting affine registration") brain_reg.register_affine() if run.freeform: logging.info("Starting freeform registration") brain_reg.register_freeform() if run.segment: logging.info("Starting segmentation") brain_reg.segment() if run.hemispheres: logging.info("Segmenting hemispheres") brain_reg.register_hemispheres() if run.inverse_transform: logging.info("Generating inverse (sample to atlas) transforms") brain_reg.generate_inverse_transforms() if run.volumes: logging.info("Calculating volumes of each brain area") calculate_volumes( paths.registered_atlas_path, paths.hemispheres_atlas_path, atlas.get_element_path("structures_name"), registration_config, paths.volume_csv_path, left_hemisphere_value=int(atlas["left_hemisphere_value"]), right_hemisphere_value=int(atlas["right_hemisphere_value"]), ) if run.boundaries: logging.info("Generating boundary image") calc_boundaries( paths.registered_atlas_path, paths.boundaries_file_path, atlas_config=registration_config, ) if run.delete_temp: logging.info("Removing registration temp files") delete_temp(paths.registration_output_folder, paths) logging.info(f"amap completed. Results can be found here: " f"{registration_output_folder}")
def save_all(self): brainio.to_nii(self._data, self.get_dest_path("atlas_name")) brainio.to_nii(self._brain_data, self.get_dest_path("brain_name")) brainio.to_nii(self._hemispheres_data, self.get_dest_path("hemispheres_name"))
def run( cells_file, output_filename, target_size, raw_image_shape, raw_image_bin_sizes, transformation_matrix, atlas_scale, smoothing=10, mask=True, atlas=None, cells_only=True, convert_16bit=True, ): """ :param cells_file: Cellfinder output cells file. :param output_filename: File to save heatmap into :param target_size: Size of the final heatmap :param raw_image_shape: Size of the raw data (coordinate space of the cells) :param raw_image_bin_sizes: List/tuple of the sizes of the bins in the raw data space :param transformation_matrix: Transformation matrix so that the resulting nifti can be processed using other tools. :param atlas_scale: Image scaling so that the resulting nifti can be processed using other tools. :param smoothing: Smoothing kernel size, in the target image space :param mask: Whether or not to mask the heatmap based on an atlas file :param atlas: Atlas file to mask the heatmap :param cells_only: Only use "cells", not artefacts :param convert_16bit: Convert final image to 16 bit """ # TODO: compare the smoothing effects of gaussian filtering, and upsampling target_size = convert_shape_dict_to_array_shape(target_size, type="fiji") raw_image_shape = convert_shape_dict_to_array_shape(raw_image_shape, type="fiji") cells_array = get_cell_location_array(cells_file, cells_only=cells_only) bins = get_bins(raw_image_shape, raw_image_bin_sizes) logging.debug("Generating heatmap (3D histogram)") heatmap_array, _ = np.histogramdd(cells_array, bins=bins) # otherwise resized array is too big to fit into RAM heatmap_array = heatmap_array.astype(np.uint16) logging.debug("Resizing heatmap to the size of the target image") heatmap_array = resize_array(heatmap_array, target_size) if smoothing is not None: logging.debug("Applying Gaussian smoothing with a kernel sigma of: " "{}".format(smoothing)) heatmap_array = gaussian(heatmap_array, sigma=smoothing) if mask: logging.debug("Masking image based on registered atlas") # copy, otherwise it's modified, which affects later figure generation atlas_for_mask = np.copy(atlas) heatmap_array = mask_image_threshold(heatmap_array, atlas_for_mask) if convert_16bit: logging.debug("Converting to 16 bit") heatmap_array = scale_and_convert_to_16_bits(heatmap_array) logging.debug("Ensuring output directory exists") ensure_directory_exists(Path(output_filename).parent) logging.debug("Saving heatmap image") brainio.to_nii( heatmap_array, output_filename, scale=atlas_scale, affine_transform=transformation_matrix, )