lsst.obs.base  18.1.0-28-g32d0971
instrument.py
Go to the documentation of this file.
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 # (http://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/>.
21 
22 __all__ = ("Instrument", "makeExposureRecordFromObsInfo", "makeVisitRecordFromObsInfo",
23  "addUnboundedCalibrationLabel")
24 
25 import os.path
26 from datetime import datetime
27 from abc import ABCMeta, abstractmethod
28 
29 
30 class Instrument(metaclass=ABCMeta):
31  """Base class for instrument-specific logic for the Gen3 Butler.
32 
33  Concrete instrument subclasses should be directly constructable with no
34  arguments.
35  """
36 
37  configPaths = []
38  """Paths to config files to read for specific Tasks.
39 
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  """
43 
44  @property
45  @abstractmethod
46  def filterDefinitions(self):
47  """`~lsst.obs.base.FilterDefinitionCollection`, defining the filters
48  for this instrument.
49  """
50  return None
51 
52  def __init__(self, *args, **kwargs):
53  self.filterDefinitions.defineFilters()
54 
55  @classmethod
56  @abstractmethod
57  def getName(cls):
58  raise NotImplementedError()
59 
60  @abstractmethod
61  def getCamera(self):
62  """Retrieve the cameraGeom representation of this instrument.
63 
64  This is a temporary API that should go away once obs_ packages have
65  a standardized approach to writing versioned cameras to a Gen3 repo.
66  """
67  raise NotImplementedError()
68 
69  @abstractmethod
70  def register(self, registry):
71  """Insert instrument, physical_filter, and detector entries into a
72  `Registry`.
73  """
74  raise NotImplementedError()
75 
76  def _registerFilters(self, registry):
77  """Register the physical and abstract filter Dimension relationships.
78  This should be called in the ``register`` implementation.
79 
80  Parameters
81  ----------
82  registry : `lsst.daf.butler.core.Registry`
83  The registry to add dimensions to.
84  """
85  registry.insertDimensionData(
86  "physical_filter",
87  *[
88  {
89  "instrument": self.getName(),
90  "name": filter.physical_filter,
91  "abstract_filter": filter.abstract_filter,
92  }
93  for filter in self.filterDefinitions
94  ]
95  )
96 
97  @abstractmethod
98  def getRawFormatter(self, dataId):
99  """Return the Formatter class that should be used to read a particular
100  raw file.
101 
102  Parameters
103  ----------
104  dataId : `DataCoordinate`
105  Dimension-based ID for the raw file or files being ingested.
106 
107  Returns
108  -------
109  formatter : `Formatter` class
110  Class to be used that reads the file into an
111  `lsst.afw.image.Exposure` instance.
112  """
113  raise NotImplementedError()
114 
115  @abstractmethod
116  def writeCuratedCalibrations(self, butler):
117  """Write human-curated calibration Datasets to the given Butler with
118  the appropriate validity ranges.
119 
120  This is a temporary API that should go away once obs_ packages have
121  a standardized approach to this problem.
122  """
123  raise NotImplementedError()
124 
125  def applyConfigOverrides(self, name, config):
126  """Apply instrument-specific overrides for a task config.
127 
128  Parameters
129  ----------
130  name : `str`
131  Name of the object being configured; typically the _DefaultName
132  of a Task.
133  config : `lsst.pex.config.Config`
134  Config instance to which overrides should be applied.
135  """
136  for root in self.configPaths:
137  path = os.path.join(root, f"{name}.py")
138  if os.path.exists(path):
139  config.load(path)
140 
141 
142 def makeExposureRecordFromObsInfo(obsInfo, universe):
143  """Construct an exposure DimensionRecord from
144  `astro_metadata_translator.ObservationInfo`.
145 
146  Parameters
147  ----------
148  obsInfo : `astro_metadata_translator.ObservationInfo`
149  A `~astro_metadata_translator.ObservationInfo` object corresponding to
150  the exposure.
151  universe : `DimensionUniverse`
152  Set of all known dimensions.
153 
154  Returns
155  -------
156  record : `DimensionRecord`
157  A record containing exposure metadata, suitable for insertion into
158  a `Registry`.
159  """
160  dimension = universe["exposure"]
161  return dimension.RecordClass.fromDict({
162  "instrument": obsInfo.instrument,
163  "id": obsInfo.exposure_id,
164  "name": obsInfo.observation_id,
165  "datetime_begin": obsInfo.datetime_begin.to_datetime(),
166  "datetime_end": obsInfo.datetime_end.to_datetime(),
167  "exposure_time": obsInfo.exposure_time.to_value("s"),
168  "dark_time": obsInfo.dark_time.to_value("s"),
169  "observation_type": obsInfo.observation_type,
170  "physical_filter": obsInfo.physical_filter,
171  "visit": obsInfo.visit_id,
172  })
173 
174 
175 def makeVisitRecordFromObsInfo(obsInfo, universe, *, region=None):
176  """Construct a visit `DimensionRecord` from
177  `astro_metadata_translator.ObservationInfo`.
178 
179  Parameters
180  ----------
181  obsInfo : `astro_metadata_translator.ObservationInfo`
182  A `~astro_metadata_translator.ObservationInfo` object corresponding to
183  the exposure.
184  universe : `DimensionUniverse`
185  Set of all known dimensions.
186  region : `lsst.sphgeom.Region`, optional
187  Spatial region for the visit.
188 
189  Returns
190  -------
191  record : `DimensionRecord`
192  A record containing visit metadata, suitable for insertion into a
193  `Registry`.
194  """
195  dimension = universe["visit"]
196  return dimension.RecordClass.fromDict({
197  "instrument": obsInfo.instrument,
198  "id": obsInfo.visit_id,
199  "name": obsInfo.observation_id,
200  "datetime_begin": obsInfo.datetime_begin.to_datetime(),
201  "datetime_end": obsInfo.datetime_end.to_datetime(),
202  "exposure_time": obsInfo.exposure_time.to_value("s"),
203  "physical_filter": obsInfo.physical_filter,
204  "region": region,
205  })
206 
207 
208 def addUnboundedCalibrationLabel(registry, instrumentName):
209  """Add a special 'unbounded' calibration_label dimension entry for the
210  given camera that is valid for any exposure.
211 
212  If such an entry already exists, this function just returns a `DataId`
213  for the existing entry.
214 
215  Parameters
216  ----------
217  registry : `Registry`
218  Registry object in which to insert the dimension entry.
219  instrumentName : `str`
220  Name of the instrument this calibration label is associated with.
221 
222  Returns
223  -------
224  dataId : `DataId`
225  New or existing data ID for the unbounded calibration.
226  """
227  d = dict(instrument=instrumentName, calibration_label="unbounded")
228  try:
229  return registry.expandDataId(d)
230  except LookupError:
231  pass
232  entry = d.copy()
233  entry["datetime_begin"] = datetime.min
234  entry["datetime_end"] = datetime.max
235  registry.insertDimensionData("calibration_label", entry)
236  return registry.expandDataId(d)
def applyConfigOverrides(self, name, config)
Definition: instrument.py:125
def makeExposureRecordFromObsInfo(obsInfo, universe)
Definition: instrument.py:142
def getRawFormatter(self, dataId)
Definition: instrument.py:98
def makeVisitRecordFromObsInfo(obsInfo, universe, region=None)
Definition: instrument.py:175
def __init__(self, args, kwargs)
Definition: instrument.py:52
def register(self, registry)
Definition: instrument.py:70
def addUnboundedCalibrationLabel(registry, instrumentName)
Definition: instrument.py:208
def writeCuratedCalibrations(self, butler)
Definition: instrument.py:116