def make_all_spectra(catalog_files, input_spectra=None, input_spectra_file=None, extrapolate_SED=True, output_filename=None, normalizing_mag_column=None, module=None, detector=None, ghost_catalog_files=[]): """Overall wrapper function Parameters ---------- catalog_files : str or list Name(s) of Mirage-formatted source catalog file(s) (ascii) input_spectra : dict Dictionary containing spectra for some/all targets. Dictionary keys are the object indexes (such as from the index column in the catalog_file. Entries must be e.g. d[1] = {"wavelengths": <wavelength_list>, "fluxes": <List of spectral flux densities>} Wavelengths and fluxes can be lists, or lists with astropy units attached. If no units are supplied, Mirage assumes wavelengths in microns and flux densities in Flambda units. input_spectra_file : str Name of an hdf5 file containing spectra for some/all targets extrapolate_SED : bool If True and an input SED does not cover the entire wavelength range of the grism, linear interpolation is used to extend the SED output_filename : str Name of the output HDF5 file, which will contain SEDs for all targets. normalizing_mag_column : str If some/all of the input spectra (from input_spectra or input_spectra_file) are to be renormalized, they will be scaled based on the magnitude values given in this specified column from the input catalog_file. module : str Name of module (e.g. 'A'). Only used when ``normalizing_mag_column`` is a NIRCam filter detector : str Name of detector (e.g. 'GUIDER1'). Only used when ``normalizing_mag_column`` is for FGS ghost_catalog_files : list Source catalogs containing optical ghosts, based on the astrophysical sources within ``catalog_files`` Returns ------- output_filename : str Name of the saved HDF5 file containing all object spectra. """ logger = logging.getLogger( 'mirage.catalogs.spectra_from_catalog.make_all_spectra') # Create the output filename if needed if output_filename is None: output_filename = create_output_sed_filename(catalog_files[0], input_spectra_file) # If normalizing_mag_column is not None, get instrument info in order # to find the correct gain value if normalizing_mag_column is not None: instrument = normalizing_mag_column.split('_')[0].lower() filter_name = 'none' pupil_name = 'none' if instrument == 'niriss': filter_name = normalizing_mag_column.split('_')[1] gain = MEAN_GAIN_VALUES['niriss'] elif instrument == 'nircam': filter_name, pupil_name = mag_col_name_to_filter_pupil( normalizing_mag_column) filter_val = int(filter_name[1:4]) if filter_val > 230.: channel = 'lw' else: channel = 'sw' selector = '{}{}'.format(channel, module.lower()) gain = MEAN_GAIN_VALUES['nircam'][selector] elif instrument == 'fgs': gain = MEAN_GAIN_VALUES['fgs'][detector.lower()] # Dictionary to contain all input spectra all_input_spectra = {} # Read in input spectra from file, add to all_input_spectra dictionary if input_spectra_file is not None: spectra_from_file = hdf5_catalog.open(input_spectra_file) all_input_spectra = {**all_input_spectra, **spectra_from_file} # If both an input spectra file and dictionary are given, combine into # a single dictionary. Running in this order means that dictionary # inputs override inputs from a file. if input_spectra is not None: all_input_spectra = {**all_input_spectra, **input_spectra} # If a single input source catalog is provided, make it a list, in order # to be consistent with the case of multiple input catalogs if isinstance(catalog_files, str): catalog_files = [catalog_files] # Loop over input catalogs, which may be of different types # (e.g. point source, galaxy, etc) for catalog_file in catalog_files: # Read in input catalog ascii_catalog, mag_sys = read_catalog(catalog_file) # Create catalog output name if none is given cat_dir, cat_file = os.path.split(catalog_file) index = cat_file.rindex('.') suffix = cat_file[index:] outbase = cat_file[0:index] + '_with_flambda' + suffix flambda_output_catalog = os.path.join(cat_dir, outbase) catalog, filter_info = add_flam_columns(ascii_catalog, mag_sys) catalog.write(flambda_output_catalog, format='ascii', overwrite=True) logger.info( 'Catalog updated with f_lambda columns, saved to: {}'.format( flambda_output_catalog)) # Renormalize if len(all_input_spectra) > 0 and normalizing_mag_column is not None: rescaling_magnitudes = ascii_catalog['index', normalizing_mag_column] # Note that nircam_module is ignored for non-NIRCam requests. # fgs_detector is ignored for non-FGS requests filter_thru_file = get_filter_throughput_file( instrument=instrument, filter_name=filter_name, pupil_name=pupil_name, nircam_module=module, fgs_detector=detector) all_input_spectra = rescale_normalized_spectra( all_input_spectra, rescaling_magnitudes, mag_sys, filter_thru_file, gain) # For sources in catalog_file but not in all_input_spectra, use the # magnitudes in the catalog_file, interpolate/extrapolate as necessary # and create continuum spectra indexes_to_create = [] for i, line in enumerate(ascii_catalog): if line['index'] not in all_input_spectra.keys(): indexes_to_create.append(i) if len(indexes_to_create) > 0: continuum = create_spectra(ascii_catalog[indexes_to_create], filter_info, extrapolate_SED=extrapolate_SED) all_input_spectra = {**all_input_spectra, **continuum} # Add entries for any ghost sources by copying the spectra for the # sources that caused the ghosts. This needs to be done after we have # spectra for all real sources, so we need a separate, second loop over # the catalogs here. for catalog_file in ghost_catalog_files: ascii_catalog, mag_sys = read_catalog(catalog_file) if 'corresponding_source_index_for_ghost' in ascii_catalog.colnames: print('Within the catalog') for source in ascii_catalog: if source['corresponding_source_index_for_ghost'] != 0: spec = all_input_spectra[ source['corresponding_source_index_for_ghost']] if source['index'] in all_input_spectra.keys(): raise ValueError(( "Attempting to add spectrum for ghost source {} to SED file, " "but there is already a spectrum for that index present." .format(source['index']))) print('Found a ghost. Adding source.') all_input_spectra[source[index]] = spec logger.info(( "Adding spectrum for ghost source {} to SED file. Copy spectrum from source {}. " .format( source[index], source['corresponding_source_index_for_ghost']))) # For convenience, reorder the sources by index number spectra = OrderedDict({}) for item in sorted(all_input_spectra.items()): spectra[item[0]] = item[1] # Save the source spectra in an hdf5 file hdf5_catalog.save(spectra, output_filename, wavelength_unit='micron', flux_unit='flam') logger.info('Spectra catalog file saved to {}'.format(output_filename)) return output_filename
def get_filter_info(column_names, magsys): """Given a list of catalog columns names (e.g. 'nircam_f480m_magnitude') get the corresponding PHOTFLAM, PHOTFNU, filter zeropoint and pivot wavelength values. Parameters ---------- column_names : list List of Mirage catalog column names magsys : str Magnitude system (e.g. 'abmag') Returns ------- info : dict Dictionary of values (e.g. info['nircam_f480m_magnitude'] = (<photflam>, <photfnu>, <zeropoint>, <pivot>)) """ # Get the correct zeropoint file name instrument = column_names[0].split('_')[0].lower() zp_file = ZEROPOINT_FILES[instrument] # Read in the file zp_table = ascii.read(zp_file) magsys = magsys.upper() info = {} if instrument in ['nircam', 'niriss']: for entry in column_names: filter_name, pupil_name = mag_col_name_to_filter_pupil(entry) # NIRCam zeropoint files have entries for WLP8 but not WLM8 since # they are identical if pupil_name.upper() == 'WLM8': pupil_name = 'WLP8' if instrument == 'nircam': match = ((zp_table['Filter'] == filter_name.upper()) & (zp_table['Pupil'] == pupil_name.upper()) & (zp_table['Module'] == 'B')) elif instrument == 'niriss': match = zp_table['Filter'] == filter_name.upper() if not np.any(match): raise ValueError("ERROR: no filter matching {} in {}.".format( filter_name, zp_file)) zp = zp_table[magsys].data[match][0] photflam = zp_table['PHOTFLAM'].data[match][0] * FLAMBDA_CGS_UNITS photfnu = zp_table['PHOTFNU'].data[match][0] * FNU_CGS_UNITS pivot = zp_table['Pivot_wave'].data[match][0] * u.micron info[entry] = (photflam, photfnu, zp, pivot) # For FGS, just use the values for GUIDER1 detector. elif instrument == 'fgs': line = zp_table[0] zp = line[magsys] photflam = line['PHOTFLAM'] * FLAMBDA_CGS_UNITS photfnu = line['PHOTFNU'] * FNU_CGS_UNITS pivot = line['Pivot_wave'] * u.micron info[column_names[0]] = (photflam, photfnu, zp, pivot) return info