--- a +++ b/brats_toolkit/util/own_itk.py @@ -0,0 +1,280 @@ +# -*- coding: UTF-8 -*- +"""Module containing functions enabling to read, make and +write ITK images. + +""" +__version__ = "0.2" +__author__ = "Esther Alberts" + +import os + +import numpy as np +import SimpleITK as itk + + +def reduce_arr_dtype(arr, verbose=False): + """Change arr.dtype to a more memory-efficient dtype, without changing + any element in arr.""" + + if np.all(arr - np.asarray(arr, "uint8") == 0): + if arr.dtype != "uint8": + if verbose: + print("Converting " + str(arr.dtype) + " to uint8 np.ndarray") + arr = np.asarray(arr, dtype="uint8") + elif np.all(arr - np.asarray(arr, "int8") == 0): + if arr.dtype != "int8": + if verbose: + print("Converting " + str(arr.dtype) + " to int8 np.ndarray") + arr = np.asarray(arr, dtype="int8") + elif np.all(arr - np.asarray(arr, "uint16") == 0): + if arr.dtype != "uint16": + if verbose: + print("Converting " + str(arr.dtype) + " to uint16 np.ndarray") + arr = np.asarray(arr, dtype="uint16") + elif np.all(arr - np.asarray(arr, "int16") == 0): + if arr.dtype != "int16": + if verbose: + print("Converting " + str(arr.dtype) + " to int16 np.ndarray") + arr = np.asarray(arr, dtype="int16") + + return arr + + +def make_itk_image(arr, proto_image=None, verbose=True): + """Create an itk image given an image array. + + Parameters + ---------- + arr : ndarray + Array to create an itk image with. + proto_image : itk image, optional + Proto itk image to provide Origin, Spacing and Direction. + + Returns + ------- + image : itk image + The itk image containing the input array `arr`. + + """ + + arr = reduce_arr_dtype(arr, verbose=verbose) + + image = itk.GetImageFromArray(arr) + if proto_image != None: + image.CopyInformation(proto_image) + + return image + + +def write_itk_image(image, path): + """Write an itk image to a path. + + Parameters + ---------- + image : itk image or np.ndarray + Image to be written. + path : str + Path where the image should be written to. + + """ + + if isinstance(image, np.ndarray): + image = make_itk_image(image) + + writer = itk.ImageFileWriter() + writer.SetFileName(path) + + if os.path.splitext(path)[1] == ".nii": + Warning("You are converting nii, " + "be careful with type conversions") + + writer.Execute(image) + + +def get_itk_image(path_or_image): + """Get an itk image given a path. + + Parameters + ---------- + path : str or itk.Image + Path pointing to an image file with extension among + *TIFF, JPEG, PNG, BMP, DICOM, GIPL, Bio-Rad, LSM, Nifti (.nii and .nii.gz), + Analyze, SDT/SPR (Stimulate), Nrrd or VTK images*. + + Returns + ------- + image : itk image + The itk image. + + """ + if isinstance(path_or_image, itk.Image): + return path_or_image + + if not os.path.exists(path_or_image): + err = path_or_image + " doesnt exist" + raise AttributeError(err) + + reader = itk.ImageFileReader() + reader.SetFileName(path_or_image) + + image = reader.Execute() + + return image + + +def get_itk_array(path_or_image): + """Get an image array given a path or itk image. + + Parameters + ---------- + path_or_image : str or itk image + Path pointing to an image file with extension among + *TIFF, JPEG, PNG, BMP, DICOM, GIPL, Bio-Rad, LSM, Nifti (.nii and .nii.gz), + Analyze, SDT/SPR (Stimulate), Nrrd or VTK images* or an itk image. + + Returns + ------- + arr : ndarray + Image ndarray contained in the given path or the itk image. + + """ + + if isinstance(path_or_image, np.ndarray): + return path_or_image + + elif isinstance(path_or_image, str): + image = get_itk_image(path_or_image) + + elif isinstance(path_or_image, itk.Image): + image = path_or_image + + else: + err = "Image type not recognized: " + str(type(path_or_image)) + raise RuntimeError(err) + + arr = itk.GetArrayFromImage(image) + + return arr + + +def copy_image_info(input_path, ref_path): + """Copy origin, direction and spacing information from ref_path + into the header in input_path.""" + + print("OVerwriting " + input_path[-50:]) + + ref_im = get_itk_image(ref_path) + im = get_itk_image(input_path) + + dim = im.GetSize() + if dim != ref_im.GetSize(): + err = "Images are not of same dimension, I will not copy image info!" + raise RuntimeError(err) + + im.SetOrigin(ref_im.GetOrigin()) + im.SetDirection(ref_im.GetDirection()) + im.SetSpacing(ref_im.GetSpacing()) + + if im.GetSize() != dim: + err = "Dimension changed during copying image info: aborting" + raise RuntimeError(err) + + write_itk_image(im, input_path) + + +def load_arr_from_paths(paths): + """For every str in paths (paths can consis of nested lists), + load the image at this path. If any str is not a path, an error + is thrown. All other objects are preserved.""" + + if isinstance(paths, str): + im_arrs = get_itk_array(paths) + elif isinstance(paths, (list, tuple)): + for i, sub_paths in enumerate(paths): + paths[i] = load_arr_from_paths(sub_paths) + im_arrs = paths + else: + im_arrs = paths + + return im_arrs + + +def get_itk_data(path_or_image, verbose=False): + """Get the image array, image size and pixel dimensions given an itk + image or a path. + + Parameters + ---------- + path_or_image : str or itk image + Path pointing to an image file with extension among + *TIFF, JPEG, PNG, BMP, DICOM, GIPL, Bio-Rad, LSM, Nifti (.nii and .nii.gz), + Analyze, SDT/SPR (Stimulate), Nrrd or VTK images* or an itk image. + verbose : boolean, optional + If true, print image shape, spacing and data type of the image + corresponding to `path_or_image.` + + Returns + ------- + arr : ndarray + Image array contained in the given path or the itk image. + shape : tuple + Shape of the image array contained in the given path or the itk + image. + spacing : tuple + Pixel spacing (resolution) of the image array contained in the + given path or the itk image. + + """ + + if isinstance(path_or_image, np.ndarray): + arr = path_or_image + spacing = None + else: + if isinstance(path_or_image, str): + image = get_itk_image(path_or_image) + else: + image = path_or_image + arr = itk.GetArrayFromImage(image) + spacing = image.GetSpacing()[::-1] + + shape = arr.shape + data_type = arr.dtype + + if verbose: + print("\t image shape: " + str(shape)) + print("\t image spacing: " + str(spacing)) + print("\t image data type: " + str(data_type)) + + return arr, shape, spacing + + +def read_dicom(source_path, verbose=True): + """Reads dicom series into an itk image. + + Parameters + ---------- + source_path : string + path to directory containing dicom series. + verbose : boolean + print out all series file names. + + Returns + ------- + image : itk image + image volume. + """ + + reader = itk.ImageSeriesReader() + names = reader.GetGDCMSeriesFileNames(source_path) + if len(names) < 1: + raise IOError("No Series can be found at the specified path!") + elif verbose: + print( + "image series with %d dicom files found in : %s" + % (len(names), source_path[-50:]) + ) + reader.SetFileNames(names) + image = reader.Execute() + if verbose: + get_itk_data(image, verbose=True) + + return image