Skip to content

Commit

Permalink
Add BNL TS3 translator
Browse files Browse the repository at this point in the history
  • Loading branch information
timj committed Jan 29, 2019
1 parent 41ca749 commit c10b5b8
Show file tree
Hide file tree
Showing 5 changed files with 460 additions and 0 deletions.
1 change: 1 addition & 0 deletions python/lsst/obs/lsst/translators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# license that can be found in the LICENSE file.

from .phosim import *
from .ts3 import *
from .ts8 import *
from .imsim import *
from .auxTel import *
Expand Down
239 changes: 239 additions & 0 deletions python/lsst/obs/lsst/translators/ts3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# This file is currently part of obs_lsst but is written to allow it
# to be migrated to the astro_metadata_translator package at a later date.
#
# This product includes software developed by the LSST Project
# (http://www.lsst.org).
# See the LICENSE file in this directory for details of code ownership.
#
# Use of this source code is governed by a 3-clause BSD-style
# license that can be found in the LICENSE file.

"""Metadata translation code for LSST BNL TestStand 3 headers"""

__all__ = ("LsstTS3Translator", )

import logging
import re
import os.path

import astropy.units as u
from astropy.time import Time

from astro_metadata_translator import cache_translation, StubTranslator

log = logging.getLogger(__name__)

# Define the group name for TS8 globally so that it can be used
# in multiple places. There is only a single sensor so define a
# raft for consistency with other LSST cameras.
_DETECTOR_GROUP_NAME = "RXX"
_DETECTOR_NAME = "S00"


class LsstTS3Translator(StubTranslator):
"""Metadata translator for LSST BNL Test Stand 3 data.
"""

name = "LSST-TS3"
"""Name of this translation class"""

_const_map = {
# TS3 is not attached to a telescope so many translations are null.
"telescope": "LSST",
"location": None,
"boresight_rotation_coord": None,
"boresight_rotation_angle": None,
"boresight_airmass": None,
"tracking_radec": None,
"altaz_begin": None,
"object": "UNKNOWN",
"relative_humidity": None,
"temperature": None,
"pressure": None,
"detector_group": _DETECTOR_GROUP_NAME,
"detector_name": _DETECTOR_NAME, # Single sensor
"detector_num": 0,
}

_trivial_map = {
"detector_serial": "LSST_NUM",
"physical_filter": "FILTER",
"exposure_time": ("EXPTIME", dict(unit=u.s)),
}

DETECTOR_GROUP_NAME = _DETECTOR_GROUP_NAME
"""Fixed name of detector group."""

DETECTOR_NAME = _DETECTOR_NAME
"""Fixed name of single sensor."""

@classmethod
def can_translate(cls, header, filename=None):
"""Indicate whether this translation class can translate the
supplied header.
There is no usable ``INSTRUME`` header in TS3 data. Instead we use
the ``TSTAND`` header.
Parameters
----------
header : `dict`-like
Header to convert to standardized form.
filename : `str`, optional
Name of file being translated.
Returns
-------
can : `bool`
`True` if the header is recognized by this class. `False`
otherwise.
"""
return cls.can_translate_with_options(header, {"TSTAND": "BNL-TS3-2-Janeway"}, filename=filename)

@staticmethod
def compute_detector_exposure_id(exposure_id, detector_num):
"""Compute the detector exposure ID from detector number and
exposure ID.
This is a helper method to allow code working outside the translator
infrastructure to use the same algorithm.
Parameters
----------
exposure_id : `int`
Unique exposure ID.
detector_num : `int`
Detector number.
Returns
-------
detector_exposure_id : `int`
The calculated ID.
"""
if detector_num != 0:
log.warning("Unexpected non-zero detector number for TS3")
return exposure_id

@staticmethod
def compute_exposure_id(dateobs, seqnum=0):
"""Helper method to calculate the TS8 exposure_id.
Parameters
----------
dateobs : `str`
Date of observation in FITS ISO format.
seqnum : `int`, unused
Sequence number. Ignored.
Returns
-------
exposure_id : `int`
Exposure ID.
"""
# There is worry that seconds are too coarse so use 10th of second
# and read the first 21 characters.
exposure_id = re.sub(r"\D", "", dateobs[:21])
return int(exposure_id)

@cache_translation
def to_instrument(self):
"""Calculate the instrument name.
Returns
-------
instrume : `str`
Name of the test stand and manufacturer.
For example: "TS3-E2V"
"""
manu = self._header["CCD_MANU"]
self._used_these_cards("CCD_MANU")
return f"TS3-{manu}"

@cache_translation
def to_datetime_begin(self):
# Docstring will be inherited. Property defined in properties.py
self._used_these_cards("MJD-OBS")
return Time(self._header["MJD-OBS"], scale="utc", format="mjd")

@cache_translation
def to_datetime_end(self):
# Docstring will be inherited. Property defined in properties.py
return self.to_datetime_begin() + self.to_exposure_time()

@cache_translation
def to_dark_time(self):
"""Calculate the dark time.
If a DARKTIME header is not found, the value is assumed to be
identical to the exposure time.
Returns
-------
dark : `astropy.units.Quantity`
The dark time in seconds.
"""
if "DARKTIME" in self._header:
darktime = self._header("DARKTIME")*u.s
else:
log.warning("Unable to determine dark time. Setting from exposure time.")
darktime = self.to_exposure_time()
return darktime

@cache_translation
def to_detector_exposure_id(self):
# Docstring will be inherited. Property defined in properties.py
exposure_id = self.to_exposure_id()
num = self.to_detector_num()
return self.compute_detector_exposure_id(exposure_id, num)

def to_exposure_id(self):
"""Generate a unique exposure ID number
Note that SEQNUM is not unique for a given day in TS8 data
so instead we convert the ISO date of observation directly to an
integer.
Returns
-------
exposure_id : `int`
Unique exposure number.
"""
iso = self._header["DATE-OBS"]
self._used_these_cards("DATE-OBS")

return self.compute_exposure_id(iso)

# For now assume that visit IDs and exposure IDs are identical
to_visit_id = to_exposure_id

@cache_translation
def to_science_program(self):
"""Calculate the science program information.
There is no header recording this in TS3 data so instead return
the observing day in YYYY-MM-DD format.
Returns
-------
run : `str`
Observing day in YYYY-MM-DD format.
"""
# Get a copy so that we can edit the default formatting
date = self.to_datetime_begin().copy()
date.format = "iso"
date.out_subfmt = "date" # YYYY-MM-DD format
return str(date)

@cache_translation
def to_observation_id(self):
# Docstring will be inherited. Property defined in properties.py
filename = self._header["FILENAME"]
self._used_these_cards("FILENAME")
return os.path.splitext(filename)[0]

@cache_translation
def to_observation_type(self):
# Docstring will be inherited. Property defined in properties.py
obstype = self._header["IMGTYPE"]
self._used_these_cards("IMGTYPE")
return obstype.lower()
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
!!python/object/apply:collections.OrderedDict
- - [SIMPLE, true]
- [BITPIX, 16]
- [NAXIS, 2]
- [EXTEND, true]
- [ORIGIN, BNL]
- [DATE, '2018-11-15T12:55:59.113']
- [DATE-OBS, '2018-11-15T12:55:11.149']
- [MJD-OBS, 58437.538323483896]
- [IMAGETAG, CCS]
- [TSTAND, BNL-TS3-2-Janeway]
- [INSTRUME, ArchonCCDController]
- [CONTROLL, ArchonCCDController]
- [CCD_MANU, E2V]
- [CCD_TYPE, E2V11093-12-01]
- [CCD_SERN, E2V-CCD250-411]
- [LSST_NUM, E2V-CCD250-411]
- [TESTTYPE, LAMBDA]
- [IMGTYPE, FLAT]
- [SEQNUM, 25]
- [TEMP_SET, -95.0]
- [CCDTEMP, -94.6311]
- [CCDBSS, -50.0]
- [MONDIODE, -29.45297597062094]
- [MONOWL, 1000.008]
- [PIXRATE, -1.0]
- [FILTER, 550CutOn]
- [FILTPOS, 3]
- [EXPTIME, 44.631]
- [CTLRCFG, CCD250_20161109_RCLR4_e2v.acf]
- [FILENAME, E2V-CCD250-411_lambda_flat_1000_025_20181115075559.fits]
- [DETSIZE, '[1:4096,1:4004]']
- [BINX, 1]
- [BINY, 1]
- [HEADVER, 1]
- [CCDGAIN, 3.0]
- [CCDNOISE, 1.0]
- [CFGFILE, CCD250_20161109_RCLR4_e2v.acf]
- [CTLRID, '30584592850333780']
- [CTLRRV, 3]
- [CTLRTYP, 1]
- [CTLRVER, 1.0.762]
- [MONOCH-WAVELENG, 1000.008]
- [MONOCH-SLIT_A, 210.0]
- [MONOCH-SLIT_B, 210.0]
- [MONOCH-SLIT_C, 0.0]
- [MONOCH-BANDPASS, 0.0]
- [MONOCH-FILT_1, 3.0]
- [MONOCH-MSTEPS, 1200.0]
- [MONOCH-GRATING, 1.0]
- [AMP0-IDN, 'KEITHLEY-INSTRUMENTS-INC.,MODEL-6487']
- [AMP0-AZERO, F]
- [AMP0-COUNT, 2048]
- [AMP0-MEAS_NPLC, 1]
- [AMP2-IDN, 'KEITHLEY-INSTRUMENTS-INC.,MODEL-6487']
- [AMP2-VOLTAGE, -50.0]
- [AMP2-CURRENT, -37607.009999999995]
- [AMP2-ZERO_CHECK, 'off']
- [CHECKSUM, 6SI36PF26PF26PF2]
- [DATASUM, '3400510793']
- [XTENSION, IMAGE]
- [NAXIS1, 572]
- [NAXIS2, 2048]
- [PCOUNT, 0]
- [GCOUNT, 1]
- [CHANNEL, 1]
- [EXTNAME, Segment10]
- [DATASEC, '[11:522,1:2002]']
- [DETSEC, '[512:1,4004:2003]']
- [BIASSEC, '[523:572,1:2002]']
- [CCDSUM, 1 1]
- [AVERAGE, 8421.654815048383]
- [AVGBIAS, 1473.132128338105]
- [STDVBIAS, 2.8196067949998382]
- [STDEV, 465.74800104878386]
- [LTV1, 512.0]
- [LTV2, 4004.0]
- [LTM1_1, -1.0]
- [LTM2_2, -1.0]
- [WCSNAME, PRIMARY]
- [CTYPE1, CCD_X]
- [CTYPE2, CCD_Y]
- [CD1_1, -1.0]
- [CD1_2, 0.0]
- [CD2_1, 0.0]
- [CD2_2, -1.0]
- [CRPIX1, 573.0]
- [CRPIX2, 4097.0]
- [BSCALE, 1.0]
- [BZERO, 32768]

0 comments on commit c10b5b8

Please sign in to comment.