"""
Module for Magellan/FIRE specific methods.
Important Notes:
- If you are reducing old FIRE data (before the broken happened
in 2016), please change the ord_spat_pos array (see lines from
~220 to ~230)
.. include:: ../include/links.rst
"""
import numpy as np
from pypeit import msgs
from pypeit import telescopes
from pypeit.core import framematch
from pypeit.spectrographs import spectrograph
from pypeit.images import detector_container
[docs]
class MagellanFIRESpectrograph(spectrograph.Spectrograph):
"""
Child to handle Magellan/FIRE specific code
.. note::
For FIRE Echelle, we usually use high gain and SUTR read mode.
The exposure time is usually around 900s. The detector
parameters below are based on such mode. Standard star and
calibrations are usually use Fowler 1 read mode in which case
the read noise is ~20 electron.
"""
ndet = 1
telescope = telescopes.MagellanTelescopePar()
camera = 'FIRE'
url = 'http://web.mit.edu/~rsimcoe/www/FIRE/index.html'
header_name = 'FIRE'
[docs]
def pypeit_file_keys(self):
"""
Define the list of keys to be output into a standard PypeIt file.
Returns:
:obj:`list`: The list of keywords in the relevant
:class:`~pypeit.metadata.PypeItMetaData` instance to print to the
:ref:`pypeit_file`.
"""
pypeit_keys = super().pypeit_file_keys()
# TODO: Why are these added here? See
# pypeit.metadata.PypeItMetaData.set_pypeit_cols
pypeit_keys += ['calib', 'comb_id', 'bkg_id']
return pypeit_keys
[docs]
class MagellanFIREEchelleSpectrograph(MagellanFIRESpectrograph):
"""
Child to handle Magellan/FIRE Echelle data
.. note::
For FIRE Echelle, we usually use high gain and SUTR read mode.
The exposure time is usually around 900s. The detector
parameters below are based on such mode. Standard star and
calibrations are usually use Fowler 1 read mode in which case
the read noise is ~20 electron.
"""
name = 'magellan_fire'
pypeline = 'Echelle'
ech_fixed_format = True
supported = True
comment = 'Magellan/FIRE in echelle mode'
[docs]
def get_detector_par(self, det, hdu=None):
"""
Return metadata for the selected detector.
Args:
det (:obj:`int`):
1-indexed detector number.
hdu (`astropy.io.fits.HDUList`_, optional):
The open fits file with the raw image of interest. If not
provided, frame-dependent parameters are set to a default.
Returns:
:class:`~pypeit.images.detector_container.DetectorContainer`:
Object with the detector metadata.
"""
# Detector 1
detector_dict = dict(
binning = '1,1',
det = 1,
dataext = 0,
specaxis = 1,
specflip = True,
spatflip = False,
platescale = 0.18,
darkcurr = 3.06, # e-/pixel/hour (=0.00085 e-/pixel/s)
#saturation = 20000., # high gain is 20000 ADU, low gain is 32000 ADU
saturation = 100000., # This is an arbitrary value.
nonlinear = 1.0, # high gain mode, low gain is 0.875
mincounts = -1e10,
numamplifiers = 1,
gain = np.atleast_1d(1.2), # high gain mode, low gain is 3.8 e-/DN
ronoise = np.atleast_1d(5.0), # for high gain mode and SUTR read modes with exptime ~ 900s
datasec = np.atleast_1d('[5:2044,5:2044]'),
oscansec = np.atleast_1d('[5:2044,:5]')
)
return detector_container.DetectorContainer(**detector_dict)
[docs]
@classmethod
def default_pypeit_par(cls):
"""
Return the default parameters to use for this instrument.
Returns:
:class:`~pypeit.par.pypeitpar.PypeItPar`: Parameters required by
all of PypeIt methods.
"""
par = super().default_pypeit_par()
# Wavelengths
# 1D wavelength solution with OH lines
par['calibrations']['wavelengths']['rms_thresh_frac_fwhm'] = 0.25
par['calibrations']['wavelengths']['sigdetect']=[5,5,10,10,10,20,30,30,30,30,30,10,30,30,60,30,30,10,20,30,10]
par['calibrations']['wavelengths']['n_first']=2
par['calibrations']['wavelengths']['n_final']=[3,2,3,2,4,4,4,3,4,4,4,3,4,4,4,4,4,4,6,6,4]
par['calibrations']['wavelengths']['lamps'] = ['OH_FIRE_Echelle']
#par['calibrations']['wavelengths']['nonlinear_counts'] = self.detector[0]['nonlinear'] * self.detector[0]['saturation']
par['calibrations']['wavelengths']['method'] = 'reidentify'
par['calibrations']['wavelengths']['cc_thresh'] = 0.35
par['calibrations']['wavelengths']['reid_arxiv'] = 'magellan_fire_echelle.fits'
# par['calibrations']['wavelengths']['match_toler']=30.0
# Echelle parameters
par['calibrations']['wavelengths']['echelle'] = True
# par['calibrations']['wavelengths']['ech_fix_format'] = True
par['calibrations']['wavelengths']['ech_nspec_coeff'] = 4
par['calibrations']['wavelengths']['ech_norder_coeff'] = 6
par['calibrations']['wavelengths']['ech_sigrej'] = 3.0
# Always correct for flexure, starting with default parameters
par['scienceframe']['process']['sigclip'] = 20.0
par['scienceframe']['process']['satpix'] ='nothing'
# Set slits and tilts parameters
par['calibrations']['tilts']['tracethresh'] = 5
par['calibrations']['slitedges']['edge_thresh'] = 3.
par['calibrations']['slitedges']['trace_thresh'] = 10.
par['calibrations']['slitedges']['fit_order'] = 5
par['calibrations']['slitedges']['max_shift_adj'] = 0.5
par['calibrations']['slitedges']['fit_min_spec_length'] = 0.5
par['calibrations']['slitedges']['left_right_pca'] = True
par['calibrations']['slitedges']['pca_order'] = 3
# Model entire slit
par['reduce']['extraction']['model_full_slit'] = True # local sky subtraction operates on entire slit
par['reduce']['findobj']['maxnumber_sci'] = 2 # Slit is narrow so allow one object per order
par['reduce']['findobj']['maxnumber_std'] = 1 # Slit is narrow so allow one object per order
# Processing steps
turn_off = dict(use_illumflat=False, use_biasimage=False, use_overscan=False,
use_darkimage=False)
par.reset_all_processimages_par(**turn_off)
# Do not correct for flexure
par['flexure']['spec_method'] = 'skip'
# Set the default exposure time ranges for the frame typing
par['calibrations']['standardframe']['exprng'] = [None, 60]
par['calibrations']['arcframe']['exprng'] = [20, None]
par['calibrations']['darkframe']['exprng'] = [20, None]
par['scienceframe']['exprng'] = [20, None]
# Sensitivity function parameters
# Sensitivity function parameters
par['sensfunc']['algorithm'] = 'IR'
par['sensfunc']['polyorder'] = 5
par['sensfunc']['IR']['maxiter'] = 2
# place holder for telgrid file
par['sensfunc']['IR']['telgridfile'] = 'TellPCA_3000_26000_R15000.fits'
# Coadding. I'm not sure what this should be for PRISM mode?
par['coadd1d']['wave_method'] = 'log10'
return par
[docs]
def check_frame_type(self, ftype, fitstbl, exprng=None):
"""
Check for frames of the provided type.
Args:
ftype (:obj:`str`):
Type of frame to check. Must be a valid frame type; see
frame-type :ref:`frame_type_defs`.
fitstbl (`astropy.table.Table`_):
The table with the metadata for one or more frames to check.
exprng (:obj:`list`, optional):
Range in the allowed exposure time for a frame of type
``ftype``. See
:func:`pypeit.core.framematch.check_frame_exptime`.
Returns:
`numpy.ndarray`_: Boolean array with the flags selecting the
exposures in ``fitstbl`` that are ``ftype`` type frames.
"""
good_exp = framematch.check_frame_exptime(fitstbl['exptime'], exprng)
if ftype in ['pinhole', 'bias']:
# No pinhole or bias frames
return np.zeros(len(fitstbl), dtype=bool)
if ftype in ['pixelflat', 'trace']:
return good_exp & (fitstbl['idname'] == 'PixFlat')
if ftype == 'standard':
return good_exp & (fitstbl['idname'] == 'Telluric')
if ftype == 'science':
return good_exp & (fitstbl['idname'] == 'Science')
if ftype in ['arc', 'tilt']:
return good_exp & (fitstbl['idname'] == 'Science')
msgs.warn('Cannot determine if frames are of type {0}.'.format(ftype))
return np.zeros(len(fitstbl), dtype=bool)
@property
def norders(self):
"""
Number of orders for this spectograph. Should only defined for
echelle spectrographs, and it is undefined for the base class.
"""
return 21
@property
def order_spat_pos(self):
"""
Return the expected spatial position of each echelle order.
"""
# ToDo: We somehow need to automate this.
## For OLD data, i.e. before 2017
#ord_spat_pos = np.array([0.06054688, 0.14160156, 0.17089844, 0.22753906, 0.27539062,
# 0.32128906, 0.36474609, 0.40673828, 0.45019531, 0.48974609,
# 0.52978516, 0.56054688, 0.59814453, 0.63378906, 0.66503906,
# 0.70019531, 0.7421875 , 0.77978516, 0.82763672, 0.87109375,
# 0.9296875])
## For NEW data
ord_spat_pos = np.array([0.078125, 0.13769531, 0.19189453, 0.24414062, 0.29296875,
0.34179688, 0.38330078, 0.42724609, 0.46582031, 0.50439453,
0.54199219, 0.57763672, 0.61279297, 0.6484375 , 0.68457031,
0.71875 , 0.75439453, 0.79443359, 0.83789062, 0.88671875,
0.94091797])
return ord_spat_pos
@property
def orders(self):
"""
Return the order number for each echelle order.
"""
return np.arange(31, 10, -1, dtype=int)
@property
def spec_min_max(self):
"""
Return the minimum and maximum spectral pixel expected for the
spectral range of each order.
"""
spec_max = np.asarray([2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,
2048,2048,2048,2048,2048])
spec_min = np.asarray([ 500, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0])
return np.vstack((spec_min, spec_max))
[docs]
def order_platescale(self, order_vec, binning=None):
"""
Return the platescale for each echelle order.
Note that FIRE has no binning.
Args:
order_vec (`numpy.ndarray`_):
The vector providing the order numbers.
binning (:obj:`str`, optional):
The string defining the spectral and spatial binning. **This
is always ignored.**
Returns:
`numpy.ndarray`_: An array with the platescale for each order
provided by ``order``.
"""
return np.full(order_vec.size, 0.15)
@property
def dloglam(self):
"""
Return the logarithmic step in wavelength for output spectra.
"""
# This number was determined using the resolution and sampling quoted on the FIRE website
R = 6000.0 * 2.7
dloglam = 1.0 / R / np.log(10.0)
return dloglam
@property
def loglam_minmax(self):
"""
Return the base-10 logarithm of the first and last wavelength for
ouput spectra.
"""
return np.log10(8000.0), np.log10(25700)
[docs]
class MagellanFIRELONGSpectrograph(MagellanFIRESpectrograph):
"""
Child to handle Magellan/FIRE high-throughput data
.. note::
For FIRE longslit, science data are usually taken with SUTR readout
mode with ~600s exposure (at least for quasar hunting people) and the
readout noise is ~6 e-
"""
name = 'magellan_fire_long'
supported = True
comment = 'Magellan/FIRE in long-slit/high-throughput mode'
[docs]
def get_detector_par(self, det, hdu=None):
"""
Return metadata for the selected detector.
Args:
det (:obj:`int`):
1-indexed detector number.
hdu (`astropy.io.fits.HDUList`_, optional):
The open fits file with the raw image of interest. If not
provided, frame-dependent parameters are set to a default.
Returns:
:class:`~pypeit.images.detector_container.DetectorContainer`:
Object with the detector metadata.
"""
# Detector 1
detector_dict = dict(
binning = '1,1',
det = 1,
dataext = 0,
specaxis = 0,
specflip = False,
spatflip = False,
platescale = 0.15,
darkcurr = 3.06, # e-/pixel/hour (=0.00085 e-/pixel/s)
saturation = 320000., #32000 for low gain, I set to a higher value to keep data in K-band
nonlinear = 0.875,
mincounts = -1e10,
numamplifiers = 1,
gain = np.atleast_1d(3.8),
ronoise = np.atleast_1d(6.0), # SUTR readout mode with exposure~600s
datasec = np.atleast_1d('[5:2044, 900:1250]'),
oscansec = np.atleast_1d('[:5, 900:1250]')
)
return detector_container.DetectorContainer(**detector_dict)
[docs]
@classmethod
def default_pypeit_par(cls):
"""
Return the default parameters to use for this instrument.
Returns:
:class:`~pypeit.par.pypeitpar.PypeItPar`: Parameters required by
all of PypeIt methods.
"""
par = super().default_pypeit_par()
# Wavelengths
# 1D wavelength solution with arc lines
par['calibrations']['wavelengths']['rms_thresh_frac_fwhm'] = 0.05
par['calibrations']['wavelengths']['sigdetect']=3
par['calibrations']['wavelengths']['fwhm'] = 10
par['calibrations']['wavelengths']['n_first']=3
par['calibrations']['wavelengths']['n_final']=6
par['calibrations']['wavelengths']['nsnippet'] = 4
par['calibrations']['wavelengths']['lamps'] = ['Ar_IR_MOSFIRE', 'Ne_IR_MOSFIRE']
#par['calibrations']['wavelengths']['nonlinear_counts'] = self.detector[0]['nonlinear'] * self.detector[0]['saturation']
par['calibrations']['wavelengths']['method'] = 'full_template'
par['calibrations']['wavelengths']['reid_arxiv'] = 'magellan_fire_long.fits'
par['calibrations']['wavelengths']['match_toler']=5.0
# Set slits and tilts parameters
par['calibrations']['tilts']['tracethresh'] = 5
par['calibrations']['slitedges']['trace_thresh'] = 10.
par['calibrations']['slitedges']['sync_predict'] = 'nearest'
# Processing steps
turn_off = dict(use_illumflat=False, use_biasimage=False, use_overscan=False,
use_darkimage=False)
par.reset_all_processimages_par(**turn_off)
# Scienceimage parameters
par['reduce']['findobj']['snr_thresh'] = 5
#par['reduce']['maxnumber'] = 2
par['reduce']['findobj']['find_trim_edge'] = [50,50]
par['flexure']['spec_method'] = 'skip'
par['sensfunc']['IR']['telgridfile'] = 'TellPCA_3000_26000_R10000.fits'
# Set the default exposure time ranges for the frame typing
par['calibrations']['standardframe']['exprng'] = [None, 60]
par['calibrations']['arcframe']['exprng'] = [1, 50]
par['calibrations']['darkframe']['exprng'] = [20, None]
par['scienceframe']['exprng'] = [20, None]
return par
[docs]
def check_frame_type(self, ftype, fitstbl, exprng=None):
"""
Check for frames of the provided type.
Args:
ftype (:obj:`str`):
Type of frame to check. Must be a valid frame type; see
frame-type :ref:`frame_type_defs`.
fitstbl (`astropy.table.Table`_):
The table with the metadata for one or more frames to check.
exprng (:obj:`list`, optional):
Range in the allowed exposure time for a frame of type
``ftype``. See
:func:`pypeit.core.framematch.check_frame_exptime`.
Returns:
`numpy.ndarray`_: Boolean array with the flags selecting the
exposures in ``fitstbl`` that are ``ftype`` type frames.
"""
good_exp = framematch.check_frame_exptime(fitstbl['exptime'], exprng)
if ftype in ['pinhole', 'bias']:
# No pinhole or bias frames
return np.zeros(len(fitstbl), dtype=bool)
if ftype in ['pixelflat', 'trace']:
return good_exp & (fitstbl['idname'] == 'PixFlat')
if ftype == 'standard':
return good_exp & (fitstbl['idname'] == 'Telluric')
if ftype == 'science':
return good_exp & (fitstbl['idname'] == 'Science')
if ftype in ['arc', 'tilt']:
return good_exp & (fitstbl['idname'] == 'Arc')
msgs.warn('Cannot determine if frames are of type {0}.'.format(ftype))
return np.zeros(len(fitstbl), dtype=bool)