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