Source code for fiberlab.io

"""
General input/output methods.

.. include:: ../include/links.rst
""" 

from pathlib import Path
import warnings
from IPython import embed
import numpy
from skimage import io
from astropy.io import fits


[docs] def bench_image(ifile, ext=0, compress=True): """ Read an image from a bench imaging camera. Args: ifile (:obj:`str`, `Path`_): File name. Can be None, but, if so, None is returned. ext (:obj:`int`, optional): If the file is a multi-extension fits file, this selects the extension with the relevant data. compress (:obj:`bool`, optional): If the data include multiple color channels (as indicated by the image data array being 3D), compress into a single 2D image by summing across the last dimension? Returns: `numpy.ndarray`_: Array with the (floating-point) image data. If ``ifile`` is None, this is also None. """ # No file so return None. if ifile is None: return None # Set the file path and check that it exists. _ifile = Path(ifile).resolve() if not _ifile.exists(): raise FileNotFoundError(f'{_ifile} does not exist!') # Read fits files using astropy if any([s in ['.fit', '.fits'] for s in _ifile.suffixes]): return fits.open(_ifile)[ext].data.astype(float) # Read all other files using scikit-image img = io.imread(_ifile).astype(float) if compress and img.ndim == 3: img = numpy.sum(img, axis=2) return img
[docs] def gather_collimated_file_list(root, par=None, threshold=None): """ Gather the list of collimated farfield output files to analyze. Args: root (:obj:`str`, `Path`_): Root directory with files. par (:obj:`str`, `Path`_, optional): Name of a file that provides 2 or 3 columns: (1) the files to analyze, (2) the background image to use for each file, and (3) the threshold to use for each file. If this file is provided, any threshold is ignored and the root directory is not trolled for all bg*, z*, and a* files. The last column with the thresholds can be omitted, which means the code will use the value provided on the command line (or its default). threshold (:obj:`float`, optional): If ``par`` is not provided or it has no 3nd column, this is the threshold to use for all files. Returns: :obj:`tuple`: Provides (1) the z0 file, (2) its background file, (3) its threshold, (4) the z1 file, (5) its background file, (6) its threshold, (7) the list of a* files, (8) their background files, and (9) their thresholds. """ if par is None: z0, z1, bg, afiles = find_collimated_farfield_files(root) na = len(afiles) return z0, bg, threshold, z1, bg, threshold, afiles, [bg]*na, [threshold]*na return parse_file_list(root, par, threshold)
[docs] def gather_fullcone_file_list(root, par=None, threshold=None): """ Gather the list of full-cone farfield output files to analyze. Args: root (:obj:`str`, `Path`_): Root directory with files. par (:obj:`str`, `Path`_, optional): Name of a file that provides 2 or 3 columns: (1) the files to analyze, (2) the background image to use for each file, and (3) the threshold to use for each file. If this file is provided, any threshold is ignored and the root directory is not trolled for all bg*, z*, and a* files. The last column with the thresholds can be omitted, which means the code will use the value provided on the command line (or its default). threshold (:obj:`float`, optional): If ``par`` is not provided or it has no 3nd column, this is the threshold to use for all files. Returns: :obj:`tuple`: Provides (1) the z0 file, (2) its background file, (3) its threshold, (4) the z1 file, (5) its background file, (6) its threshold, (7) the list of a* files, (8) their background files, and (9) their thresholds. """ if par is None: z0, z1, bg = find_fullcone_farfield_files(root) return z0, bg, threshold, z1, bg, threshold return parse_file_list(root, par, threshold)[:6]
[docs] def find_collimated_farfield_files(root): """ Find the set of collimated far-field output files according to the expected nomenclature. Args: root (:obj:`str`, `Path`_): Root directory with files. Returns: :obj:`tuple`: Full paths to (1) the z0 image, (2) the z1 image, (3) the background image, and (4) the list of a* files. """ # Set the root directory _root = Path(root).resolve() if not _root.exists(): raise FileNotFoundError(f'{_root} is not a valid directory.') # Find the z0 and z1 images z0 = sorted(list(_root.glob('z0*'))) if len(z0) > 1: raise ValueError('More than one z0 image found. Directory should only have one of ' f'the following: {[f.name for f in z0]}') if len(z0) == 0: raise ValueError('Could not find z0 image.') z0 = z0[0] z1 = sorted(list(_root.glob('z1*'))) if len(z1) > 1: raise ValueError('More than one z1 image found. Directory should only have one of ' f'the following: {[f.name for f in z1]}') if len(z1) == 0: raise ValueError('Could not find z1 image.') z1 = z1[0] # Attempt to find a background image bg = sorted(list(_root.glob('bg*'))) if len(bg) >= 1: if len(bg) > 1: warnings.warn(f'Found more than one background image: {[f.name for f in bg]}. ' 'Using the first one.') bg = bg[0] else: warnings.warn('No background images found. Will attempt to use main image to set ' 'background level.') bg = None afiles = sorted(list(_root.glob('a*'))) return z0, z1, bg, afiles
[docs] def find_fullcone_farfield_files(root): """ Find a set of fullcone far-field output files according to the expected nomenclature. Args: root (:obj:`str`, `Path`_): Root directory with files. Returns: :obj:`tuple`: Full paths to (1) the z0 image, (2) the z1 image, and (3) the background image. """ # Set the root directory _root = Path(root).resolve() if not _root.exists(): raise FileNotFoundError(f'{_root} is not a valid directory.') # Find the z0 and z1 images z0 = sorted(list(_root.glob('z0*'))) if len(z0) > 1: raise ValueError('More than one z0 image found. Directory should only have one of ' f'the following: {[f.name for f in z0]}') if len(z0) == 0: raise ValueError('Could not find z0 image.') z0 = z0[0] z1 = sorted(list(_root.glob('z1*'))) if len(z1) > 1: raise ValueError('More than one z1 image found. Directory should only have one of ' f'the following: {[f.name for f in z1]}') if len(z1) == 0: raise ValueError('Could not find z1 image.') z1 = z1[0] # Attempt to find a background image bg = sorted(list(_root.glob('bg*'))) for i in range(len(bg)): if bg[i].is_dir(): bg[i] = sorted(list(bg[i].glob('bg*'))) bg = numpy.concatenate(bg).tolist() if len(bg) >= 1: if len(bg) > 1: warnings.warn(f'Found more than one background image: {[f.name for f in bg]}. ' 'Using the first one.') bg = bg[0] else: warnings.warn('No background images found. Will attempt to use main image to set ' 'background level.') bg = None return z0, z1, bg
[docs] def parse_file_list(root, par, threshold): """ Parse a file with the set of data files to analyze. Args: root (:obj:`str`, `Path`_): Root directory with files. par (:obj:`str`, `Path`_): Name of a file that provides 2 or 3 columns: (1) the files to analyze, (2) the background image to use for each file, and (3) the threshold to use for each file. The last column with the thresholds can be omitted, which means the code will use the value provided on the command line (or its default). threshold (:obj:`float`, optional): If ``par`` has no 3nd column, this is the threshold to use for all files. Returns: :obj:`tuple`: Provides (1) the z0 file, (2) its background file, (3) its threshold, (4) the z1 file, (5) its background file, (6) its threshold, (7) the list of a* files, (8) their background files, and (9) their thresholds. """ # Set the root directory _root = Path(root).resolve() if not _root.exists(): raise FileNotFoundError(f'{_root} is not a valid directory.') _par = Path(par).resolve() if not _par.exists(): raise FileNotFoundError(f'{_par} does not exist!') # Use numpy to read the file db = numpy.genfromtxt(str(_par), dtype=str) nfiles = db.shape[0] if db.shape[1] == 2: imgs, bkgs = db.T thresh = [threshold]*nfiles pseudo = [None] * nfiles elif db.shape[1] == 3: imgs, bkgs, thresh = db.T thresh = thresh.astype(float) pseudo = [None] * nfiles elif db.shape[1] == 4: imgs, bkgs, thresh, pseudo = db.T thresh = thresh.astype(float) else: raise ValueError(f'{_par} must only contain 2, 3, or 4 columns!') # Check the files exist in the root directory for i in range(nfiles): if not (_root / imgs[i]).exists(): raise FileNotFoundError(f'{imgs[i]} is not a file in {_root}.') if not (_root / bkgs[i]).exists(): raise FileNotFoundError(f'{bkgs[i]} is not a file in {_root}.') if nfiles == 2: return _root / imgs[0], _root / bkgs[0], thresh[0], \ _root / imgs[1], _root / bkgs[1], thresh[1], None, None, None # Find the z0 file indx = numpy.where([i[:2] == 'z0' or p == 'z0' for i,p in zip(imgs, pseudo)])[0] if len(indx) != 1: raise ValueError('There should one and only one file named/marked as "z0".') z0 = _root / imgs[indx[0]] z0_bg = _root / bkgs[indx[0]] z0_thresh = thresh[indx[0]] # Find the z1 file indx = numpy.where([i[:2] == 'z1' or p == 'z1' for i,p in zip(imgs, pseudo)])[0] if len(indx) != 1: raise ValueError('There should one and only one file named/marked as "z1".') z1 = _root / imgs[indx[0]] z1_bg = _root / bkgs[indx[0]] z1_thresh = thresh[indx[0]] # Find the angle-sweep files indx = numpy.where([i[0] == 'a' or (p is not None and p[0] == 'a') for i,p in zip(imgs, pseudo)])[0] if nfiles != indx.size + 2: warnings.warn(f'Could not parse {nfiles - 2 - indx.size} files in {_par}.') if indx.size == 0: warnings.warn(f'Could not find any angle-sweep files.') return z0, z0_bg, z0_thresh, z1, z1_bg, z1_thresh, \ _root / imgs[indx], _root / bkgs[indx], thresh[indx]