Source code for pypeit.scripts.chk_noise_1dspec

"""
This script explores the noise in a slit or spectrum
for one of the outputs of PypeIt

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

"""
import numpy as np

import os
from matplotlib import pyplot as plt
from astropy.stats import sigma_clip, mad_std
from astropy.modeling.models import Gaussian1D
from astropy.io import fits

from pypeit.scripts import scriptbase
from pypeit import utils
from pypeit import msgs
from pypeit import specobjs
from pypeit.onespec import OneSpec

from IPython import embed


[docs]def plot(args, line_wav_z:np.ndarray, line_names:np.ndarray, flux:np.ndarray, err:np.ndarray, mask:np.ndarray, input_mask:np.ndarray, ratio:np.ndarray, lbda:np.ndarray, basename:str, folder:str=None, z:float=None, plot_shaded=True): """ Plot the 1D spectrum Args: args (`argparse.ArgumentParser`_): Command-line argument parser. line_wav_z (`numpy.ndarray`_): Array of wavelength of spectral features to be plotted. line_names (`numpy.ndarray`_): Array of names of spectral features to be plotted. flux (`numpy.ndarray`_): Flux of the spectrum. err (`numpy.ndarray`_): Sig of the spectrum. lbda (`numpy.ndarray`_): Wavelength values of the spectrum. mask (`numpy.ndarray`_): input_mask (`numpy.ndarray`_): ratio (`numpy.ndarray`_): Chi values basename (:obj:`str`): Basename folder (:obj:`str`, optional): Output folder, if saving. Defaults to None. z (:obj:`float`, optional): Redshift of the object that we want to plot. Defaults to None. plot_shaded (:obj:`bool`, optional): Do you want to plot the shaded area that shows the wavelength range used in the analysis? Defaults to True. """ _folder = 'chk_noise_1dspec' if folder is None else folder fig = plt.figure(figsize=(23, 6.)) ax = plt.subplot2grid((1, 4), (0, 0), rowspan=1, colspan=3) ax.minorticks_on() drawstyle = 'steps-mid' if args.step else 'default' sec=mask if lbda[sec].size == 0: sec = lbda > 0 ax.plot(lbda[sec], flux[sec], lw=1, zorder=1, drawstyle=drawstyle, label='{}'.format(basename)) ax.plot(lbda[sec], np.zeros(lbda[sec].size), ls='--', color='Gray', zorder=-1) if args.ploterr: ax.plot(lbda[sec], err[sec], drawstyle=drawstyle, lw=1, color='#ff6666', zorder=2, label='noise') if z is not None: line_num = 0 for i in range(line_wav_z.shape[0]): if lbda[sec].min() < line_wav_z[i] < lbda[sec].max(): line_num += 1 yannot = 0.94 if (line_num % 2) == 0 else 0.90 ax.axvline(line_wav_z[i], color='Gray', ls='dotted', zorder=-1) ax.annotate('{}'.format(line_names[i]), xy=(line_wav_z[i],1), xytext=(line_wav_z[i], yannot), xycoords=('data', 'axes fraction'), arrowprops=dict(facecolor='None', edgecolor='None', headwidth=0., headlength=0, width=0, shrink=0.), annotation_clip=True, horizontalalignment='center', color='k', fontsize=10) if plot_shaded: ax.axvspan(lbda[input_mask].min(), lbda[input_mask].max(), color='tab:green', alpha=0.2) ax.set_xlim(np.min(lbda[sec]), np.max(lbda[sec])) ymax = sigma_clip(flux[sec], sigma=10, return_bounds=True)[2]*1.3 ymin = sigma_clip(flux[sec], sigma=5, return_bounds=True)[1]*1.05 ax.set_ylim(ymin, ymax) ax.set_xlabel('Wavelength (Angstrom)') ax.set_ylabel(r'Counts') plt.legend(loc=3) ax2 = plt.subplot2grid((1, 4), (0, 3), rowspan=1, colspan=1) ax2.minorticks_on() bin_step = (np.nanmax(ratio) - np.nanmin(ratio))*0.01 bins = np.arange(np.nanmin(ratio), np.nanmax(ratio), bin_step) hist_n, hist_bins, _ = ax2.hist(ratio, bins=bins, histtype='stepfilled') mod_mods = Gaussian1D(amplitude=hist_n.max(), mean=np.median(ratio), stddev=1.) ax2.plot(bins, mod_mods(bins), label=r"$\sigma=1$") ax2.axvline(0, ls='dotted', color='Gray') ax2.set_xlim(-6.5, 6.5) ax2.set_ylim(-0.02, hist_n.max()*1.5) ax2.set_xlabel(r'Flux/Noise') ax2.set_ylabel(r'#') err_over_flux = (np.median(err[input_mask])/mad_std(flux[input_mask])) ax2.text(0.97, 0.93, r'Median Noise= {:.1f} - Flux RMS= {:.1f} --> {:.2f}x'.format(np.median(err[input_mask]), mad_std(flux[input_mask]), err_over_flux), color='k', fontsize=9, horizontalalignment='right', transform=ax2.transAxes) ax2.text(0.97, 0.87, r'Chi: Median = {:.2f}, Std = {:.2f}'.format(np.median(ratio), mad_std(ratio)), color='k', fontsize=12, horizontalalignment='right', transform=ax2.transAxes, weight='bold') ax2.legend(loc=2) plt.tight_layout() # Finish if args.plot_or_save == 'plot': plt.show() elif args.plot_or_save == 'save': plt.savefig('{}/noisecheck_{}.png'.format(_folder, basename), bbox_inches='tight', dpi=200) plt.close()
[docs]def get_basename(header, extract_type, pypeit_name=None, maskdef_objname=None, maskdef_extract=False): if maskdef_extract: return '{}_{}obj{}_{}_{}'.format(header.get('DECKER'), extract_type, maskdef_objname, pypeit_name, 'maskdef_extract') elif maskdef_objname is not None: return '{}_{}obj{}_{}'.format(header.get('DECKER'), extract_type, maskdef_objname, pypeit_name) elif pypeit_name is not None: return '{}_{}_{}'.format(header.get('DECKER'), extract_type, pypeit_name) else: return '{}_{}'.format(header.get('DECKER'), extract_type)
[docs]class ChkNoise1D(scriptbase.ScriptBase):
[docs] @classmethod def get_parser(cls, width=None): parser = super().get_parser(description='Examine the noise in a PypeIt spectrum', width=width) parser.add_argument('files', type=str, nargs='*', help='PypeIt spec1d file(s)') parser.add_argument('--fileformat', default='spec1d', type=str, help='Is this coadd1d or spec1d?') parser.add_argument('--extraction', default='opt', type=str, help='If spec1d, which extraction? opt or box') parser.add_argument('--ploterr', default=False, help='Plot noise spectrum', action='store_true') parser.add_argument('--step', default=False, help='Use `steps-mid` as linestyle', action='store_true') parser.add_argument('--z', default=None, type=float, nargs='*', help='Object redshift') parser.add_argument('--maskdef_objname', default=None, type=str, help='MASKDEF_OBJNAME of the ' 'target that you want to plot. ' 'If maskdef_objname is not provided, ' 'nor a pypeit_name, all the 1D spectra ' 'in the file(s) will be plotted.') parser.add_argument('--pypeit_name', default=None, type=str, help='PypeIt name of the target that ' 'you want to plot. ' 'If pypeit_name is not provided, ' 'nor a maskdef_objname, all the 1D spectra ' 'in the file(s) will be plotted.') parser.add_argument('--wavemin', default=None, type=float, help='Wavelength min. This is for selecting ' 'a region of the spectrum to analyze.') parser.add_argument('--wavemax', default=None, type=float, help='Wavelength max.This is for selecting ' 'a region of the spectrum to analyze.') parser.add_argument('--plot_or_save', default='plot', type=str, help='Do you want to save to disk or open ' 'a plot in a mpl window. If you choose ' 'save, a folder called spec1d*_noisecheck' ' will be created and all the relevant ' 'plot will be placed there.') parser.add_argument('--try_old', default=False, action='store_true', help='Attempt to load old datamodel versions. A crash may ensue..') return parser
[docs] @staticmethod def main(args): chk_version = not args.try_old # Load em line_names, line_wav = utils.list_of_spectral_lines() files = np.array(args.files) if args.z is not None: zs = np.array(args.z) # Loop on the files for i in range(files.size): # reinitialize lines wave line_wav_z = line_wav.copy() # Deal with redshifts if args.z is not None: z = zs[i] if zs.size == files.size else zs[0] line_wav_z *= (1+z) # redshifted linelist else: z = None # this file and header file = files[i] head = fits.getheader(file) # I/O spec object specObjs = [OneSpec.from_file(file, chk_version=chk_version)] \ if args.fileformat == 'coadd1d' else \ specobjs.SpecObjs.from_fitsfile(file, chk_version=chk_version) # loop on the spectra for spec in specObjs: if args.fileformat == 'coadd1d': lbda = spec.wave flux = spec.flux err = spec.sig mask = spec.mask == 1 extract_type = spec.ext_mode maskdef_objname = None pypeit_name = None maskdef_extract = False else: maskdef_objname = spec.MASKDEF_OBJNAME pypeit_name = spec.NAME maskdef_extract = spec.MASKDEF_EXTRACT if args.extraction == 'opt' and spec['OPT_COUNTS'] is not None: lbda = spec['OPT_WAVE'] flux = spec['OPT_COUNTS'] err = spec['OPT_COUNTS_SIG'] mask = spec['OPT_MASK'] extract_type = 'OPT' else: lbda = spec['BOX_WAVE'] flux = spec['BOX_COUNTS'] err = spec['BOX_COUNTS_SIG'] mask = spec['BOX_MASK'] extract_type = 'BOX' # plot specific spectrum or all of them? plot_this = True if args.maskdef_objname is not None: plot_this = True if maskdef_objname == args.maskdef_objname else False if args.pypeit_name is not None: plot_this = True if pypeit_name == args.pypeit_name else False # OK plot if plot_this: basename = get_basename(head, extract_type, pypeit_name=pypeit_name, maskdef_objname=maskdef_objname, maskdef_extract=maskdef_extract) # Cut down input_mask = mask.copy() if args.wavemin is not None: input_mask &= lbda > args.wavemin if args.wavemax is not None: input_mask &= lbda < args.wavemax if lbda[input_mask].size < 10: msgs.warn("The spectrum was cut down to <10 pixels. Skipping") continue # determine if plotting the shaded area in the plot that shows the # wavelength range used to compute the chi plot_shaded = False if args.wavemin is None and args.wavemax is None else True # Save? if args.plot_or_save == 'save': folder = '{}_noisecheck'.format(file.split('.fits')[0]) if not os.path.exists(folder): os.makedirs(folder) else: folder = None # Plot ratio = flux[input_mask]/err[input_mask] plot(args, line_wav_z, line_names, flux, err, mask, input_mask, ratio, lbda, basename, folder=folder, z=z, plot_shaded=plot_shaded)