from itertools import chain
import logging
import datetime
from astropysics import obstools
import numpy as np
from fitskeyword import FITSKeyword
logger = logging.getLogger(__name__)
__all__ = ['FederSite', 'ImageSoftware', 'Instrument', 'ApogeeAltaU9',
'MaximDL4', 'MaximDL5']
[docs]class FederSite(obstools.Site):
"""
The Feder Observatory site.
An astropysics site with the observatory location and name pre-set to:
+ `lat` = 46.86678 degrees North
+ `long` = -96.453278 degrees East
+ `alt` = 311.8 meters
+ `name` = Feder Observatory
"""
def __init__(self):
obstools.Site.__init__(self,
lat=46.86678,
long=-96.453278,
alt=311.8,
name='Feder Observatory')
[docs] def localSiderialTime(self, seconds_decimal=None, *arg, **kwd):
try:
return_type = kwd['returntype']
except KeyError:
return_type = 'hours'
if return_type is None or return_type == 'hours':
return super(FederSite, self).localSiderialTime(*arg,
**kwd)
return_type = kwd.pop('returntype', None)
lst = super(FederSite, self).localSiderialTime(*arg,
returntype='datetime',
**kwd)
if seconds_decimal is not None:
seconds = np.round(lst.second + lst.microsecond / 1e6,
seconds_decimal)
sec = np.int(seconds)
microsec = np.int(np.round((seconds - sec) * 1e6))
lst = datetime.time(lst.hour, lst.minute, sec, microsec)
if return_type == 'string':
lst = lst.isoformat()
return lst
[docs]class Instrument(object):
"""
Telescope instrument with simple properties.
Parameters
----------
name : str
Name of the instrument.
fits_names : list of str
List of names by which the instrument is known in FITS headers
rows : int
Number of rows in an image produced by this instrument, including
overscan.
columns : int
Number of columns in an image produced by this instrument, including
overscan.
overscan_start : int
Position at which the overscan starts. The overscan region is assumed
to extend from this starting position to the edge of the image.
overscan_axis : one of (1, 2)
Axis along which the overscan varies. Numbers correspond to ``NAXIS1``
and ``NAXIS2`` in the FITS header.
Examples
--------
Consider an image whose dimensions as given in its FITS header are
``NAXIS1 = 3085`` and ``NAXIS2 = 2048`` with an overscan region that
begins at position 3073 along axis 1. The correct overscan settings for
this instrument are::
overscan_start = 3073
overscan_axis = 1
"""
def __init__(self, name, fits_names=None,
rows=0, columns=0,
overscan_start=None,
overscan_axis=None):
self.name = name
self.fits_names = fits_names
self.rows = rows
self.columns = columns
self.overscan_start = overscan_start
self.overscan_axis = overscan_axis
[docs] def has_overscan(self, image_dimensions):
"""
Determine whether an image taken by this instrument has overscan
Parameters
----------
image_dimensions : list-like with two elements
Shape of the image; can be any type as long as it has two elements.
Returns
-------
bool
Indicates whether or not image has overscan present.
"""
if (image_dimensions[self.overscan_axis - 1] >
self.overscan_start):
return True
else:
return False
[docs]class ApogeeAltaU9(Instrument):
"""
The Apogee Alta U9
"""
def __init__(self):
Instrument.__init__(self, "Apogee Alta U9",
fits_names=["Apogee Alta", "Apogee USB/Net"],
rows=2048, columns=3085,
overscan_start=3073,
overscan_axis=1)
[docs]class ImageSoftware(object):
"""
Represents software that takes images at telescope.
Parameters
----------
name : str
Name of the software. Can be the same is the name in the FITS file,
or not.
fits_keyword : str
Name of the FITS keyword that contains the name of the software.
fits_name : list of str
Name of the software as written in the FITS file
major_version : int
Major version number of the software.
minor_version : int
Minor version number of the software.
bad_keywords : list of strings
Names of any keywords that should be removed from the FITS before
further processing.
purged_flag_keyword : str, optional
Name of the keyword which indicates whether bad keywords have already
been purged. Default value is 'PURGED'
"""
def __init__(self, name, fits_name=None,
major_version=None,
minor_version=None,
bad_keywords=None,
fits_keyword=None,
purged_flag_keyword=None):
self.name = name
self.fits_name = fits_name
self.major_version = major_version
self.minor_version = minor_version
self.bad_keywords = bad_keywords
self.fits_keyword = fits_keyword
self.purged_flag_keyword = purged_flag_keyword or "PURGED"
[docs] def created_this(self, version_string):
"""
Indicate whether version string matches this software
Parameters
----------
version_string : str
String from FITS header that indicates software version.
Returns
-------
bool
``True`` if the version string matches the software instance.
"""
return version_string in self.fits_name
[docs]class MaximDL4(ImageSoftware):
"""
Represents MaximDL version 4, all sub-versions
"""
def __init__(self):
fits_name = ['MaxIm DL Version 4.10']
super(MaximDL4, self).__init__("MaxImDL",
fits_name=fits_name,
major_version=4,
minor_version=10,
bad_keywords=['OBSERVER'],
fits_keyword='SWCREATE')
[docs]class MaximDL5(ImageSoftware):
"""
Represents MaximDL version 5, all sub-versions.
Subversions are included by listing the FITS names of all versions that
have been used at Feder Observatory.
"""
def __init__(self):
bad_keys = ['OBJECT', 'JD', 'JD-HELIO', 'OBJCTALT', 'OBJCTAZ',
'OBJCTHA', 'AIRMASS', 'OBSERVER']
fits_name = ['MaxIm DL Version 5.21 130912 01A17',
'MaxIm DL Version 5.21 120829 2R1M0',
'MaxIm DL Version 5.23 130912 01A17']
super(MaximDL5, self).__init__("MaxImDL",
fits_name=fits_name,
major_version=5,
minor_version=21,
bad_keywords=bad_keys,
fits_keyword='SWCREATE'
)
class SBIGCCDOps(ImageSoftware):
"""
Represents software used to create images from the SBIG spectrometer.
"""
def __init__(self):
bad_keys = []
fits_name = ['SBIG Win CCDOPS Version 5.47 Build 6-NT']
super(SBIGCCDOps, self).__init__("SBIG CCDOps",
fits_name=fits_name,
major_version=5,
minor_version=47,
bad_keywords=[],
fits_keyword='SWCREATE')
class Feder(object):
"""
Class encapsulating site, instrument, and software information for Feder
Observatory.
Attributes
----------
site : feder.FederSite instance
instrument : dict
Instruments available; key is name, value is an :py:class:`Instrument`
software : dict
Software available; key is name, value is an :class:`ImageSoftware`
software_FITS_keywords : list of str
FITS names of all software available.
keywords_for_all_files
keywords_for_light_files
keywords_for_overscan
"""
def __init__(self):
self.site = FederSite()
self._apogee_alta_u9 = ApogeeAltaU9()
self._instrument_objects = [self._apogee_alta_u9]
self.instruments = {}
for instrument in self._instrument_objects:
for name in instrument.fits_names:
self.instruments[name] = instrument
self._maximdl4 = MaximDL4()
self._maximdl5 = MaximDL5()
self._sbig_ccdops = SBIGCCDOps()
self._software_objects = [self._maximdl4, self._maximdl5, self._sbig_ccdops]
self.software = {}
self.software_FITS_keywords = []
for software in self._software_objects:
for name in software.fits_name:
self.software[name] = software
self.software_FITS_keywords.append(software.fits_keyword)
self.software_FITS_keywords = list(set(self.software_FITS_keywords))
self._keywords_for_all_files = []
self._set_site_keywords_values()
self._time_keywords_to_set()
self._keywords_for_light_files = []
self._define_keywords_for_light_files()
self._overscan_keywords = []
self._define_overscan_keywords()
for key in chain(self.keywords_for_all_files,
self.keywords_for_light_files,
self.keywords_for_overscan):
name = key.name
name = name.replace('-', '_')
setattr(self, name, key)
@property
def keywords_for_all_files(self):
"""
List of :class:`~msumastro.header_processing.fitskeyword.FITSKeyword` s
whose values need to be set for all image types.
"""
return self._keywords_for_all_files
@property
def keywords_for_light_files(self):
"""
List of :class:`~msumastro.header_processing.fitskeyword.FITSKeyword` s
whose values need to be set only for light image types.
"""
return self._keywords_for_light_files
@property
def keywords_for_overscan(self):
"""
List of :class:`~msumastro.header_processing.fitskeyword.FITSKeyword` s
related to overscan.
"""
return self._overscan_keywords
def _define_keywords_for_light_files(self):
RA = FITSKeyword(name='ra',
comment='Approximate RA at EQUINOX',
synonyms=['objctra'])
Dec = FITSKeyword(name='DEC',
comment='Approximate DEC at EQUINOX',
synonyms=['objctdec'])
target_object = FITSKeyword(name='object',
comment='Target of the observations')
hour_angle = FITSKeyword(name='ha',
comment='Hour angle')
airmass = FITSKeyword(name='airmass',
comment='Airmass (Sec(Z)) at start of observation',
synonyms=['secz'])
altitude = FITSKeyword(name='alt-obj',
comment='[degrees] Altitude of object, no refraction')
azimuth = FITSKeyword(name='az-obj',
comment='[degrees] Azimuth of object, no refraction')
self._keywords_for_light_files.append(RA)
self._keywords_for_light_files.append(Dec)
self._keywords_for_light_files.append(target_object)
self._keywords_for_light_files.append(hour_angle)
self._keywords_for_light_files.append(airmass)
self._keywords_for_light_files.append(altitude)
self._keywords_for_light_files.append(azimuth)
def _set_site_keywords_values(self):
latitude = FITSKeyword(name="latitude",
comment='[degrees] Observatory latitude',
synonyms=['sitelat'])
longitude = FITSKeyword(name='longitud',
comment='[degrees east] Observatory longitude',
synonyms='sitelong')
obs_altitude = FITSKeyword(name='altitude',
comment='[meters] Observatory altitude')
latitude.value = self.site.latitude.getDmsStr(canonical=True)
longitude.value = self.site.longitude.getDmsStr(canonical=True)
obs_altitude.value = self.site.altitude
self._keywords_for_all_files.extend([latitude, longitude,
obs_altitude])
def _time_keywords_to_set(self):
LST = FITSKeyword(name='LST',
comment='Local Sidereal Time at start of observation')
JD = FITSKeyword(name='jd-obs',
comment='Julian Date at start of observation')
MJD = FITSKeyword(name='mjd-obs',
comment='Modified Julian date at start of observation')
self._keywords_for_all_files.extend([LST, JD, MJD])
def _define_overscan_keywords(self):
overscan_present = FITSKeyword(name='oscan',
comment='True if image has overscan region')
overscan_axis = FITSKeyword(name='oscanax',
comment='Overscan axis, 1 is NAXIS1, 2 is NAXIS 2')
overscan_start = FITSKeyword(name='oscanst',
comment='Starting pixel of overscan region')
self._overscan_keywords.extend([overscan_present,
overscan_axis,
overscan_start])