Example #1
0
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,
    )
Example #2
0
    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)
Example #3
0
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()
Example #4
0
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,
    )
Example #5
0
    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,
        )
Example #6
0
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}")
Example #7
0
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()
Example #8
0
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)
Example #9
0
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,
    )
Example #10
0
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}")
Example #11
0
 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"))
Example #12
0
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,
    )