Coverage for python/lsst/obs/base/instrument.py : 37%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of obs_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22__all__ = ("Instrument", "makeExposureRecordFromObsInfo", "makeVisitRecordFromObsInfo",
23 "addUnboundedCalibrationLabel")
25import os.path
26from datetime import datetime
27from abc import ABCMeta, abstractmethod
30class Instrument(metaclass=ABCMeta):
31 """Base class for instrument-specific logic for the Gen3 Butler.
33 Concrete instrument subclasses should be directly constructable with no
34 arguments.
35 """
37 configPaths = []
38 """Paths to config files to read for specific Tasks.
40 The paths in this list should contain files of the form `task.py`, for
41 each of the Tasks that requires special configuration.
42 """
44 @property
45 @abstractmethod
46 def filterDefinitions(self):
47 """`~lsst.obs.base.FilterDefinitionCollection`, defining the filters
48 for this instrument.
49 """
50 return None
52 def __init__(self, *args, **kwargs):
53 self.filterDefinitions.reset()
54 self.filterDefinitions.defineFilters()
56 @classmethod
57 @abstractmethod
58 def getName(cls):
59 raise NotImplementedError()
61 @abstractmethod
62 def getCamera(self):
63 """Retrieve the cameraGeom representation of this instrument.
65 This is a temporary API that should go away once obs_ packages have
66 a standardized approach to writing versioned cameras to a Gen3 repo.
67 """
68 raise NotImplementedError()
70 @abstractmethod
71 def register(self, registry):
72 """Insert instrument, physical_filter, and detector entries into a
73 `Registry`.
74 """
75 raise NotImplementedError()
77 def _registerFilters(self, registry):
78 """Register the physical and abstract filter Dimension relationships.
79 This should be called in the ``register`` implementation.
81 Parameters
82 ----------
83 registry : `lsst.daf.butler.core.Registry`
84 The registry to add dimensions to.
85 """
86 for filter in self.filterDefinitions:
87 # fix for undefined abstract filters causing trouble in the registry:
88 if filter.abstract_filter is None:
89 abstract_filter = filter.physical_filter
90 else:
91 abstract_filter = filter.abstract_filter
93 registry.insertDimensionData("physical_filter",
94 {"instrument": self.getName(),
95 "name": filter.physical_filter,
96 "abstract_filter": abstract_filter
97 })
99 @abstractmethod
100 def getRawFormatter(self, dataId):
101 """Return the Formatter class that should be used to read a particular
102 raw file.
104 Parameters
105 ----------
106 dataId : `DataCoordinate`
107 Dimension-based ID for the raw file or files being ingested.
109 Returns
110 -------
111 formatter : `Formatter` class
112 Class to be used that reads the file into an
113 `lsst.afw.image.Exposure` instance.
114 """
115 raise NotImplementedError()
117 @abstractmethod
118 def writeCuratedCalibrations(self, butler):
119 """Write human-curated calibration Datasets to the given Butler with
120 the appropriate validity ranges.
122 This is a temporary API that should go away once obs_ packages have
123 a standardized approach to this problem.
124 """
125 raise NotImplementedError()
127 def applyConfigOverrides(self, name, config):
128 """Apply instrument-specific overrides for a task config.
130 Parameters
131 ----------
132 name : `str`
133 Name of the object being configured; typically the _DefaultName
134 of a Task.
135 config : `lsst.pex.config.Config`
136 Config instance to which overrides should be applied.
137 """
138 for root in self.configPaths:
139 path = os.path.join(root, f"{name}.py")
140 if os.path.exists(path):
141 config.load(path)
144def makeExposureRecordFromObsInfo(obsInfo, universe):
145 """Construct an exposure DimensionRecord from
146 `astro_metadata_translator.ObservationInfo`.
148 Parameters
149 ----------
150 obsInfo : `astro_metadata_translator.ObservationInfo`
151 A `~astro_metadata_translator.ObservationInfo` object corresponding to
152 the exposure.
153 universe : `DimensionUniverse`
154 Set of all known dimensions.
156 Returns
157 -------
158 record : `DimensionRecord`
159 A record containing exposure metadata, suitable for insertion into
160 a `Registry`.
161 """
162 dimension = universe["exposure"]
163 return dimension.RecordClass.fromDict({
164 "instrument": obsInfo.instrument,
165 "id": obsInfo.exposure_id,
166 "name": obsInfo.observation_id,
167 "datetime_begin": obsInfo.datetime_begin.to_datetime(),
168 "datetime_end": obsInfo.datetime_end.to_datetime(),
169 "exposure_time": obsInfo.exposure_time.to_value("s"),
170 "dark_time": obsInfo.dark_time.to_value("s"),
171 "observation_type": obsInfo.observation_type,
172 "physical_filter": obsInfo.physical_filter,
173 "visit": obsInfo.visit_id,
174 })
177def makeVisitRecordFromObsInfo(obsInfo, universe, *, region=None):
178 """Construct a visit `DimensionRecord` from
179 `astro_metadata_translator.ObservationInfo`.
181 Parameters
182 ----------
183 obsInfo : `astro_metadata_translator.ObservationInfo`
184 A `~astro_metadata_translator.ObservationInfo` object corresponding to
185 the exposure.
186 universe : `DimensionUniverse`
187 Set of all known dimensions.
188 region : `lsst.sphgeom.Region`, optional
189 Spatial region for the visit.
191 Returns
192 -------
193 record : `DimensionRecord`
194 A record containing visit metadata, suitable for insertion into a
195 `Registry`.
196 """
197 dimension = universe["visit"]
198 return dimension.RecordClass.fromDict({
199 "instrument": obsInfo.instrument,
200 "id": obsInfo.visit_id,
201 "name": obsInfo.observation_id,
202 "datetime_begin": obsInfo.datetime_begin.to_datetime(),
203 "datetime_end": obsInfo.datetime_end.to_datetime(),
204 "exposure_time": obsInfo.exposure_time.to_value("s"),
205 "physical_filter": obsInfo.physical_filter,
206 "region": region,
207 })
210def addUnboundedCalibrationLabel(registry, instrumentName):
211 """Add a special 'unbounded' calibration_label dimension entry for the
212 given camera that is valid for any exposure.
214 If such an entry already exists, this function just returns a `DataId`
215 for the existing entry.
217 Parameters
218 ----------
219 registry : `Registry`
220 Registry object in which to insert the dimension entry.
221 instrumentName : `str`
222 Name of the instrument this calibration label is associated with.
224 Returns
225 -------
226 dataId : `DataId`
227 New or existing data ID for the unbounded calibration.
228 """
229 d = dict(instrument=instrumentName, calibration_label="unbounded")
230 try:
231 return registry.expandDataId(d)
232 except LookupError:
233 pass
234 entry = d.copy()
235 entry["datetime_begin"] = datetime.min
236 entry["datetime_end"] = datetime.max
237 registry.insertDimensionData("calibration_label", entry)
238 return registry.expandDataId(d)