diff --git a/example/cfis/config.mask b/example/cfis/config.mask index a1f6fbb4b..9a147bdcd 100644 --- a/example/cfis/config.mask +++ b/example/cfis/config.mask @@ -1,4 +1,4 @@ -# Mask module configuration file +# Mask module configuration file for single-exposure images ## Paths to executables [PROGRAM_PATH] @@ -51,12 +51,23 @@ SPIKE_REG_FILE = spike.reg MESSIER_MAKE = True -MESSIER_CAT_PATH = $SP_CONFIG/mask_default/Messier_catalog_updated.npy +MESSIER_CAT_PATH = $SP_CONFIG/mask_default/Messier_catalog_updated.fits MESSIER_SIZE_PLUS = 0. MESSIER_FLAG_VALUE = 16 -# -------------------------- Missing data parameters ------------------------------ +## NGC mask +[NGC_PARAMETERS] + +NGC_MAKE = True + +NGC_CAT_PATH = $SP_CONFIG/mask_default/ngc_cat.fits +NGC_SIZE_PLUS = 0. +NGC_FLAG_VALUE = 32 + + + +## Missing data parameters [MD_PARAMETERS] MD_MAKE = False @@ -66,11 +77,10 @@ MD_THRESH_REMOVE = 0.75 MD_REMOVE = False -# ----------------------------- Other parameters ---------------------------------- +## Other parameters [OTHER] TEMP_DIRECTORY = .temp KEEP_REG_FILE = False KEEP_INDIVIDUAL_MASK = False - diff --git a/example/cfis/config_GitFeGie_vos.ini b/example/cfis/config_GitFeGie_vos.ini index 40c98cd16..fd3207bb3 100644 --- a/example/cfis/config_GitFeGie_vos.ini +++ b/example/cfis/config_GitFeGie_vos.ini @@ -18,7 +18,7 @@ RUN_DATETIME = True [EXECUTION] # Module name, single string or comma-separated list of valid module runner names -MODULE = get_images_runner, find_exposures_runner, get_images_runner +MODULE = get_images_runner, find_exposures_runner, get_images_runner # Parallel processing mode, SMP or MPI MODE = SMP @@ -146,4 +146,3 @@ N_TRY = 3 # Retrieve command options, optional RETRIEVE_OPTIONS = --certfile=$VM_HOME/.ssl/cadcproxy.pem - diff --git a/example/cfis/config_MaMa.ini b/example/cfis/config_MaMa.ini index 102a9aa5e..cc4b7f9c4 100644 --- a/example/cfis/config_MaMa.ini +++ b/example/cfis/config_MaMa.ini @@ -44,7 +44,7 @@ OUTPUT_DIR = $SP_RUN/output [JOB] # Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 4 +SMP_BATCH_SIZE = 8 # Timeout value (optional), default is None, i.e. no timeout limit applied TIMEOUT = 96:00:00 diff --git a/example/cfis/config_exp_SpMh.ini b/example/cfis/config_exp_SpMh.ini index c5057ac1c..76f87ddd7 100644 --- a/example/cfis/config_exp_SpMh.ini +++ b/example/cfis/config_exp_SpMh.ini @@ -45,7 +45,7 @@ OUTPUT_DIR = $SP_RUN/output [JOB] # Batch size of parallel processing (optional), default is 1, i.e. run all jobs in serial -SMP_BATCH_SIZE = 4 +SMP_BATCH_SIZE = 16 # Timeout value (optional), default is None, i.e. no timeout limit applied TIMEOUT = 96:00:00 diff --git a/example/cfis/config_tile.mask b/example/cfis/config_tile.mask index d3eed92fb..4381d61b9 100644 --- a/example/cfis/config_tile.mask +++ b/example/cfis/config_tile.mask @@ -1,4 +1,6 @@ -# ----------------------------- Path ---------------------------------- +# Mask module config file for tiles + +## Paths to executables [PROGRAM_PATH] WW_PATH = ww @@ -8,7 +10,7 @@ WW_CONFIG_FILE = $SP_CONFIG/mask_default/default.ww # (e.g. no internet access on run nodes) CDSCLIENT_PATH = findgsc2.2 -# ----------------------------- Border parameters ---------------------------------- +## Border parameters [BORDER_PARAMETERS] BORDER_MAKE = False @@ -17,7 +19,7 @@ BORDER_WIDTH = 0 BORDER_FLAG_VALUE = 4 -# ----------------------------- Halo parameters ---------------------------------- +## Halo parameters [HALO_PARAMETERS] HALO_MAKE = True @@ -30,7 +32,7 @@ HALO_FLAG_VALUE = 2 HALO_REG_FILE = halo.reg -# ------------------------- Diffraction pike parameters ------------------------------ +## Diffraction pike parameters [SPIKE_PARAMETERS] SPIKE_MAKE = True @@ -43,24 +45,33 @@ SPIKE_FLAG_VALUE = 128 SPIKE_REG_FILE = spike.reg -# ---------------------------- Messier parameters --------------------------------- +## Messier parameters [MESSIER_PARAMETERS] MESSIER_MAKE = True -MESSIER_CAT_PATH = $SP_CONFIG/mask_default/Messier_catalog_updated.npy +MESSIER_CAT_PATH = $SP_CONFIG/mask_default/Messier_catalog_updated.fits MESSIER_PIXEL_SCALE = 0.187 MESSIER_SIZE_PLUS = 0. MESSIER_FLAG_VALUE = 16 +## NGC mask +[NGC_PARAMETERS] + +NGC_MAKE = True + +NGC_CAT_PATH = $SP_CONFIG/mask_default/ngc_cat.fits +NGC_SIZE_PLUS = 0. +NGC_FLAG_VALUE = 32 + -# -------------------------------- External flag ---------------------------------- +## External flag [EXTERNAL_FLAG] EF_MAKE = False -# -------------------------- Missing data parameters ------------------------------ +## Missing data parameters [MD_PARAMETERS] MD_MAKE = False @@ -70,7 +81,7 @@ MD_THRESH_REMOVE = 0.75 MD_REMOVE = False -# ----------------------------- Other parameters ---------------------------------- +## Other parameters [OTHER] KEEP_REG_FILE = False diff --git a/example/cfis/mask_default/Messier_catalog_updated.fits b/example/cfis/mask_default/Messier_catalog_updated.fits new file mode 100644 index 000000000..6a9f00096 Binary files /dev/null and b/example/cfis/mask_default/Messier_catalog_updated.fits differ diff --git a/example/cfis/mask_default/Messier_catalog_updated.npy b/example/cfis/mask_default/Messier_catalog_updated.npy deleted file mode 100644 index 2a59780e3..000000000 Binary files a/example/cfis/mask_default/Messier_catalog_updated.npy and /dev/null differ diff --git a/example/cfis/mask_default/ngc_cat.fits b/example/cfis/mask_default/ngc_cat.fits new file mode 100644 index 000000000..f51546da7 Binary files /dev/null and b/example/cfis/mask_default/ngc_cat.fits differ diff --git a/scripts/python/cfis_field_select.py b/scripts/python/cfis_field_select.py index 71733cab0..efe961ca7 100755 --- a/scripts/python/cfis_field_select.py +++ b/scripts/python/cfis_field_select.py @@ -26,7 +26,6 @@ from optparse import OptionParser, IndentedHelpFormatter, OptionGroup from astropy.io import fits -from astropy.table import Table, Column from astropy import units from astropy.coordinates import Angle, SkyCoord diff --git a/shapepipe/modules/get_images_package/__init__.py b/shapepipe/modules/get_images_package/__init__.py index 8544168ab..2c0b6b69c 100644 --- a/shapepipe/modules/get_images_package/__init__.py +++ b/shapepipe/modules/get_images_package/__init__.py @@ -44,8 +44,8 @@ INPUT_NUMBERING : str Input numbering scheme, python regexp CHECK_EXISTING_DIR : str, optional - If given, check this directory for existing images, which will then not be - downloaded + If given, search this directory (recursively) for existing images, which + will then not be downloaded N_EXPECTED : int, optional If ``CHECK_EXISTING_DIR`` is given, this option specifies the number of expected images; the default value is ``1`` diff --git a/shapepipe/modules/get_images_package/get_images.py b/shapepipe/modules/get_images_package/get_images.py index 52eae63b0..f0a4220c6 100644 --- a/shapepipe/modules/get_images_package/get_images.py +++ b/shapepipe/modules/get_images_package/get_images.py @@ -281,9 +281,25 @@ def retrieve(self, all_inputs, all_outputs): f'{self._check_existing_dir}/**/{out_base}', recursive=True, ) - if path and len(path) == self._n_expected: - self._w_log.info(f'{path[0]} found, skipping') - continue + if path: + if len(path) == self._n_expected: + self._w_log.info( + f'{path[0]} found, skipping download' + ) + continue + else: + self._w_log.info( + f'{len(path)} instead of {self._n_expected} ' + + 'existing files found at' + + f' {self._check_existing_dir}' + + ', downloading images' + ) + else: + self._w_log.info( + 'No existing images found at' + + f' {self._check_existing_dir},' + + ' downloading images' + ) self.retrieve_one(in_per_type[idx], out_per_type[idx]) def retrieve_one(self, in_path, out_path): diff --git a/shapepipe/modules/mask_package/__init__.py b/shapepipe/modules/mask_package/__init__.py index 88bcfd50a..96f638a1d 100644 --- a/shapepipe/modules/mask_package/__init__.py +++ b/shapepipe/modules/mask_package/__init__.py @@ -14,13 +14,17 @@ Description =========== -This module creates masks for bright stars, diffraction spikes, Messier -objects, borders, and other artifacts. If a flag file is given as input, -for example from pre-processing, the mask that is created by this module -is joined with the mask from this external flag file. In this case -the config flag ``USE_EXT_FLAG`` needs to be set to ``True``. To distinguish -the newly created output flag file from the input ones, a prefix can added as -specificed by the config entry ``PREFIX``. +This module creates masks for bright stars, diffraction spikes, deep sky +objects (from the Messier and NGC catalogues), borders, and other artifacts. If +a flag file is given as input, for example from pre-processing, the mask that +is created by this module is joined with the mask from this external flag file. +In this case the config flag ``USE_EXT_FLAG`` needs to be set to ``True``. To +distinguish the newly created output flag file from the input ones, a prefix +can added as specificed by the config entry ``PREFIX``. + +An NGC catalogue with positions, sizes, and types is provided with +``shapepipe``, +`source `_ Masked pixels of different mask types are indicated by integers, which conveniently are powers of two such that they can be combined bit-wise. @@ -137,6 +141,18 @@ MESSIER_FLAG_VALUE : int Messier mask pixel value, power of 2 +[NGC_PARAMETERS] +-------------------- + +NGC_MAKE : bool + Create mask around NGC objects if ``True`` +NGC_CAT_PATH : str + Path to NGC catalogue +NGC_SIZE_PLUS : float + Fraction to increase NGC mask +NGC_FLAG_VALUE : int + NGC mask pixel value, power of 2 + [MD_PARAMETERS] --------------- diff --git a/shapepipe/modules/mask_package/mask.py b/shapepipe/modules/mask_package/mask.py index 6e38f45f5..572f91bad 100644 --- a/shapepipe/modules/mask_package/mask.py +++ b/shapepipe/modules/mask_package/mask.py @@ -12,6 +12,7 @@ import numpy as np from astropy import units, wcs from astropy.coordinates import SkyCoord +from astropy.io import fits from shapepipe.pipeline import file_io from shapepipe.pipeline.config import CustomParser @@ -30,10 +31,23 @@ class Mask(object): Path to image (FITS format) weight_path : str Path to the weight image (FITS format) + image_prefix : str + Prefix to input image name, specify as ``'none'`` for no prefix + image_num : str + File number identified config_filepath : str Path to the ``.mask`` config file output_dir : str Path to the output directory + w_log : logging.Logger + Log file + path_external_flag : str, optional + Path to external flag file, default is ``None`` (not used) + outname_base : str, optional + Output file name base, default is ``flag`` + star_cat_path : str, optional + Path to external star catalogue, default is ``None`` (not used; + instead the star catalogue is produced on the fly at run time) hdu : int, optional HDU number, default is ``0`` @@ -47,6 +61,7 @@ def __init__( image_num, config_filepath, output_dir, + w_log, path_external_flag=None, outname_base='flag', star_cat_path=None, @@ -55,33 +70,51 @@ def __init__( # Path to the image to mask self._image_fullpath = image_path + # Path to the weight associated to the image self._weight_fullpath = weight_path + + # Input image prefix + if (image_prefix.lower() != 'none') and (image_prefix != ''): + self._img_prefix = f'{image_prefix}_' + else: + self._img_prefix = '' + + # File number identified + self._img_number = image_num + + # Path to mask config file self._config_filepath = config_filepath + # Path to the output directory self._output_dir = output_dir + + # Log file + self._w_log = w_log + # Path to an external flag file self._path_external_flag = path_external_flag + # Output file base name self._outname_base = outname_base - self._img_number = image_num - if (image_prefix.lower() != 'none') and (image_prefix != ''): - self._img_prefix = f'{image_prefix}_' - else: - self._img_prefix = '' - + # Set external star catalogue path if given if star_cat_path is not None: self._star_cat_path = star_cat_path self._hdu = hdu + + # Read mask config file self._get_config() - # Set parameters needed for the stars detection - self._set_parameters() + + # Set parameters needed for the star detection + self._set_image_coordinates() + + # Set error flag self._err = False def _get_config(self): - """Get Config Values. + """Get Config. Read the config file and set parameters. @@ -110,6 +143,7 @@ def _get_config(self): 'HALO': {}, 'SPIKE': {}, 'MESSIER': {}, + 'NGC': {}, 'MD': {}, } @@ -159,6 +193,7 @@ def _get_config(self): ) if self._config[mask_shape]['make']: + self._config[mask_shape]['maskmodel_path'] = conf.getexpanded( f'{mask_shape}_PARAMETERS', f'{mask_shape}_MASKMODEL_PATH', @@ -193,21 +228,27 @@ def _get_config(self): else: self._config[mask_shape]['reg_file'] = None - self._config['MESSIER']['make'] = ( - conf.getboolean('MESSIER_PARAMETERS', 'MESSIER_MAKE') - ) + for mask_type in ['MESSIER', 'NGC']: - if self._config['MESSIER']['make']: - self._config['MESSIER']['cat_path'] = ( - conf.getexpanded('MESSIER_PARAMETERS', 'MESSIER_CAT_PATH') - ) - self._config['MESSIER']['size_plus'] = ( - conf.getfloat('MESSIER_PARAMETERS', 'MESSIER_SIZE_PLUS') - ) - self._config['MESSIER']['flag'] = ( - conf.getint('MESSIER_PARAMETERS', 'MESSIER_FLAG_VALUE') + self._config[mask_type]['make'] = conf.getboolean( + f'{mask_type}_PARAMETERS', + f'{mask_type}_MAKE' ) + if self._config[mask_type]['make']: + self._config[mask_type]['cat_path'] = conf.getexpanded( + f'{mask_type}_PARAMETERS', + f'{mask_type}_CAT_PATH', + ) + self._config[mask_type]['size_plus'] = conf.getfloat( + f'{mask_type}_PARAMETERS', + f'{mask_type}_SIZE_PLUS', + ) + self._config[mask_type]['flag'] = conf.getint( + f'{mask_type}_PARAMETERS', + f'{mask_type}_FLAG_VALUE', + ) + self._config['MD']['make'] = ( conf.getboolean('MD_PARAMETERS', 'MD_MAKE') ) @@ -223,10 +264,11 @@ def _get_config(self): conf.getboolean('MD_PARAMETERS', 'MD_REMOVE') ) - def _set_parameters(self): - """Set Parameters. + def _set_image_coordinates(self): + """Set Image Coordinates. - Set the parameters for the stars detection. + Compute the image coordinates for matching with the star catalogue + and star mask. """ img = file_io.FITSCatalogue(self._image_fullpath, hdu_no=0) @@ -238,16 +280,25 @@ def _set_parameters(self): self._wcs = wcs.WCS(self._header) + # Compute field center + # Note: get_data().shape corresponds to (n_y, n_x) pix_center = [img_shape[1] / 2.0, img_shape[0] / 2.0] wcs_center = self._wcs.all_pix2world([pix_center], 1)[0] - self._fieldcenter = {} self._fieldcenter['pix'] = np.array(pix_center) self._fieldcenter['wcs'] = ( SkyCoord(ra=wcs_center[0], dec=wcs_center[1], unit='deg') ) + # Get the four corners of the image + corners = self._wcs.calc_footprint() + self._corners_sc = SkyCoord( + ra=corners[:, 0] * units.degree, + dec=corners[:, 1] * units.degree, + ) + + # Compute image radius = image diagonal self._img_radius = self._get_image_radius() def make_mask(self): @@ -279,14 +330,6 @@ def make_mask(self): mag_pivot=self._config[_type]['mag_pivot'], ) - if not self._err: - if self._config['BORDER']['make']: - border_mask = self.mask_border( - width=self._config['BORDER']['width'] - ) - else: - border_mask = None - if not self._err: mask_name = [] if self._config['HALO']['make'] and self._config['SPIKE']['make']: @@ -307,15 +350,22 @@ def make_mask(self): else: mask_name.append(None) + masks_internal = {} if not self._err: - if self._config['MESSIER']['make']: - messier_mask = self.mask_messier( - self._config['MESSIER']['cat_path'], - size_plus=self._config['MESSIER']['size_plus'], - flag_value=self._config['MESSIER']['flag'], + if self._config['BORDER']['make']: + masks_internal['BORDER'] = self.mask_border( + width=self._config['BORDER']['width'] ) - else: - messier_mask = None + + if not self._err: + for _type in ('MESSIER', 'NGC'): + if self._config[_type]['make']: + masks_internal[_type] = self.mask_dso( + self._config[_type]['cat_path'], + size_plus=self._config[_type]['size_plus'], + flag_value=self._config[_type]['flag'], + typ=_type, + ) if not self._err: try: @@ -331,8 +381,7 @@ def make_mask(self): final_mask = self._build_final_mask( path_mask1=mask_name[0], path_mask2=mask_name[1], - border=border_mask, - messier=messier_mask, + masks_internal=masks_internal, path_external_flag=path_external_flag, ) @@ -486,25 +535,28 @@ def mask_border(self, width=100, flag_value=4): return flag - def mask_messier(self, cat_path, size_plus=0.1, flag_value=8): - """Mask Messier. + def mask_dso(self, cat_path, size_plus=0.1, flag_value=8, typ='Messier'): + """Mask DSO. - Create a circular patch for Messier objects. + Create a circular patch for deep-sky objects (DSOs), e.g. + Messier or NGC objects. Parameters ---------- cat_path : str - Path to the Messier catalogue + Path to the deep-sky catalogue size_plus : float Increase the size of the mask by this factor (e.g. ``0.1`` means 10%) flag_value : int Value of the flag, some power of 2 + obj_type : {'Messier', 'NGO'}, optional + Object type Returns ------- numpy.ndarray or ``None`` - If no Messier objects are found in the field return ``None`` and + If no deep-sky objects are found in the field return ``None`` and the flag map Raises @@ -517,41 +569,66 @@ def mask_messier(self, cat_path, size_plus=0.1, flag_value=8): """ if size_plus < 0: raise ValueError( - 'Messier mask size increase variable cannot be negative' + 'deep-sky mask size increase variable cannot be negative' ) if cat_path is None: - raise ValueError('Path to Messier object catalogue not provided') + raise ValueError('Path to deep-sky object catalogue not provided') - m_cat = np.load(cat_path, allow_pickle=True) + m_cat, header = fits.getdata(cat_path, header=True) + + unit_ra = file_io.get_unit_from_fits_header(header, 'ra') + unit_dec = file_io.get_unit_from_fits_header(header, 'dec') m_sc = SkyCoord( - ra=m_cat['ra'] * units.degree, - dec=m_cat['dec'] * units.degree, + ra=m_cat['ra'] * unit_ra, + dec=m_cat['dec'] * unit_dec, ) - nx = self._fieldcenter['pix'][0] * 2 - ny = self._fieldcenter['pix'][1] * 2 - - # Get the four corners of the image - corners = self._wcs.calc_footprint() - corners_sc = SkyCoord( - ra=corners[:, 0] * units.degree, - dec=corners[:, 1] * units.degree, - ) + unit_size_X = file_io.get_unit_from_fits_header(header, 'size_X') + unit_size_Y = file_io.get_unit_from_fits_header(header, 'size_Y') - # Loop through all Messier objects and check whether any corner is + # Loop through all deep-sky objects and check whether any corner is # closer than the object's radius indices = [] + size_max_deg = [] for idx, m_obj in enumerate(m_cat): - r = max(m_obj['size']) * units.arcmin + + # DSO size + # r = max(m_obj['size']) * units.arcmin + r = max( + m_obj['size_X'] * unit_size_X, + m_obj['size_Y'] * unit_size_Y, + ) r_deg = r.to(units.degree) - if np.any(corners_sc.separation(m_sc[idx]) < r_deg): + size_max_deg.append(r_deg) + + # Add index to list if distance between DSO and any image corner + # is closer than DSO size + if np.any(self._corners_sc.separation(m_sc[idx]) < r_deg): indices.append(idx) + self._w_log.info( + f'Found {len(indices)} {obj_type} objects overlapping with' + ' image' + ) + if len(indices) == 0: - # No closeby Messier object found + # No closeby deep-sky object found return None + # Compute number of DSO center coordinates in footprint, for logging + # purpose only + n_dso_center_in_footprint = 0 + for idx in indices: + in_img = self._wcs.footprint_contains(m_sc[idx]) + self._w_log.info( + '(obj_type, ra, dec, in_img) = ' + + f'({obj_type}, ' + + f'{m_cat["ra"][idx]}, ' + + f'{m_cat["dec"][idx]}, ' + + f'{in_img})' + ) + # Note: python image array is [y, x] flag = np.zeros( ( @@ -561,6 +638,8 @@ def mask_messier(self, cat_path, size_plus=0.1, flag_value=8): dtype='uint16', ) + nx = self._fieldcenter['pix'][0] * 2 + ny = self._fieldcenter['pix'][1] * 2 for idx in indices: m_center = np.hstack(self._wcs.all_world2pix( m_cat['ra'][idx], @@ -568,11 +647,11 @@ def mask_messier(self, cat_path, size_plus=0.1, flag_value=8): 0, )) r_pix = ( - max(m_cat['size'][idx]) / 60.0 * (1 + size_plus) + size_max_deg[idx].to(units.deg).value * (1 + size_plus) / np.abs(self._wcs.pixel_scale_matrix[0][0]) ) - # The following accounts for Messier centers outside of image, + # The following accounts for deep-sky centers outside of image, # without creating masks for coordinates out of range y_c, x_c = np.ogrid[0:ny, 0:nx] mask_tmp = ( @@ -1016,8 +1095,7 @@ def _build_final_mask( self, path_mask1, path_mask2=None, - border=None, - messier=None, + masks_internal=None, path_external_flag=None, ): """Create Final Mask. @@ -1030,10 +1108,8 @@ def _build_final_mask( Path to a mask (FITS format) path_mask2 : str, optional Path to a mask (FITS format) - border : numpy.ndarray, optional - Array containing the border mask - messier : numpy.ndarray, optional - Array containing the messier mask + masks_internal : dict, optional + Internally created masks path_external_flag : str, optional Path to an external flag file @@ -1045,8 +1121,7 @@ def _build_final_mask( Raises ------ ValueError - If ``path_mask1``, ``path_mask2``, border and messier are of type - ``None`` + If all masks are of type ``None`` TypeError If border is not a Numpy array TypeError @@ -1056,12 +1131,12 @@ def _build_final_mask( final_mask = None if ( - path_mask1 is None and path_mask2 is None and border is None - and messier is None + path_mask1 is None and path_mask2 is None + and not masks_internal ): raise ValueError( 'No paths to mask files containing halos and/or spikes,' - + ' borders, or Messier objects provided' + + ' borders, or deep-sky objects provided' ) if path_mask1 is not None: @@ -1078,23 +1153,18 @@ def _build_final_mask( else: final_mask = mask2.get_data()[:, :] - if border is not None: - if type(border) is np.ndarray: - if final_mask is not None: - final_mask += border - else: - final_mask = border - else: - raise TypeError('border mask has to be a numpy.ndarray') - - if messier is not None: - if type(messier) is np.ndarray: - if final_mask is not None: - final_mask += messier + for typ in masks_internal: + if masks_internal[typ] is not None: + if type(masks_internal[typ]) is np.ndarray: + if final_mask is not None: + final_mask += masks_internal[typ] + else: + final_mask = masks_internal[typ] else: - final_mask = messier - else: - raise TypeError('Messier mask has to be a numpy.ndarray') + raise TypeError( + f'internally created mask of type {typ} ' + + 'has to be numpy.ndarray' + ) if path_external_flag is not None: external_flag = file_io.FITSCatalogue( diff --git a/shapepipe/modules/mask_runner.py b/shapepipe/modules/mask_runner.py index ce8c53dbd..3683076e4 100644 --- a/shapepipe/modules/mask_runner.py +++ b/shapepipe/modules/mask_runner.py @@ -102,6 +102,7 @@ def mask_runner( outname_base=outname_base, star_cat_path=ext_star_cat, hdu=hdu, + w_log=w_log, ) # Process module diff --git a/shapepipe/pipeline/file_io.py b/shapepipe/pipeline/file_io.py index a4f54badd..a768f0c06 100644 --- a/shapepipe/pipeline/file_io.py +++ b/shapepipe/pipeline/file_io.py @@ -10,6 +10,7 @@ import numpy as np from astropy.io import fits +from astropy import units from astropy.table import Table @@ -1841,3 +1842,59 @@ def comment(self, comment): @data.setter def data(self, data): self._data = data + + +def get_unit_from_fits_header(header, key): + """Get Unit From FITS Header. + + Return coordinate unit corresponding to column with name ``key``. + + Parameters + ---------- + header : FITS header + Header information + key : str + Column name + + Raises + ------ + IndexError + If key not in header + + Returns + ------- + astropy.units.Unit + Unit object + + """ + # Loop over column names to find key + idx = 1 + idx_found = -1 + while True: + ttype_idx = f'TTYPE{idx}' + if ttype_idx not in header: + # Reached beyond last column + break + else: + if header[ttype_idx] == key: + # Found correct column + idx_found = idx + break + idx += 1 + + if idx_found == -1: + raise IndexError(f'Column \'{key}\' not found in FITS header') + + # Extract coordinate unit string from header + tcunit_idx = f'TCUNI{idx}' + if tcunit_idx not in header: + raise IndexError( + f'No coordinate unit found for column \'{key}\'' + ' in FITS header' + ) + unit_str = header[tcunit_idx] + + # Transform to Unit object + u = units.Unit(unit_str) + + return u