LSSTApplications  20.0.0
LSSTDataManagementBasePackage
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 .fitsExposureFormatter import FitsExposureFormatter
35 from .makeRawVisitInfoViaObsInfo import MakeRawVisitInfoViaObsInfo
36 from .utils import createInitialSkyWcs, 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  skyWcs = createInitialSkyWcs(visitInfo, detector)
235 
236  return skyWcs
237 
238  def _createSkyWcsFromMetadata(self):
239  """Create a SkyWcs from the FITS header metadata in an Exposure.
240 
241  Returns
242  -------
243  skyWcs: `lsst.afw.geom.SkyWcs`, or None
244  The WCS that was created from ``self.metadata``, or None if that
245  creation fails due to invalid metadata.
246  """
247  if not self.isOnSky():
248  # This is not an on-sky observation
249  return None
250 
251  try:
252  return lsst.afw.geom.makeSkyWcs(self.metadata, strip=True)
253  except TypeError as e:
254  log = lsst.log.Log.getLogger("fitsRawFormatter")
255  log.warn("Cannot create a valid WCS from metadata: %s", e.args[0])
256  return None
257 
258  def makeFilter(self):
259  """Construct a Filter from metadata.
260 
261  Returns
262  -------
263  filter : `~lsst.afw.image.Filter`
264  Object that identifies the filter for this image.
265 
266  Raises
267  ------
268  NotFoundError
269  Raised if the physical filter was not registered via
270  `~lsst.afw.image.utils.defineFilter`.
271  """
272  return lsst.afw.image.Filter(self.observationInfo.physical_filter)
273 
274  def readComponent(self, component, parameters=None):
275  """Read a component held by the Exposure.
276 
277  Parameters
278  ----------
279  component : `str`, optional
280  Component to read from the file.
281  parameters : `dict`, optional
282  If specified, a dictionary of slicing parameters that
283  overrides those in ``fileDescriptor``.
284 
285  Returns
286  -------
287  obj : component-dependent
288  In-memory component object.
289 
290  Raises
291  ------
292  KeyError
293  Raised if the requested component cannot be handled.
294  """
295  if component == "image":
296  return self.readImage()
297  elif component == "mask":
298  return self.readMask()
299  elif component == "variance":
300  return self.readVariance()
301  elif component == "filter":
302  return self.makeFilter()
303  elif component == "visitInfo":
304  return self.makeVisitInfo()
305  elif component == "wcs":
306  detector = self.getDetector(self.observationInfo.detector_num)
307  visitInfo = self.makeVisitInfo()
308  return self.makeWcs(visitInfo, detector)
309  return None
310 
311  def readFull(self, parameters=None):
312  """Read the full Exposure object.
313 
314  Parameters
315  ----------
316  parameters : `dict`, optional
317  If specified, a dictionary of slicing parameters that overrides
318  those in the `fileDescriptor` attribute.
319 
320  Returns
321  -------
322  exposure : `~lsst.afw.image.Exposure`
323  Complete in-memory exposure.
324  """
325  from lsst.afw.image import makeExposure, makeMaskedImage
326  full = makeExposure(makeMaskedImage(self.readImage()))
327  mask = self.readMask()
328  if mask is not None:
329  full.setMask(mask)
330  variance = self.readVariance()
331  if variance is not None:
332  full.setVariance(variance)
333  full.setDetector(self.getDetector(self.observationInfo.detector_num))
334  info = full.getInfo()
335  info.setFilter(self.makeFilter())
336  info.setVisitInfo(self.makeVisitInfo())
337  info.setWcs(self.makeWcs(info.getVisitInfo(), info.getDetector()))
338  # We don't need to call stripMetadata() here because it has already
339  # been stripped during creation of the ObservationInfo, WCS, etc.
340  full.setMetadata(self.metadata)
341  return full
342 
343  def readRawHeaderWcs(self, parameters=None):
344  """Read the SkyWcs stored in the un-modified raw FITS WCS header keys.
345  """
346  return lsst.afw.geom.makeSkyWcs(lsst.afw.fits.readMetadata(self.fileDescriptor))
347 
348  def write(self, inMemoryDataset):
349  """Write a Python object to a file.
350 
351  Parameters
352  ----------
353  inMemoryDataset : `object`
354  The Python object to store.
355 
356  Returns
357  -------
358  path : `str`
359  The `URI` where the primary file is stored.
360  """
361  raise NotImplementedError("Raw data cannot be `put`.")
362 
363  @property
364  def observationInfo(self):
365  """The `~astro_metadata_translator.ObservationInfo` extracted from
366  this file's metadata (`~astro_metadata_translator.ObservationInfo`,
367  read-only).
368  """
369  if self._observationInfo is None:
370  self._observationInfo = ObservationInfo(self.metadata, translator_class=self.translatorClass)
371  return self._observationInfo
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.isOnSky
def isOnSky(self)
Definition: fitsRawFormatterBase.py:139
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.stripMetadata
def stripMetadata(self)
Definition: fitsRawFormatterBase.py:159
lsst.obs.base.fitsExposureFormatter.FitsExposureFormatter.metadata
def metadata(self)
Definition: fitsExposureFormatter.py:36
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase._observationInfo
_observationInfo
Definition: fitsRawFormatterBase.py:89
lsst.obs.base.fitsExposureFormatter.FitsExposureFormatter._metadata
_metadata
Definition: fitsExposureFormatter.py:33
lsst::afw::image
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Definition: imageAlgorithm.dox:1
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.filterDefinitions
def filterDefinitions(self)
Definition: fitsRawFormatterBase.py:93
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.readImage
def readImage(self)
Definition: fitsRawFormatterBase.py:99
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.makeFilter
def makeFilter(self)
Definition: fitsRawFormatterBase.py:258
lsst::afw::image::Filter
Holds an integer identifier for an LSST filter.
Definition: Filter.h:141
lsst::afw::image::makeExposure
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
A function to return an Exposure of the correct type (cf.
Definition: Exposure.h:442
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.makeWcs
def makeWcs(self, visitInfo, detector)
Definition: fitsRawFormatterBase.py:194
lsst::log::Log::getLogger
static Log getLogger(Log const &logger)
Definition: Log.h:760
lsst::afw::geom.transform.transformContinued.cls
cls
Definition: transformContinued.py:33
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.readComponent
def readComponent(self, component, parameters=None)
Definition: fitsRawFormatterBase.py:274
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.__init__
def __init__(self, *args, **kwargs)
Definition: fitsRawFormatterBase.py:44
lsst::log
Definition: Log.h:706
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase._createSkyWcsFromMetadata
def _createSkyWcsFromMetadata(self)
Definition: fitsRawFormatterBase.py:238
lsst::afw::geom::makeSkyWcs
std::shared_ptr< SkyWcs > makeSkyWcs(TransformPoint2ToPoint2 const &pixelsToFieldAngle, lsst::geom::Angle const &orientation, bool flipX, lsst::geom::SpherePoint const &boresight, std::string const &projection="TAN")
Construct a FITS SkyWcs from camera geometry.
Definition: SkyWcs.cc:536
lsst::afw::fits::readMetadata
std::shared_ptr< daf::base::PropertyList > readMetadata(fits::Fits &fitsfile, bool strip=false)
Read FITS header.
Definition: fits.cc:1669
lsst::afw::fits
Definition: fits.h:31
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.readMask
def readMask(self)
Definition: fitsRawFormatterBase.py:109
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.makeVisitInfo
def makeVisitInfo(self)
Definition: fitsRawFormatterBase.py:168
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase
Definition: fitsRawFormatterBase.py:39
lsst.obs.base.utils.InitialSkyWcsError
Definition: utils.py:35
lsst.obs.base.utils.createInitialSkyWcs
def createInitialSkyWcs(visitInfo, detector, flipX=False)
Definition: utils.py:44
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.getDetector
def getDetector(self, id)
Definition: fitsRawFormatterBase.py:179
lsst::afw::image::makeMaskedImage
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT >> image, typename std::shared_ptr< Mask< MaskPixelT >> mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT >> variance=Image< VariancePixelT >())
A function to return a MaskedImage of the correct type (cf.
Definition: MaskedImage.h:1279
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.write
def write(self, inMemoryDataset)
Definition: fitsRawFormatterBase.py:348
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.readVariance
def readVariance(self)
Definition: fitsRawFormatterBase.py:124
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:311
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.readRawHeaderWcs
def readRawHeaderWcs(self, parameters=None)
Definition: fitsRawFormatterBase.py:343
lsst.obs.base.fitsExposureFormatter.FitsExposureFormatter
Definition: fitsExposureFormatter.py:29
lsst.obs.base.fitsRawFormatterBase.FitsRawFormatterBase.observationInfo
def observationInfo(self)
Definition: fitsRawFormatterBase.py:364
lsst::afw::geom
Definition: frameSetUtils.h:40