Coverage for python/lsst/obs/lsst/translators/lsstCam.py: 37%
53 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-17 03:04 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-17 03:04 -0700
1# This file is currently part of obs_lsst but is written to allow it
2# to be migrated to the astro_metadata_translator package at a later date.
3#
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the LICENSE file in this directory for details of code ownership.
7#
8# Use of this source code is governed by a 3-clause BSD-style
9# license that can be found in the LICENSE file.
11"""Metadata translation code for the main LSST Camera"""
13__all__ = ("LsstCamTranslator", )
15import logging
16import astropy.units as u
18from astro_metadata_translator import cache_translation
19from astro_metadata_translator.translators.helpers import is_non_science
21from .lsst import LsstBaseTranslator, SIMONYI_TELESCOPE, FILTER_DELIMITER
23log = logging.getLogger(__name__)
25# Normalized name of the LSST Camera
26LSST_CAM = "LSSTCam"
29def is_non_science_or_lab(self):
30 """Pseudo method to determine whether this is a lab or non-science
31 header.
33 Raises
34 ------
35 KeyError
36 If this is a science observation and on the mountain.
37 """
38 if is_non_science(self):
39 return
40 if not self._is_on_mountain():
41 return
42 raise KeyError(f"{self._log_prefix}: Required key is missing and this is a mountain science observation")
45class LsstCamTranslator(LsstBaseTranslator):
46 """Metadata translation for the main LSST Camera."""
48 name = LSST_CAM
49 """Name of this translation class"""
51 supported_instrument = LSST_CAM
52 """Supports the lsstCam instrument."""
54 _const_map = {
55 "instrument": LSST_CAM,
56 "telescope": SIMONYI_TELESCOPE,
57 # Migrate these to full translations once test data appears that
58 # includes them
59 "altaz_begin": None,
60 "object": "UNKNOWN",
61 "relative_humidity": None,
62 "temperature": None,
63 "pressure": None,
64 }
66 _trivial_map = {
67 "detector_group": "RAFTBAY",
68 "detector_name": "CCDSLOT",
69 "observation_id": "OBSID",
70 "exposure_time": ("EXPTIME", dict(unit=u.s)),
71 "detector_serial": "LSST_NUM",
72 "science_program": (["PROGRAM", "RUNNUM"], dict(default="unknown")),
73 "boresight_rotation_angle": (["ROTPA", "ROTANGLE"], dict(checker=is_non_science_or_lab,
74 default=float("nan"), unit=u.deg)),
75 }
77 # Use Imsim raft definitions until a true lsstCam definition exists
78 cameraPolicyFile = "policy/lsstCam.yaml"
80 @classmethod
81 def fix_header(cls, header, instrument, obsid, filename=None):
82 """Fix LSSTCam headers.
84 Notes
85 -----
86 See `~astro_metadata_translator.fix_header` for details of the general
87 process.
88 """
90 modified = False
92 # Calculate the standard label to use for log messages
93 log_label = cls._construct_log_prefix(obsid, filename)
95 if "FILTER" not in header and header.get("FILTER2") is not None:
96 ccdslot = header.get("CCDSLOT", "unknown")
97 raftbay = header.get("RAFTBAY", "unknown")
99 log.warning("%s %s_%s: No FILTER key found but FILTER2=\"%s\" (removed)",
100 log_label, raftbay, ccdslot, header["FILTER2"])
101 header["FILTER2"] = None
102 modified = True
104 return modified
106 @classmethod
107 def can_translate(cls, header, filename=None):
108 """Indicate whether this translation class can translate the
109 supplied header.
111 Parameters
112 ----------
113 header : `dict`-like
114 Header to convert to standardized form.
115 filename : `str`, optional
116 Name of file being translated.
118 Returns
119 -------
120 can : `bool`
121 `True` if the header is recognized by this class. `False`
122 otherwise.
123 """
124 # INSTRUME keyword might be of two types
125 if "INSTRUME" in header:
126 instrume = header["INSTRUME"].lower()
127 if instrume == cls.supported_instrument.lower():
128 return True
129 return False
131 @cache_translation
132 def to_physical_filter(self):
133 """Calculate the physical filter name.
135 Returns
136 -------
137 filter : `str`
138 Name of filter. Can be a combination of FILTER and FILTER2
139 headers joined by a "~" if FILTER2 is set and not empty.
140 Returns "UNKNOWN" if no filter is declared.
141 """
142 physical_filter = self._determine_primary_filter()
144 filter2 = None
145 if self.is_key_ok("FILTER2"):
146 self._used_these_cards("FILTER2")
147 filter2 = self._header["FILTER2"]
148 if self._is_filter_empty(filter2):
149 filter2 = None
151 if filter2:
152 physical_filter = f"{physical_filter}{FILTER_DELIMITER}{filter2}"
154 return physical_filter