Source code for pypeit.scripts.flux_setup

"""
Setup files for flux calibration.

.. include common links, assuming primary doc root is up one directory
.. include:: ../include/links.rst
"""

import os
from pathlib import Path

import numpy as np

from astropy.table import Table

from pypeit import msgs
from pypeit import io
from pypeit.scripts import scriptbase
from pypeit import inputfiles
from pypeit.spectrographs.util import load_spectrograph

[docs]def match_spec1ds_to_sensfuncs(spectrograph_name, spec1dfiles, sensfiles): """ This needs a docstring """ result_map = {} spectrograph = load_spectrograph(spectrograph_name) # Read configurations of each sensfile sens_configs = [] for sensfile in sensfiles: with io.fits_open(sensfile) as hdul: header = hdul[0].header sens_configs.append({config_key: header[config_key] for config_key in spectrograph.configuration_keys() if config_key in header}) # Read the configurations of each spec1d file, and try to find a matching sensfunc file for spec1dfile in spec1dfiles: with io.fits_open(spec1dfile) as hdul: header = hdul[0].header spec1d_config = {config_key: header[config_key] for config_key in spectrograph.configuration_keys() if config_key in header} for i, sens_config in enumerate(sens_configs): if spectrograph.same_configuration([spec1d_config, sens_config],check_keys=False): result_map[spec1dfile.name] = sensfiles[i] break return result_map
[docs]class FluxSetup(scriptbase.ScriptBase):
[docs] @classmethod def get_parser(cls, width=None): parser = super().get_parser(description='Setup configuration files to perform flux calibration, 1D coadding, and telluric correction.', width=width, formatter=scriptbase.SmartFormatter) parser.add_argument("paths", type=str, nargs='+', help="One or more paths for Science folders or sensitivity functions. Sensitivity functions must start with 'sens_' to be detected.") parser.add_argument("--name", type=str, default=None, help="The base name to use for the output files. Defaults to the instrument name is used.") parser.add_argument("--objmodel", type=str, default='qso', choices=['qso', 'star', 'poly'], help='R|science object model used in the telluric fitting. The ' 'options are:\n\n' 'qso = For quasars. You might need to set redshift, ' 'bal_wv_min_max in the tell file.\n' '\n' 'star = For stars. You need to set star_type, star_ra, star_dec, ' 'and star_mag in the tell_file.\n' '\n' 'poly = For other type object, You might need to set ' 'fit_wv_min_max, and norder in the tell_file.\n' '\n') return parser
[docs] @staticmethod def main(args): """ This setups PypeIt input files for fluxing, coadding, and telluric corrections. It will produce three files named as name.flux, name.coadd1d, and name.tell. "name" defaults to the spectrograph name but can be overriden on the command line. """ allfiles = [] for path in args.paths: allfiles += Path(path).iterdir() spec1dfiles = [] spec2dfiles = [] spec1dinfos = [] unique_paths = set() sensfiles = [] for ifile in allfiles: if ('spec1d' in ifile.name) and ('.fits' in ifile.name): spec1dfiles.append(ifile) unique_paths.add(str(ifile.parent)) elif ('spec2d' in ifile.name) and ('.fits' in ifile.name): spec2dfiles.append(ifile) elif ('spec1d' in ifile.name) and ('.txt' in ifile.name): spec1dinfos.append(ifile) elif ifile.name.startswith('sens_') and ('.fits' in ifile.name): sensfiles.append(ifile) unique_paths.add(str(ifile.parent)) else: msgs.info('{:} is not a standard PypeIt output, skipping.'.format(ifile)) if len(spec2dfiles) > len(spec1dfiles): msgs.warn('The following exposures do not have 1D extractions:') for ii in range(len(spec2dfiles)): if (spec2dfiles[ii].parent / spec2dfiles[ii].name.replace("spec2d", "spec1d")).exists(): msgs.info('\t {:}'.format(spec2dfiles[ii])) if len(spec1dfiles) > 0: with io.fits_open(spec1dfiles[0]) as hdul: # Get basic configuration info from first spec1d spectrograph = hdul[0].header['PYP_SPEC'] pypeline = hdul[0].header['PYPELINE'] if args.name is None: output_basename = spectrograph else: output_basename = args.name # Determine how to map sensitivity functions to spec1d files sensfile_mapping=match_spec1ds_to_sensfuncs(spectrograph, spec1dfiles, sensfiles) ## fluxing pypeit file # Build the bits and pieces cfg_lines = ['[fluxcalib]'] cfg_lines += [' extinct_correct = False # Set to True if your SENSFUNC derived with the UVIS algorithm\n'] cfg_lines += ['# Please add your SENSFUNC file name below before running pypeit_flux_calib'] data = Table() data['filename'] = [x.name for x in spec1dfiles] data['sensfile'] = ['' if x.name not in sensfile_mapping else sensfile_mapping[x.name].name for x in spec1dfiles] # Instantiate fluxFile = inputfiles.FluxFile( config=cfg_lines, file_paths = unique_paths, data_table=data) # Write flux_file = f'{output_basename}.flux' fluxFile.write(flux_file) ## coadd1d pypeit file cfg_lines = ['[coadd1d]'] cfg_lines += [' coaddfile = YOUR_OUTPUT_FILE_NAME # Please set your output file name'] if pypeline == 'Echelle': cfg_lines += [' wave_method = velocity # creates a uniformly space grid in log10(lambda)\n'] else: cfg_lines += [' wave_method = linear # creates a uniformly space grid in lambda\n'] cfg_lines += ['# This file includes all extracted objects. You need to figure out which object you want to \n'+\ '# coadd before running pypeit_coadd_1dspec!!!'] if pypeline == 'Echelle': cfg_lines += ['# For Echelle spectrographs, please double check the sensfunc file and setup id\n'] all_specfiles, all_obj = [], [] for ii in range(len(spec1dfiles)): txtinfofile = spec1dfiles[ii].parent / (spec1dfiles[ii].stem + ".txt") meta_tbl = Table.read(txtinfofile, format='ascii.fixed_width') _, indx = np.unique(meta_tbl['name'],return_index=True) objects = meta_tbl[indx] for jj in range(len(objects)): all_specfiles.append(spec1dfiles[ii]) all_obj.append(objects['name'][jj]) data = Table() data['filename'] = [str(x.name) for x in all_specfiles] data['obj_id'] = all_obj if pypeline == 'Echelle': if len(sensfiles) > 1: # If there are multiple sensfunc files, try to set sensibile values # for the 'sensfile' and 'setup_id' columns all_sensfiles = [] all_setup_ids = [] for spec1d in data['filename']: if spec1d in sensfile_mapping: sensfile = sensfile_mapping[spec1d] # Use the index of the sensfile in sensfiles as the setup id, # converted to a letter. Hopefully we don't get more than 26 setup_id = chr(ord('A') + sensfiles.index(sensfile)) else: sensfile = 'SENSFUNC FILE' setup_id = 'default' all_sensfiles.append(sensfile.name) all_setup_ids.append(setup_id) data['sensfile'] = all_sensfiles data['setup_id'] = all_setup_ids else: # Just use one default sensfunc file and one setup. if len(sensfiles) == 1: default_sensfile = sensfiles[0].name # Use the first sensfunc and only sensfunc else: default_sensfile = 'SENSFUNC FILE' # Use a dummy sensfunc filename data['sensfile'] = [default_sensfile] + ([''] * (len(all_obj)-1)) data['setup_id'] = ['A'] + ([''] * (len(all_obj)-1)) # Instantiate coadd1dFile = inputfiles.Coadd1DFile( config=cfg_lines, file_paths = unique_paths, data_table=data) # Write coadd1d_file = '{:}.coadd1d'.format(output_basename) coadd1dFile.write(coadd1d_file) ## tellfit pypeit file cfg_lines = ['[telluric]'] if args.objmodel == 'qso': cfg_lines += [' objmodel = qso'] cfg_lines += [' redshift = 0.0'] cfg_lines += [' bal_wv_min_max = 10000.,11000.'] elif args.objmodel == 'star': cfg_lines += [' objmodel = star'] cfg_lines += [' star_type = A0'] cfg_lines += [' star_mag = 0.0'] elif args.objmodel == 'poly': cfg_lines += [' objmodel = poly'] cfg_lines += [' polyorder = 5'] cfg_lines += [' fit_wv_min_max = 17000.0,22000.0'] # Instantiate tellFile = inputfiles.TelluricFile( config=cfg_lines) # Write tellfit_file = f'{output_basename}.tell' tellFile.write(tellfit_file)