Source code for webbpsf_ext.utils

# Import libraries
from copy import deepcopy
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

import os, sys
import six

import poppy, pysiaf, stpsf

from stpsf.stpsf_core import get_siaf_with_caching

siaf_nrc = get_siaf_with_caching('NIRCam')
siaf_nis = get_siaf_with_caching('NIRISS')
siaf_mir = get_siaf_with_caching('MIRI')
siaf_nrs = get_siaf_with_caching('NIRSpec')
siaf_fgs = get_siaf_with_caching('FGS')

from . import conf
from .logging_utils import setup_logging

import logging
_log = logging.getLogger('webbpsf_ext')

# from . import synphot_ext as S
from . import synphot_ext
on_rtd = os.environ.get('READTHEDOCS') == 'True'
if not on_rtd:
    synphot_ext.download_cdbs_data(verbose=True)

# Progress bar
from tqdm.auto import trange, tqdm


[docs] def check_fitsgz(opd_file, inst_str=None): """ STPSF FITS files can be either .fits or compressed .gz. Search for .fits by default, then .fits.gz. Parameters ========== opd_file : str Name of FITS file, either .fits or .fits.gz inst_str : None or str If OPD file is instrument-specific, then specify here. Will look in instrument OPD directory. If set to None, then also checks `opd_file` for an instrument-specific string to determine if to look in instrument OPD directory, otherwise assume file name is in STPSF data base directory. """ from stpsf.utils import get_stpsf_data_path # Check if instrument name is in OPD file name # If so, then this is in instrument OPD directory # Otherwise, exists in stpsf_data top directory if inst_str is None: inst_names = ['NIRCam', 'NIRISS', 'NIRSpec', 'MIRI', 'FGS'] for iname in inst_names: if iname in opd_file: inst_str = iname # Get file directory if inst_str is None: # Location of JWST_OTE_OPD_*.fits.gz opd_dir = get_stpsf_data_path() else: opd_dir = os.path.join(get_stpsf_data_path(),inst_str,'OPD') opd_fullpath = os.path.join(opd_dir, opd_file) # Check if file exists # .fits or .fits.gz? if not os.path.exists(opd_fullpath): if '.gz' in opd_file: opd_file_alt = opd_file[:-3] else: opd_file_alt = opd_file + '.gz' opd_path_alt = os.path.join(opd_dir, opd_file_alt) if not os.path.exists(opd_path_alt): err_msg = f'Cannot find either {opd_file} or {opd_file_alt} in {opd_dir}' raise OSError(err_msg) else: opd_file = opd_file_alt return opd_file
[docs] def get_one_siaf(filename=None, instrument='NIRCam'): """ Convenience function to import a SIAF XML file to override the default pysiaf aperture information. Parameters ========== filename : str or None Name of SIAF file (e.g., 'NIRCam_SIAF.xml'). If not set, returns default SIAF object. instrument : str Name of instrument associated with XML file. """ si_match = { 'NIRCAM' : siaf_nrc, 'NIRSPEC': siaf_nis, 'MIRI' : siaf_mir, 'NIRISS' : siaf_nrs, 'FGS' : siaf_fgs, } siaf_object = deepcopy(si_match[instrument.upper()]) if filename is None: return siaf_object else: aperture_collection_NRC_base = pysiaf.read.read_jwst_siaf(filename=filename) siaf_object.apertures = aperture_collection_NRC_base siaf_object.description = os.path.basename(filename) siaf_object.observatory = 'JWST' return siaf_object
[docs] def get_detname(det_id, use_long=False): """Return NRC[A-B][1-4,5/LONG] for valid detector/SCA IDs Parameters ========== det_id : int or str Detector ID, either integer SCA ID or string detector name. use_long : bool For longwave detectors, return 'LONG' instead of '5' in detector name. """ # For longwave devices, do we use 'LONG' or '5'? long_str_use = 'LONG' if use_long else '5' long_str_not = '5' if use_long else 'LONG' det_dict = {481:'A1', 482:'A2', 483:'A3', 484:'A4', 485:f'A{long_str_use}', 486:'B1', 487:'B2', 488:'B3', 489:'B4', 490:f'B{long_str_use}'} scaids = det_dict.keys() detids = det_dict.values() detnames = ['NRC' + idval for idval in detids] # If already valid, then return if det_id in detnames: return det_id elif det_id in scaids: detname = 'NRC' + det_dict[det_id] elif det_id.upper() in detids: detname = 'NRC' + det_id.upper() else: detname = det_id # If NRCA5 or NRCB5, change '5' to 'LONG' detname = detname.upper() if long_str_not in detname: detname = detname.replace(long_str_not, long_str_use) # Ensure NRC is prepended if detname[0:3]!='NRC': detname = 'NRC' + detname if detname not in detnames: all_names = ', '.join(detnames) err_str = f"Invalid detector: {detname} \n\tValid names are: {all_names}" raise ValueError(err_str) return detname
[docs] def pix_ang_size(ap=None, sr=True, pixscale=None): """Angular area of pixel from aperture object If `sr=True` then return in sterradians, otherwise return in units of arcsec^2. Parameters ========== ap : pysiaf.Aperture Aperture object sr : bool Return in steradians? Default True. pixscale : float, array-like, or None Pixel scale in arcsec/pixel. If None, then use `ap.XSciScale` and `ap.YSciScale` to determine pixel scale. If `pixscale` is array-like, then assume (xscale, yscale). """ sr2asec2 = 42545170296.1522 # X and Y scale in arcsec / pixel if pixscale is not None: if isinstance(pixscale, (np.ndarray, list, tuple)): xscale, yscale = pixscale else: xscale = yscale = pixscale else: if ap is None: raise ValueError("Must specify either `ap` or `pixscale`.") xscale = ap.XSciScale yscale = ap.YSciScale # Area in sq arcsec area_asec2 = xscale * yscale if sr: # Convert to sterradian return area_asec2 / sr2asec2 else: return area_asec2
[docs] def load_plt_style(style='webbpsf_ext.wext_style'): """ Load the matplotlib style for spaceKLIP plots. Load the style sheet in `sk_style.mplstyle`, which is a modified version of the style sheet from the `webbpsf_ext` package. """ plt.style.use(style)