import os
import json
import numpy as np
import pandeia.engine.instrument_factory as pif
from synphot import SourceSpectrum, SpectralElement
from synphot.models import Empirical1D
import astropy.units as u
from astropy.io import fits
#Load configuration files for NIRCam and MIRI to extract details from.
nircam_config_file = os.path.join(os.environ.get("pandeia_refdata"), 'jwst/nircam/config.json')
miri_config_file = os.path.join(os.environ.get("pandeia_refdata"), 'jwst/miri/config.json')
try:
with open(nircam_config_file, 'r') as f:
nircam_config_dict = json.load(f)
except IOError:
raise IOError("Couldn't locate Pandeia reference files and/or pandeia_data/jwst/nircam/config.json file.")
try:
with open(miri_config_file, 'r') as f:
miri_config_dict = json.load(f)
except IOError:
raise IOError("Couldn't locate Pandeia reference files and/or pandeia_data/jwst/miri/config.json file.")
nircam_filters = [x for x in nircam_config_dict['filters']]
miri_filters = [x for x in miri_config_dict['filters']]
nircam_coro_filters = [x for x in nircam_filters if x != 'wlp4' and (int(x[1:3]) > 17 or (int(x[1]) < 2 and x[-1] == 'n'))]
miri_coro_filters = [x for x in miri_filters if 'c' in x]
nircam_supported_subarrays = [x for x in nircam_config_dict['subarrays'] if not any(y in x for y in ['grism', 'output', 'x', 'tats'])]
miri_supported_subarrays = [x for x in miri_config_dict['subarrays'] if x != 'slitlessprism']
nircam_readout_patterns = [x for x in nircam_config_dict['readout_patterns']]
miri_readout_patterns = [x for x in miri_config_dict['readout_patterns']]
[docs]def determine_instrument(filt):
'''
Determine instrument from the input filter, only compatible with NIRCam / MIRI.
Parameters
----------
filt : str
JWST filter name
Returns
-------
instrument : str
Pandeia compatible NIRCam or MIRI string
'''
if filt in nircam_filters:
instrument = 'nircam'
elif filt in miri_filters:
instrument = 'miri'
return instrument
[docs]def determine_detector(filt):
'''
Determine detector from the input filter, only compatible with NIRCam / MIRI.
Parameters
----------
filt : str
JWST filter name
Returns
-------
detector : str
Pandeia compatible string
'''
if filt in nircam_filters:
if int(filt[1:3]) < 24:
detector = 'sw'
else:
detector = 'lw'
elif filt in miri_filters:
detector = 'imager'
return detector
[docs]def determine_aperture(filt, nircam_aperture, mode):
'''
Determine aperture (coronagraph) from filter and instrument mode
Parameters
----------
filt : str
JWST filter name
nircam_aperture : str
Direct input for NIRCam aperture, or 'default' to automatically assume based on the filter.
mode : str
Observing mode, 'imaging' or 'coronagraphy'
Returns
-------
aperture : str
Pandeia compatible aperture string
'''
if 'imaging' in mode:
if filt in nircam_filters:
if int(filt[1:3]) < 24:
aperture = 'sw'
else:
aperture = 'lw'
elif filt in miri_filters:
aperture = 'imager'
elif mode == 'coronagraphy':
if filt in nircam_coro_filters:
if nircam_aperture != 'default':
if int(filt[1:3]) < 24 and nircam_aperture[-2:] != 'sw':
aperture = nircam_aperture + 'sw'
elif int(filt[1:3]) > 24 and nircam_aperture[-2:] != 'lw':
aperture = nircam_aperture + 'lw'
else:
aperture = nircam_aperture
else:
if int(filt[1:3]) < 24:
#Short wavelength
aperture = 'mask210rsw'
elif filt == 'f277w':
#This filter can only be done by the LWB
aperture = 'masklwblw'
else:
#Remaining filters
aperture = 'mask335rlw'
elif filt in miri_coro_filters:
#Each MIRI coronagraphic filter uniquely tied to a aperture
if '2300' in filt:
#Must be Lyot aperture
aperture = 'lyot2300'
else:
aperture = 'fqpm' + filt[1:-1]
return aperture
[docs]def determine_subarray(filt, mode, nircam_subarray, miri_subarray):
'''
Determine default subarray for filter and mode
Parameters
----------
filt : str
JWST filter name
mode : str
Observing mode, 'imaging' or 'coronagraphy'
nircam_subarray : str
Manual input for NIRCam subarray, or 'default' to automatically determine.
miri_subarray : str
Manual input for MIRI subarray, or 'default' to automatically determine.
Returns
-------
subarray str
Pandeia compatible subarray string
'''
if filt in nircam_filters:
if nircam_subarray != 'default':
subarray = nircam_subarray
elif mode == 'coronagraphy':
if int(filt[1:3]) < 24:
subarray = 'sub640' #Short wavelength channel
else:
subarray = 'sub320' #Long wavelength channel
elif mode == 'imaging':
subarray = 'sub640' #This is just an arbitrary imaging subarrray
elif filt in miri_filters:
if miri_subarray != 'default':
subarray = miri_subarray
elif mode == 'coronagraphy':
subarray = 'mask'+filt[1:-1]
elif mode == 'imaging':
subarray = 'brightsky' #Again, just arbitrary imaging subarray
return subarray
[docs]def determine_pixel_scale(filt):
'''
Determine pixel scale in arcsec/pixel based on filter used.
Parameters
----------
filt : str
JWST filter name
Returns
-------
pixel_scale : float
Pixel scale in arcseconds / pixel
'''
instrument = determine_instrument(filt)
if instrument == 'miri':
pixel_scale = 0.11
elif instrument == 'nircam' and int(filt[1:3]) < 24:
pixel_scale = 0.0311
elif instrument == 'nircam' and int(filt[1:3]) > 24:
pixel_scale = 0.063
else:
raise ValueError('Unable to estimate pixel scale from instrument "{}" and filter "{}"'.format(instrument, filt))
return pixel_scale
[docs]def determine_exposure_time(subarray, pattern, groups, integrations):
'''
Determine exposure time (in seconds) for a given readout given the underlying subarray
and readout parameters from JWST config files.
Parameters
----------
subarray : str
Subarray string
pattern : str
multiaccum readout pattern
groups : int
Number of groups
integrations : int
Number of integrations
Returns
-------
exposure_time : float
Exposure time in seconds.
'''
if pattern in miri_readout_patterns:
#Must be MIRI
nframe = miri_config_dict['readout_pattern_config'][pattern]['nframe']
subarray_frame_time = miri_config_dict['subarray_config']['default'][subarray]['tframe']
exposure_time = subarray_frame_time * nframe * groups * integrations
elif pattern in nircam_readout_patterns:
#Must be NIRCam
subarray_frame_time = nircam_config_dict['subarray_config']['default'][subarray]['tframe']
subarray_tfffr = nircam_config_dict['subarray_config']['default'][subarray]['tfffr']
nframe = nircam_config_dict['readout_pattern_config'][pattern]['nframe']
nskip = nircam_config_dict['readout_pattern_config'][pattern]['ndrop2']
exposure_time = (subarray_tfffr * integrations) + subarray_frame_time * (integrations + integrations * ((groups - 1) * (nframe + nskip) + nframe))
else:
raise ValueError('Provided readout pattern {} not recognised.'.format(pattern))
return exposure_time
[docs]def determine_bar_offset(filt):
'''
Returns the bar offset in arcsseconds for the nircam coronagraphs
NOTE: As of Pandeia 1.6.1, I'm pretty sure these are slightly wrong.
Parameters
----------
filt : str
JWST filter name
Returns
-------
offset : float
Offset value in arcseconds relative to mask center.
'''
all_offsets = nircam_config_dict['bar_offsets']
try:
offset = all_offsets[filt.lower()]
except:
raise ValueError('Invalid filter selection: {}, could not identify bar offset'.format(filt))
return offset
[docs]def read_bandpass(bandpass):
'''
Read filter bandpass file, must be from 2MASS, WISE, or a pandeia default.
Parameters
----------
bandpass : str
String representation of the bandpass
Returns
-------
Bandpass : synphot SpectralElement()
Synphot representation of the bandpass.
'''
bandpass = bandpass.lower()
try:
if '2mass' in bandpass or 'wise' in bandpass:
#Read in PanCAKE provided 2MASS and WISE bandpasses.
with open(os.path.join(os.path.dirname(__file__), "resources", "{}.txt".format(bandpass))) as bandpass_file:
bandpass_data = np.genfromtxt(bandpass_file).transpose()
bandpass_wave = bandpass_data[0] * 1e4 #Convert from microns to angstrom
bandpass_throughput = bandpass_data[1]
#Convert data to a synphot bandpass
Bandpass = SpectralElement(Empirical1D, points=bandpass_wave, lookup_table=bandpass_throughput)
elif 'cousins' in bandpass or 'bessel' in bandpass or 'johnson' in bandpass:
Bandpass = SpectralElement.from_filter(bandpass)
else:
# #A JWST filter
inst = determine_instrument(bandpass)
if inst == 'nircam':
if int(bandpass[1:3]) < 24:
mode = 'sw_imaging'
else:
mode = 'lw_imaging'
elif inst == 'miri':
mode = 'imaging'
factory_config = {}
factory_config['instrument'] = {}
factory_config['instrument']['instrument'] = inst
factory_config['instrument']['mode'] = mode
factory_config['instrument']['filter'] = bandpass
#factory_config['instrument']['aperture'] = mask
bandpass_wave = np.arange(0.6, 27, 0.001) #Microns for use with pandeia
InstrumentConfig = pif.InstrumentFactory(config=factory_config)
bandpass_throughput = InstrumentConfig.get_total_eff(bandpass_wave)
bandpass_wave *= 1e4 #Convert to angstrom for synphot
Bandpass = SpectralElement(Empirical1D, points=bandpass_wave, lookup_table=bandpass_throughput)
except:
raise ValueError('Input normalisation bandpass not recognised. Currently supported filters are: "2mass_j", "2mass_h", "2mass_ks", "wise_w1", "wise_w2", "wise_w3", "wise_w4", "bessel_j", "bessel_h", "bessel_k", "cousins_r", "cousins_i", "johnson_u", "johnson_b", "johnson_v", "johnson_r", "johnson_i", "johnson_j", "johnson_k", {}, {}.'.format(', '.join(nircam_filters), ', '.join(miri_filters)))
return Bandpass
[docs]def read_coronagraph_transmission(mask):
'''
Read 2D coronagraphic transmission from resources folder.
Parameters
----------
mask : str
String representation of the coronagraphic mask
Returns
-------
transmission : 2D ndarray
2D representation of the coronagraphic transmission.
'''
if 'fqpm' in mask.lower():
mask_file = os.path.join(os.path.dirname(__file__), "resources", "{}_2DTRANS.txt".format(mask.upper()))
else:
string = 'jwst_nircam_psfmask_{}.fits'
mask_file = os.path.join(os.path.dirname(__file__), "resources", string.format(mask.lower()))
try:
if mask[4:] in ['1065', '1140', '1550']:
transmission = np.loadtxt(mask_file)
else:
with fits.open(mask_file) as hdul:
transmission = hdul[1].data
except:
raise ValueError('Input coronagraphic mask "{}" not recognised. Currently supported masks are: "MASKSWB", "MASKLWB", "MASK210R", "MASK335R", "MASK430R", "FQPM1065", "FQPM1140", "FQPM1550"'.format(mask))
return transmission