lsst.obs.base  20.0.0-59-gb502cbb+0e9af1ef10
_fitsRawFormatterBase.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 # (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/>.
21 
22 __all__ = ("FitsRawFormatterBase",)
23 
24 from abc import ABCMeta, abstractmethod
25 
26 from astro_metadata_translator import ObservationInfo
27 
28 import lsst.afw.fits
29 import lsst.afw.geom
30 import lsst.afw.image
31 from lsst.daf.butler import FileDescriptor
32 import lsst.log
33 
34 from .formatters.fitsExposure import FitsExposureFormatter
35 from .makeRawVisitInfoViaObsInfo import MakeRawVisitInfoViaObsInfo
36 from .utils import createInitialSkyWcsFromBoresight, InitialSkyWcsError
37 
38 
39 class FitsRawFormatterBase(FitsExposureFormatter, metaclass=ABCMeta):
40  """Abstract base class for reading and writing raw data to and from
41  FITS files.
42  """
43 
44  # This has to be explicit until we fix camera geometry in DM-20746
45  wcsFlipX = False
46  """Control whether the WCS is flipped in the X-direction (`bool`)"""
47 
48  def __init__(self, *args, **kwargs):
49  self.filterDefinitions.reset()
50  self.filterDefinitions.defineFilters()
51  super().__init__(*args, **kwargs)
52 
53  @classmethod
54  def fromMetadata(cls, metadata, obsInfo=None, storageClass=None, location=None):
55  """Construct a possibly-limited formatter from known metadata.
56 
57  Parameters
58  ----------
59  metadata : `lsst.daf.base.PropertyList`
60  Raw header metadata, with any fixes (see
61  `astro_metadata_translator.fix_header`) applied but nothing
62  stripped.
63  obsInfo : `astro_metadata_translator.ObservationInfo`, optional
64  Structured information already extracted from ``metadata``.
65  If not provided, will be read from ``metadata`` on first use.
66  storageClass : `lsst.daf.butler.StorageClass`, optional
67  StorageClass for this file. If not provided, the formatter will
68  only support `makeWcs`, `makeVisitInfo`, `makeFilter`, and other
69  operations that operate purely on metadata and not the actual file.
70  location : `lsst.daf.butler.Location`, optional.
71  Location of the file. If not provided, the formatter will only
72  support `makeWcs`, `makeVisitInfo`, `makeFilter`, and other
73  operations that operate purely on metadata and not the actual file.
74 
75  Returns
76  -------
77  formatter : `FitsRawFormatterBase`
78  An instance of ``cls``.
79  """
80  self = cls(FileDescriptor(location, storageClass))
81  self._metadata = metadata
82  self._observationInfo = obsInfo
83  return self
84 
85  @property
86  @abstractmethod
87  def translatorClass(self):
88  """`~astro_metadata_translator.MetadataTranslator` to translate
89  metadata header to `~astro_metadata_translator.ObservationInfo`.
90  """
91  return None
92 
93  _observationInfo = None
94 
95  @property
96  @abstractmethod
97  def filterDefinitions(self):
98  """`~lsst.obs.base.FilterDefinitions`, defining the filters for this
99  instrument.
100  """
101  return None
102 
103  def readImage(self):
104  """Read just the image component of the Exposure.
105 
106  Returns
107  -------
108  image : `~lsst.afw.image.Image`
109  In-memory image component.
110  """
111  return lsst.afw.image.ImageU(self.fileDescriptor.location.path)
112 
113  def readMask(self):
114  """Read just the mask component of the Exposure.
115 
116  May return None (as the default implementation does) to indicate that
117  there is no mask information to be extracted (at least not trivially)
118  from the raw data. This will prohibit direct reading of just the mask,
119  and set the mask of the full Exposure to zeros.
120 
121  Returns
122  -------
123  mask : `~lsst.afw.image.Mask`
124  In-memory mask component.
125  """
126  return None
127 
128  def readVariance(self):
129  """Read just the variance component of the Exposure.
130 
131  May return None (as the default implementation does) to indicate that
132  there is no variance information to be extracted (at least not
133  trivially) from the raw data. This will prohibit direct reading of
134  just the variance, and set the variance of the full Exposure to zeros.
135 
136  Returns
137  -------
138  image : `~lsst.afw.image.Image`
139  In-memory variance component.
140  """
141  return None
142 
143  def isOnSky(self):
144  """Boolean to determine if the exposure is thought to be on the sky.
145 
146  Returns
147  -------
148  onSky : `bool`
149  Returns `True` if the observation looks like it was taken on the
150  sky. Returns `False` if this observation looks like a calibration
151  observation.
152 
153  Notes
154  -----
155  If there is tracking RA/Dec information associated with the
156  observation it is assumed that the observation is on sky.
157  Currently the observation type is not checked.
158  """
159  if self.observationInfo.tracking_radec is None:
160  return False
161  return True
162 
163  def stripMetadata(self):
164  """Remove metadata entries that are parsed into components.
165  """
166  # NOTE: makeVisitInfo() may not strip any metadata itself, but calling
167  # it ensures that ObservationInfo is created from the metadata, which
168  # will strip the VisitInfo keys and more.
169  self.makeVisitInfo()
171 
172  def makeVisitInfo(self):
173  """Construct a VisitInfo from metadata.
174 
175  Returns
176  -------
177  visitInfo : `~lsst.afw.image.VisitInfo`
178  Structured metadata about the observation.
179  """
180  return MakeRawVisitInfoViaObsInfo.observationInfo2visitInfo(self.observationInfo)
181 
182  @abstractmethod
183  def getDetector(self, id):
184  """Return the detector that acquired this raw exposure.
185 
186  Parameters
187  ----------
188  id : `int`
189  The identifying number of the detector to get.
190 
191  Returns
192  -------
193  detector : `~lsst.afw.cameraGeom.Detector`
194  The detector associated with that ``id``.
195  """
196  raise NotImplementedError("Must be implemented by subclasses.")
197 
198  def makeWcs(self, visitInfo, detector):
199  """Create a SkyWcs from information about the exposure.
200 
201  If VisitInfo is not None, use it and the detector to create a SkyWcs,
202  otherwise return the metadata-based SkyWcs (always created, so that
203  the relevant metadata keywords are stripped).
204 
205  Parameters
206  ----------
207  visitInfo : `~lsst.afw.image.VisitInfo`
208  The information about the telescope boresight and camera
209  orientation angle for this exposure.
210  detector : `~lsst.afw.cameraGeom.Detector`
211  The detector used to acquire this exposure.
212 
213  Returns
214  -------
215  skyWcs : `~lsst.afw.geom.SkyWcs`
216  Reversible mapping from pixel coordinates to sky coordinates.
217 
218  Raises
219  ------
220  InitialSkyWcsError
221  Raised if there is an error generating the SkyWcs, chained from the
222  lower-level exception if available.
223  """
224  if not self.isOnSky():
225  # This is not an on-sky observation
226  return None
227 
228  skyWcs = self._createSkyWcsFromMetadata()
229 
230  log = lsst.log.Log.getLogger("fitsRawFormatter")
231  if visitInfo is None:
232  msg = "No VisitInfo; cannot access boresight information. Defaulting to metadata-based SkyWcs."
233  log.warn(msg)
234  if skyWcs is None:
235  raise InitialSkyWcsError("Failed to create both metadata and boresight-based SkyWcs."
236  "See warnings in log messages for details.")
237  return skyWcs
238 
239  return self.makeRawSkyWcsFromBoresight(visitInfo.getBoresightRaDec(),
240  visitInfo.getBoresightRotAngle(),
241  detector)
242 
243  @classmethod
244  def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector):
245  """Class method to make a raw sky WCS from boresight and detector.
246 
247  Parameters
248  ----------
249  boresight : `lsst.geom.SpherePoint`
250  The ICRS boresight RA/Dec
251  orientation : `lsst.geom.Angle`
252  The rotation angle of the focal plane on the sky.
253  detector : `lsst.afw.cameraGeom.Detector`
254  Where to get the camera geomtry from.
255 
256  Returns
257  -------
258  skyWcs : `~lsst.afw.geom.SkyWcs`
259  Reversible mapping from pixel coordinates to sky coordinates.
260  """
261  return createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=cls.wcsFlipX)
262 
263  def _createSkyWcsFromMetadata(self):
264  """Create a SkyWcs from the FITS header metadata in an Exposure.
265 
266  Returns
267  -------
268  skyWcs: `lsst.afw.geom.SkyWcs`, or None
269  The WCS that was created from ``self.metadata``, or None if that
270  creation fails due to invalid metadata.
271  """
272  if not self.isOnSky():
273  # This is not an on-sky observation
274  return None
275 
276  try:
277  return lsst.afw.geom.makeSkyWcs(self.metadata, strip=True)
278  except TypeError as e:
279  log = lsst.log.Log.getLogger("fitsRawFormatter")
280  log.warn("Cannot create a valid WCS from metadata: %s", e.args[0])
281  return None
282 
283  def makeFilter(self):
284  """Construct a Filter from metadata.
285 
286  Returns
287  -------
288  filter : `~lsst.afw.image.Filter`
289  Object that identifies the filter for this image.
290 
291  Raises
292  ------
293  NotFoundError
294  Raised if the physical filter was not registered via
295  `~lsst.afw.image.utils.defineFilter`.
296  """
297  return lsst.afw.image.Filter(self.observationInfo.physical_filter)
298 
299  def readComponent(self, component, parameters=None):
300  """Read a component held by the Exposure.
301 
302  Parameters
303  ----------
304  component : `str`, optional
305  Component to read from the file.
306  parameters : `dict`, optional
307  If specified, a dictionary of slicing parameters that
308  overrides those in ``fileDescriptor``.
309 
310  Returns
311  -------
312  obj : component-dependent
313  In-memory component object.
314 
315  Raises
316  ------
317  KeyError
318  Raised if the requested component cannot be handled.
319  """
320  if component == "image":
321  return self.readImage()
322  elif component == "mask":
323  return self.readMask()
324  elif component == "variance":
325  return self.readVariance()
326  elif component == "filter":
327  return self.makeFilter()
328  elif component == "visitInfo":
329  return self.makeVisitInfo()
330  elif component == "wcs":
331  detector = self.getDetector(self.observationInfo.detector_num)
332  visitInfo = self.makeVisitInfo()
333  return self.makeWcs(visitInfo, detector)
334  return None
335 
336  def readFull(self, parameters=None):
337  """Read the full Exposure object.
338 
339  Parameters
340  ----------
341  parameters : `dict`, optional
342  If specified, a dictionary of slicing parameters that overrides
343  those in the `fileDescriptor` attribute.
344 
345  Returns
346  -------
347  exposure : `~lsst.afw.image.Exposure`
348  Complete in-memory exposure.
349  """
350  from lsst.afw.image import makeExposure, makeMaskedImage
351  full = makeExposure(makeMaskedImage(self.readImage()))
352  mask = self.readMask()
353  if mask is not None:
354  full.setMask(mask)
355  variance = self.readVariance()
356  if variance is not None:
357  full.setVariance(variance)
358  full.setDetector(self.getDetector(self.observationInfo.detector_num))
359  info = full.getInfo()
360  info.setFilter(self.makeFilter())
361  info.setVisitInfo(self.makeVisitInfo())
362  info.setWcs(self.makeWcs(info.getVisitInfo(), info.getDetector()))
363  # We don't need to call stripMetadata() here because it has already
364  # been stripped during creation of the ObservationInfo, WCS, etc.
365  full.setMetadata(self.metadata)
366  return full
367 
368  def readRawHeaderWcs(self, parameters=None):
369  """Read the SkyWcs stored in the un-modified raw FITS WCS header keys.
370  """
371  return lsst.afw.geom.makeSkyWcs(lsst.afw.fits.readMetadata(self.fileDescriptor))
372 
373  def write(self, inMemoryDataset):
374  """Write a Python object to a file.
375 
376  Parameters
377  ----------
378  inMemoryDataset : `object`
379  The Python object to store.
380 
381  Returns
382  -------
383  path : `str`
384  The `URI` where the primary file is stored.
385  """
386  raise NotImplementedError("Raw data cannot be `put`.")
387 
388  @property
389  def observationInfo(self):
390  """The `~astro_metadata_translator.ObservationInfo` extracted from
391  this file's metadata (`~astro_metadata_translator.ObservationInfo`,
392  read-only).
393  """
394  if self._observationInfo is None:
395  self._observationInfo = ObservationInfo(self.metadata, translator_class=self.translatorClass)
396  return self._observationInfo
lsst.obs.base.utils.createInitialSkyWcsFromBoresight
def createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=False)
Definition: utils.py:80
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.getDetector
def getDetector(self, id)
Definition: _fitsRawFormatterBase.py:183
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.readMask
def readMask(self)
Definition: _fitsRawFormatterBase.py:113
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.fromMetadata
def fromMetadata(cls, metadata, obsInfo=None, storageClass=None, location=None)
Definition: _fitsRawFormatterBase.py:54
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.observationInfo
def observationInfo(self)
Definition: _fitsRawFormatterBase.py:389
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase._observationInfo
_observationInfo
Definition: _fitsRawFormatterBase.py:93
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.makeVisitInfo
def makeVisitInfo(self)
Definition: _fitsRawFormatterBase.py:172
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.stripMetadata
def stripMetadata(self)
Definition: _fitsRawFormatterBase.py:163
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.isOnSky
def isOnSky(self)
Definition: _fitsRawFormatterBase.py:143
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.makeRawSkyWcsFromBoresight
def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector)
Definition: _fitsRawFormatterBase.py:244
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.filterDefinitions
def filterDefinitions(self)
Definition: _fitsRawFormatterBase.py:97
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.readRawHeaderWcs
def readRawHeaderWcs(self, parameters=None)
Definition: _fitsRawFormatterBase.py:368
lsst.obs.base.formatters.fitsExposure.FitsExposureFormatter
Definition: fitsExposure.py:33
lsst.obs.base.formatters.fitsExposure.FitsExposureFormatter.metadata
def metadata(self)
Definition: fitsExposure.py:91
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.write
def write(self, inMemoryDataset)
Definition: _fitsRawFormatterBase.py:373
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.wcsFlipX
bool wcsFlipX
Definition: _fitsRawFormatterBase.py:45
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase
Definition: _fitsRawFormatterBase.py:39
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.__init__
def __init__(self, *args, **kwargs)
Definition: _fitsRawFormatterBase.py:48
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase._createSkyWcsFromMetadata
def _createSkyWcsFromMetadata(self)
Definition: _fitsRawFormatterBase.py:263
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.translatorClass
def translatorClass(self)
Definition: _fitsRawFormatterBase.py:87
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.readFull
def readFull(self, parameters=None)
Definition: _fitsRawFormatterBase.py:336
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.readComponent
def readComponent(self, component, parameters=None)
Definition: _fitsRawFormatterBase.py:299
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase._metadata
_metadata
Definition: _fitsRawFormatterBase.py:81
lsst.obs.base.utils.InitialSkyWcsError
Definition: utils.py:35
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.makeFilter
def makeFilter(self)
Definition: _fitsRawFormatterBase.py:283
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.readImage
def readImage(self)
Definition: _fitsRawFormatterBase.py:103
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.readVariance
def readVariance(self)
Definition: _fitsRawFormatterBase.py:128
lsst.obs.base._fitsRawFormatterBase.FitsRawFormatterBase.makeWcs
def makeWcs(self, visitInfo, detector)
Definition: _fitsRawFormatterBase.py:198