Source code for neutompy.image.image

import numpy as np
import tifffile
import astropy
from astropy.io import fits
from read_roi import read_roi_file
import logging
import os
import types
import inspect
from neutompy.misc.uitools import get_filename_gui, get_image_gui, get_folder_gui
from tqdm import tqdm

__author__  = "Davide Micieli"
__all__     = ['read_tiff',
			 'read_fits',
			 'read_fits_stack',
			 'read_tiff_stack',
			 'get_rect_coordinates_from_roi',
			 'read_image',
			 'read_stack_from_list',
			 'read_dataset',
			 'read_image_stack',
			 'write_fits',
			 'write_tiff',
			 'write_tiff_stack',
			 'write_fits_stack',
			 'get_rect_coordinates_from_roi', 'get_filename_pattern']

#logging.basicConfig(level=logging.WARNING)
logs = logging.getLogger(__name__)


known_ext = ['.tif', '.tiff', '.fits']

# to ensure compatibility with astropy 1.X, clobber option is deprecated and replaced with overwrite
if 'clobber' in inspect.getfullargspec(astropy.io.fits.writeto).args:
	arg_overwrite = 'clobber'
else:
	arg_overwrite = 'overwrite'


def get_file_extension(fname):
	"""
	This function returns the extension of a given file name or file path
	"""
	return os.path.splitext(fname)[1]


[docs]def get_rect_coordinates_from_roi (fname): """ This function returns the coordinates from a rectangular region of interest defined in a .roi file generated by ImageJ / Fiji. N.B.: indexing starts from 0. Incremet rowmax and col_max + 1 to crop an image, for example: crop = img[rowmin:(rowmax+1), colmin:(colmax+1)] Parameters ---------- fname : str String defining the path or the name of the Image ROI file. Returns ------- rowmin : int The minimum row coordinate. rowmax : int The maximum row coordinate. colmin : int The minimum column coordinate. colmax : int The maximum column coordinate. """ # file name check if(type(fname) is not str): logs.error('File name must be a string.') raise TypeError('File name must be a string.') else: if not(fname.endswith('.roi')): logs.warning('File extension shold be .roi') # read roi file roi = read_roi_file(fname) # convert keys to list l = list(roi.keys()) # extract coordinates and length height = roi[l[0]]['height'] width = roi[l[0]]['width'] top = roi[l[0]]['top'] left = roi[l[0]]['left'] rowmin = int(top) rowmax = int(top + height - 1) colmin = int(left) colmax = int(left + width - 1) # log infos logs.info('ROI file succesfully read: %s', fname) return rowmin, rowmax, colmin, colmax
[docs]def read_tiff(fname, croi=None, froi=None): """ This function reads a 2D TIFF image. Parameters ---------- fname : str String defining the file name or the file path croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI file name or file path. Returns ------- out : ndarray The 2D image. If the file specified doesn't exist, the function returns False. """ # file name check if(type(fname) is not str): logs.error('File name must be a string.') raise TypeError('File name must be a string.') if ( not(fname.endswith('.tif')) and not(fname.endswith('.tiff')) ): logs.warning('File extension shold be .tif or .tiff') # ROI check if(croi and froi): raise ValueError('Please set the coordinates of the region of interest OR the ImageJ file .roi') if(croi): if not(len(croi)==2 and len(croi[0])==3 and len(croi[1])==3 ): raise ValueError('Tuple or list size not valid. Please set the coordinates of the ROI in the this manner: croi = ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) ') # get the absolute path of the file fname = os.path.abspath(fname) try: out = tifffile.imread(fname) #, out='memmap') logs.info('Array imported from file: %s', fname) logs.debug('Array shape and type: %s, %s', out.shape, out.dtype) except IOError: logs.error('No such file: %s', fname) return False if (len(out.shape) != 2): raise ValueError('Only 2D TIFF images are supported in read_tiff') if(croi): rstart, rend, rstep = croi[0] cstart, cend, cstep = croi[1] if (rend is not None): rend = rend + 1 if (cend is not None): cend = cend + 1 out = out[rstart:rend:rstep, cstart:cend:cstep] logs.info('Read roi coordinate from list / tuple') logs.debug('Cropped array shape and type: %s, %s', out.shape, out.dtype) logs.debug('rstart = %s, rend = %s, rstep = %s', rstart, rend, rstep) logs.debug('cstart = %s, cend = %s, cstep = %s', cstart, cend, cstep) if(froi): rstart, rend, cstart, cend = get_rect_coordinates_from_roi(froi) out = out[rstart:rend + 1, cstart:cend + 1] logs.info('Read ROI coordinate from file : %s' , froi) logs.debug('Cropped array shape and type: %s, %s', out.shape, out.dtype) logs.debug('rstart = %s, rend = %s, rstep = %s', rstart, rend) logs.debug('cstart = %s, cend = %s, cstep = %s', cstart, cend) return out
[docs]def read_fits(fname, croi=None, froi=None): """ This function reads a 2D FITS image. Parameters ---------- fname : str String defining the file name or the file path croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI filename or file path. Returns ------- out : ndarray The 2D image. If the file specified doesn't exist, the function returns False. """ # file name check if(type(fname) is not str): logs.error('File name must be a string.') raise TypeError('File name must be a string.') if ( not(fname.endswith('.fits')) ): logs.warning('File extension shold be .fits') # ROI check if(croi and froi): raise ValueError('Please set the coordinates of the region of interest OR the ImageJ file .roi') if(croi): if not(len(croi)==2 and len(croi[0])==3 and len(croi[1])==3 ): raise ValueError('Tuple or list size not valid. Please set the coordinates of the ROI in the this manner: croi = ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) ') # get the absolute path of the file fname = os.path.abspath(fname) try: f = fits.open(fname) out = f[0].data[::-1, :].copy() # flip up-down f.close() logs.info('Array imported from file: %s', fname) logs.debug('Array shape and type: %s, %s', out.shape, out.dtype) except IOError: logs.error('No such file: %s', fname) return False if (len(out.shape) != 2): raise ValueError('Only 2D FITS images are supported in read_tiff') if(croi): rstart, rend, rstep = croi[0] cstart, cend, cstep = croi[1] if (rend is not None): rend = rend + 1 if (cend is not None): cend = cend + 1 out = out[rstart:rend:rstep, cstart:cend:cstep] logs.info('Read roi coordinate from list / tuple') logs.debug('Cropped array shape and type: %s, %s', out.shape, out.dtype) logs.debug('rstart = %s, rend = %s, rstep = %s', rstart, rend, rstep) logs.debug('cstart = %s, cend = %s, cstep = %s', cstart, cend, cstep) if(froi): rstart, rend, cstart, cend = get_rect_coordinates_from_roi(froi) out = out[rstart:rend + 1, cstart:cend + 1] logs.info('Read ROI coordinate from file : %s' , froi) logs.debug('Cropped array shape and type: %s, %s', out.shape, out.dtype) logs.debug('rstart = %s, rend = %s, rstep = %s', rstart, rend) logs.debug('cstart = %s, cend = %s, cstep = %s', cstart, cend) return out
[docs]def read_image(fname, croi=None, froi=None): """ This function reads a 2D TIF or FITS image. Parameters ---------- fname : str String defining the file name or the file path croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI filename or file path. Returns ------- out : ndarray The 2D image. If the file specified doesn't exist, the function returns False. """ # check if file exists if not(os.path.isfile(fname)): raise ValueError('No such file %s' %fname) # read .fits or .tif from the file extension if(fname.endswith('.fits') or fname.endswith('.FITS')): funz = read_fits elif(fname.endswith('.tif') or fname.endswith('.tiff') or fname.endswith('.TIF') or fname.endswith('.TIFF')): funz = read_tiff else: raise ValueError('File extension not valid. File : %s' % fname) array = funz(fname, croi, froi) return array
[docs]def get_filename_pattern(fname): # get file name extension ext = get_file_extension(fname) # get prefix_XXXX aus = fname[:-len(ext)] # find how many X, remove XXXX and find the prefix name p = 0 while(aus[-1-p].isdigit()): p = p + 1 prefix_full = aus[:-p] folder = os.path.dirname(fname) prefix = os.path.basename(prefix_full) numbering = '#'*p return prefix_full, numbering, ext
[docs]def read_stack_from_list(flist, slices=[], croi=None, froi=None): """ Read stack of images from file list. Parameters ---------- flist : list of str List of the file names or file paths to read in the defined order. File with unknown image extension are skipped automatically. slices : list of int, optional List of the indexes of flist to read. If it is [] then all image files are read. croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI filename or file path. Returns ------- array : ndarray The 3D stack of images """ # if slices == [], initialize all indexes if not slices: slices = [ i for i in range(0, len(flist)) ] # list of index to read toread = [True]*len(slices) # skip files with unknown extension for e, s in enumerate(slices): if not (get_file_extension(flist[s]) in known_ext): toread[e] = False logs.warning('Unknown file extension. Skipped file: %s', flist[s]) fpat = get_filename_pattern(flist[0]) # e.g. /folder1/folder2/img_####.tiff print('> Reading stack of images:', ''.join(fpat)) array = np.array([read_image(flist[s], croi, froi) for e, s in enumerate(tqdm(slices, unit=' images')) if toread[e]]) print('') return array
[docs]def read_tiff_stack(fname, slices=[], croi=None, froi=None): """ Read stack of tiff images. Parameters ---------- fname : str One of the file names of the tiff stack. File with unknown image extension are skipped automatically. If fname is '' or a folder path, then a dialog box is opened to select one of the file of the stack to read. The initial directory is C:\ (in windows) or / (in UNIX) if fname=='', otherwise is the folder path assigned to fname. slices : list of int, optional List of the element indexes to read. If it is [] then all tif image files are read. croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI filename or file path. Returns ------- array : ndarray The 3D stack of images """ if(os.path.isdir(fname) or fname==''): fname = get_filename_gui(initialdir=fname, message='Select a file of the image stack...', ext=('TIFF Images', '*.tif *.tiff') ) # check if file exists if not(os.path.isfile(fname)): raise ValueError('No such file %s' %fname) # get file name extension ext = get_file_extension(fname) # get prefix_XXXX aus = fname[:-len(ext)] # find how many X, remove XXXX and find the prefix name p = 0 while(aus[-1-p].isdigit()): p = p + 1 prefix_full = aus[:-p] folder = os.path.dirname(fname) prefix = os.path.basename(prefix_full) flist = [os.path.normpath(os.path.join(folder, f)) for f in sorted(os.listdir(folder)) if (f.startswith(prefix) and f[-p-len(ext):-len(ext)].isdigit() and (get_file_extension(f) in ['.tif', '.tiff']) ) ] out = read_stack_from_list(flist, slices, croi, froi) return out
[docs]def read_fits_stack(fname, slices=[], croi=None, froi=None): """ Read stack of fits images. Parameters ---------- fname : str One of the file names of the fits stack. File with unknown image extension are skipped automatically. If fname is '' or a folder path, then a dialog box is opened to select one of the file of the stack to read. The initial directory is C:\ (in windows) or / (in UNIX) if fname=='', otherwise is the folder path assigned to fname. slices : list of int, optional List of the element indexes to read. If it is [] then all fits image files are read. croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI filename or file path. Returns ------- array : ndarray The 3D stack of images """ if(os.path.isdir(fname) or fname==''): fname = get_filename_gui(initialdir=fname, message='Select a file of the image stack...', ext=('FITS Images', '*.fits') ) # check if file exists if not(os.path.isfile(fname)): raise ValueError('No such file %s' %fname) # get file name extension ext = get_file_extension(fname) # get prefix_XXXX aus = fname[:-len(ext)] # find how many X, remove XXXX and find the prefix name p = 0 while(aus[-1-p].isdigit()): p = p + 1 prefix_full = aus[:-p] folder = os.path.dirname(fname) prefix = os.path.basename(prefix_full) flist = [os.path.normpath(os.path.join(folder, f)) for f in sorted(os.listdir(folder)) if (f.startswith(prefix) and f[-p-len(ext):-len(ext)].isdigit() and (get_file_extension(f) in ['.fits']) ) ] out = read_stack_from_list(flist, slices, croi, froi) return out
[docs]def read_image_stack(fname, slices=[], croi=None, froi=None): """ Read stack of TIFF or FITS images. This function recognize the file type from the file extension. Parameters ---------- fname : str One of the file names of the tiff stack. File with unknown image extension are skipped automatically. slices : list of int, optional List of the element indexes to read. If it is [] then all tif image files are read. croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI filename or file path. Returns ------- array : ndarray The 3D stack of images """ if(fname.endswith('.fits') or fname.endswith('.FITS')): funz = read_fits_stack elif(fname.endswith('.tif') or fname.endswith('.tiff') or fname.endswith('.TIFF') or fname.endswith('.TIF')): funz = read_tiff_stack else: raise ValueError('File extension not valid. File : %s' % fname) arr = funz(fname, slices, croi, froi) return arr
[docs]def read_dataset(proj_180=True, croi=None, froi=None): """ This function reads a dataset which contains dark-field, flat-field, projection images and optionally the projection at 180 degree. The user selects the main folder and the files from a dialog box. A two-dimensional region of interest of each image can be read specifying the coordinates or an ImageJ .roi file. Parameters ---------- proj_180 : bool, optional If True the user must select the projection at 180 degree separately from the stack of projections. croi : tuple, optional Tuple defining the ROI indexes for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) froi : str, optional String defining the ImageJ ROI file name or file path. Returns ------- proj : 3d array The array containing the stack of projections. dark : 3d array The array containing the stack of dark-field images. flat : 3d array The array containing the stack of flat-field images. proj180 : 2d array Only if proj_180 is True, the 2D array representing the projection at 180 degree is returned separately. """ # select the folder that contains dark, flat, and projection images using the GUI data_folder = get_folder_gui('', message = 'Select the main folder of the dataset...') proj_dir = get_image_gui(data_folder, message = 'Select a raw projection...') dark_dir = get_image_gui(data_folder, message = 'Select a dark image...') flat_dir = get_image_gui(data_folder, message = 'Select a flat image...') if proj_180: proj_pi_fname = get_image_gui(data_folder, message = 'Select raw projection at 180°...') # load the dataset proj = read_image_stack(proj_dir, croi=croi, froi=froi) dark = read_image_stack(dark_dir, croi=croi, froi=froi) flat = read_image_stack(flat_dir, croi=croi, froi=froi) if proj_180: print('> Reading projection at 180 degree:',proj_pi_fname) proj180 = read_image(proj_pi_fname, croi=croi, froi=froi) if proj_180: return proj, dark, flat, proj180 else: return proj, dark, flat
[docs]def write_tiff(fname, img, overwrite=False): """ This function write to the disk an array as tiff image. Parameters ---------- fname : str String defining the file name or file path of the image to save. If the extension is not specified, it is automatically appended to fname. img : ndarray The array to save as image. overwrite: bool, optional If ``True``, overwrites the output file if it exists. Raises an ``IOError`` if ``False`` and the output file exists. Default is ``False``. """ # add extension .tif if not specified in fname if not ( fname.endswith('.tif') or fname.endswith('.tiff') ): fname = fname + '.tiff' if not overwrite: if(os.path.isfile(fname)): raise IOError("File already exists : %s" %fname) if overwrite: if(os.path.isfile(fname)): logs.info("File to overwrite: %s", fname) tifffile.imsave(fname, img) logs.info('File saved to disk: %s', fname) logs.debug('Image shape %s and type %s', img.shape, img.dtype)
[docs]def write_fits(fname, img, overwrite=False): """ This function write to the disk an array as fits image. Parameters ---------- fname : str String defining the file name or file path of the image to save. If the extension is not specified, it is automatically appended to fname. img : ndarray The array to save as image. overwrite: bool, optional If ``True``, overwrites the output file if it exists. Raises an ``IOError`` if ``False`` and the output file exists. Default is ``False``. """ # add extension .tif if not specified in fname if not ( fname.endswith('.fits') ): fname = fname + '.fits' if overwrite: if(os.path.isfile(fname)): logs.info("File to overwrite: %s", fname) hdu = fits.PrimaryHDU() hdu.data = img[::-1, :] hdu.writeto(fname, **{arg_overwrite : overwrite}) logs.info('File saved to disk: %s', fname) logs.debug('Image shape %s and type %s', img.shape, img.dtype)
[docs]def write_tiff_stack(fname, data, axis=0, start=0, croi=None, digit=4, dtype=None, overwrite=False): """ This function writes a 3D array to a stack of 2D TIFF images. Parameters ---------- fname: str String defining the prefix of the file name and the containing folder. data : ndarray The 3D stack to write. axis : int, optional The axis along which the stacking is performed. start : int, optional Index used for saving the first image. croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) digit : int, optional Number of digits used for the numbering of the images. dtype : dtype, optional Data type of the images to save. overwrite: bool, optional If ``True``, overwrites the output file if it exists. Raises an ``IOError`` if ``False`` and the output file exists. Default is ``False``. """ ## check dimension data ==3 if (data.ndim != 3): raise ValueError('Array must have 3 dimensions.') folder = os.path.dirname(fname) if folder == '': raise ValueError('File path not valid. Please specify the file path giving at\ least one folder, e.g.: folder/file.tiff ') else: if not (os.path.isdir(folder)): raise ValueError('Folder not exist. %s' %folder) if not dtype: dtype = data.dtype if(axis!=0): data = np.swapaxes(data, 0, axis) if(croi): newcroi_list = [[None]*3]*3 newcroi_list[axis] = croi[0] newcroi_list[0] = croi[axis] newcroi_list[3-axis] = croi[3-axis] newcroi = tuple(newcroi_list) if not croi: slices, rows, cols = data.shape newcroi = ((0, slices, 1), (0, rows, 1), (0, cols, 1)) else: if(axis ==0): newcroi = croi rmin, rmax, rstep = newcroi[1] cmin, cmax, cstep = newcroi[2] smin, smax, sstep = newcroi[0] auslist = list(range(0, data.shape[0], 1)) subset = auslist[smin:smax:sstep] logs.debug('Shape of the re-stacked array %s', newcroi) print('> Saving stack of images:', fname + '_' + '#'*digit + '.tiff') for iz, s in enumerate(tqdm(subset, unit=' images'), start): outfile = fname + '_' + str(iz).zfill(digit) + '.tiff' write_tiff(outfile, data[s, rmin:rmax:rstep, cmin:cmax:cstep].astype(dtype), overwrite)
[docs]def write_fits_stack(fname, data, axis=0, start=0, croi=None, digit=4, dtype=None, overwrite=False): """ This function writes a 3D array to a stack of 2D FITS images. Parameters ---------- fname: str String defining the prefix of the file name and the containing folder. data : ndarray The 3D stack to write. axis : int, optional The axis along which the stacking is performed. start : int, optional Index used for saving the first image. croi : tuple, optional Tuple defining the indexes range for each axis. It must be follow this notation: ( (row_start, row_end, row_step), (col_start, col_end, col_step) ) digit : int, optional Number of digits used for the numbering of the images. dtype : dtype, optional Data type of the images to save. overwrite: bool, optional If ``True``, overwrites the output file if it exists. Raises an ``IOError`` if ``False`` and the output file exists. Default is ``False``. """ ## check dimension data ==3 if (data.ndim != 3): raise ValueError('Array must have 3 dimensions.') folder = os.path.dirname(fname) if folder == '': raise ValueError('File path not valid. Please specify the file path giving\ at least one folder, e.g.: folder/file.fits ') else: if not (os.path.isdir(folder)): raise ValueError('Folder not exist. %s' %folder) if not dtype: dtype = data.dtype if(axis!=0): data = np.swapaxes(data, 0, axis) if(croi): newcroi_list = [[None]*3]*3 newcroi_list[axis] = croi[0] newcroi_list[0] = croi[axis] newcroi_list[3-axis] = croi[3-axis] newcroi = tuple(newcroi_list) if not croi: slices, rows, cols = data.shape newcroi = ((0, slices, 1), (0, rows, 1), (0, cols, 1)) else: if(axis ==0): newcroi = croi rmin, rmax, rstep = newcroi[1] cmin, cmax, cstep = newcroi[2] smin, smax, sstep = newcroi[0] auslist = list(range(0, data.shape[0], 1)) subset = auslist[smin:smax:sstep] logs.debug('Shape of the re-stacked array %s', newcroi) print('> Saving stack of images:', fname + '_' + '#'*digit + '.fits') for iz, s in enumerate(tqdm(subset, unit=' images'), start): outfile = fname + '_' + str(iz).zfill(digit) + '.fits' write_fits(outfile, data[s, rmin:rmax:rstep, cmin:cmax:cstep].astype(dtype), overwrite)