LSST Applications  21.0.0+75b29a8a7f,21.0.0+e70536a077,21.0.0-1-ga51b5d4+62c747d40b,21.0.0-10-gbfb87ad6+3307648ee3,21.0.0-15-gedb9d5423+47cba9fc36,21.0.0-2-g103fe59+fdf0863a2a,21.0.0-2-g1367e85+d38a93257c,21.0.0-2-g45278ab+e70536a077,21.0.0-2-g5242d73+d38a93257c,21.0.0-2-g7f82c8f+e682ffb718,21.0.0-2-g8dde007+d179fbfa6a,21.0.0-2-g8f08a60+9402881886,21.0.0-2-ga326454+e682ffb718,21.0.0-2-ga63a54e+08647d4b1b,21.0.0-2-gde069b7+26c92b3210,21.0.0-2-gecfae73+0445ed2f95,21.0.0-2-gfc62afb+d38a93257c,21.0.0-27-gbbd0d29+ae871e0f33,21.0.0-28-g5fc5e037+feb0e9397b,21.0.0-3-g21c7a62+f4b9c0ff5c,21.0.0-3-g357aad2+57b0bddf0b,21.0.0-3-g4be5c26+d38a93257c,21.0.0-3-g65f322c+3f454acf5d,21.0.0-3-g7d9da8d+75b29a8a7f,21.0.0-3-gaa929c8+9e4ef6332c,21.0.0-3-ge02ed75+4b120a55c4,21.0.0-4-g3300ddd+e70536a077,21.0.0-4-g591bb35+4b120a55c4,21.0.0-4-gc004bbf+4911b9cd27,21.0.0-4-gccdca77+f94adcd104,21.0.0-4-ge8fba5a+2b3a696ff9,21.0.0-5-gb155db7+2c5429117a,21.0.0-5-gdf36809+637e4641ee,21.0.0-6-g00874e7+c9fd7f7160,21.0.0-6-g4e60332+4b120a55c4,21.0.0-7-gc8ca178+40eb9cf840,21.0.0-8-gfbe0b4b+9e4ef6332c,21.0.0-9-g2fd488a+d83b7cd606,w.2021.05
LSST Data Management Base Package
_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 from deprecated.sphinx import deprecated
26 
27 from astro_metadata_translator import ObservationInfo
28 
29 import lsst.afw.fits
30 import lsst.afw.geom
31 import lsst.afw.image
32 from lsst.daf.butler import FileDescriptor
33 import lsst.log
34 
35 from .formatters.fitsExposure import FitsExposureFormatter
36 from .makeRawVisitInfoViaObsInfo import MakeRawVisitInfoViaObsInfo
37 from .utils import createInitialSkyWcsFromBoresight, InitialSkyWcsError
38 
39 
40 class FitsRawFormatterBase(FitsExposureFormatter, metaclass=ABCMeta):
41  """Abstract base class for reading and writing raw data to and from
42  FITS files.
43  """
44 
45  # This has to be explicit until we fix camera geometry in DM-20746
46  wcsFlipX = False
47  """Control whether the WCS is flipped in the X-direction (`bool`)"""
48 
49  def __init__(self, *args, **kwargs):
50  self.filterDefinitionsfilterDefinitions.reset()
51  self.filterDefinitionsfilterDefinitions.defineFilters()
52  super().__init__(*args, **kwargs)
53 
54  @classmethod
55  def fromMetadata(cls, metadata, obsInfo=None, storageClass=None, location=None):
56  """Construct a possibly-limited formatter from known metadata.
57 
58  Parameters
59  ----------
60  metadata : `lsst.daf.base.PropertyList`
61  Raw header metadata, with any fixes (see
62  `astro_metadata_translator.fix_header`) applied but nothing
63  stripped.
64  obsInfo : `astro_metadata_translator.ObservationInfo`, optional
65  Structured information already extracted from ``metadata``.
66  If not provided, will be read from ``metadata`` on first use.
67  storageClass : `lsst.daf.butler.StorageClass`, optional
68  StorageClass for this file. If not provided, the formatter will
69  only support `makeWcs`, `makeVisitInfo`, `makeFilter`, and other
70  operations that operate purely on metadata and not the actual file.
71  location : `lsst.daf.butler.Location`, optional.
72  Location of the file. If not provided, the formatter will only
73  support `makeWcs`, `makeVisitInfo`, `makeFilter`, and other
74  operations that operate purely on metadata and not the actual file.
75 
76  Returns
77  -------
78  formatter : `FitsRawFormatterBase`
79  An instance of ``cls``.
80  """
81  self = cls(FileDescriptor(location, storageClass))
82  self._metadata_metadata_metadata = metadata
83  self._observationInfo_observationInfo = obsInfo
84  return self
85 
86  @property
87  @abstractmethod
88  def translatorClass(self):
89  """`~astro_metadata_translator.MetadataTranslator` to translate
90  metadata header to `~astro_metadata_translator.ObservationInfo`.
91  """
92  return None
93 
94  _observationInfo = None
95 
96  @property
97  @abstractmethod
98  def filterDefinitions(self):
99  """`~lsst.obs.base.FilterDefinitions`, defining the filters for this
100  instrument.
101  """
102  return None
103 
104  def readImage(self):
105  """Read just the image component of the Exposure.
106 
107  Returns
108  -------
109  image : `~lsst.afw.image.Image`
110  In-memory image component.
111  """
112  return lsst.afw.image.ImageU(self.fileDescriptor.location.path)
113 
114  def readMask(self):
115  """Read just the mask component of the Exposure.
116 
117  May return None (as the default implementation does) to indicate that
118  there is no mask information to be extracted (at least not trivially)
119  from the raw data. This will prohibit direct reading of just the mask,
120  and set the mask of the full Exposure to zeros.
121 
122  Returns
123  -------
124  mask : `~lsst.afw.image.Mask`
125  In-memory mask component.
126  """
127  return None
128 
129  def readVariance(self):
130  """Read just the variance component of the Exposure.
131 
132  May return None (as the default implementation does) to indicate that
133  there is no variance information to be extracted (at least not
134  trivially) from the raw data. This will prohibit direct reading of
135  just the variance, and set the variance of the full Exposure to zeros.
136 
137  Returns
138  -------
139  image : `~lsst.afw.image.Image`
140  In-memory variance component.
141  """
142  return None
143 
144  def isOnSky(self):
145  """Boolean to determine if the exposure is thought to be on the sky.
146 
147  Returns
148  -------
149  onSky : `bool`
150  Returns `True` if the observation looks like it was taken on the
151  sky. Returns `False` if this observation looks like a calibration
152  observation.
153 
154  Notes
155  -----
156  If there is tracking RA/Dec information associated with the
157  observation it is assumed that the observation is on sky.
158  Currently the observation type is not checked.
159  """
160  if self.observationInfoobservationInfo.tracking_radec is None:
161  return False
162  return True
163 
164  def stripMetadata(self):
165  """Remove metadata entries that are parsed into components.
166  """
167  # NOTE: makeVisitInfo() may not strip any metadata itself, but calling
168  # it ensures that ObservationInfo is created from the metadata, which
169  # will strip the VisitInfo keys and more.
170  self.makeVisitInfomakeVisitInfo()
171  self._createSkyWcsFromMetadata_createSkyWcsFromMetadata()
172 
173  def makeVisitInfo(self):
174  """Construct a VisitInfo from metadata.
175 
176  Returns
177  -------
178  visitInfo : `~lsst.afw.image.VisitInfo`
179  Structured metadata about the observation.
180  """
181  return MakeRawVisitInfoViaObsInfo.observationInfo2visitInfo(self.observationInfoobservationInfo)
182 
183  @abstractmethod
184  def getDetector(self, id):
185  """Return the detector that acquired this raw exposure.
186 
187  Parameters
188  ----------
189  id : `int`
190  The identifying number of the detector to get.
191 
192  Returns
193  -------
194  detector : `~lsst.afw.cameraGeom.Detector`
195  The detector associated with that ``id``.
196  """
197  raise NotImplementedError("Must be implemented by subclasses.")
198 
199  def makeWcs(self, visitInfo, detector):
200  """Create a SkyWcs from information about the exposure.
201 
202  If VisitInfo is not None, use it and the detector to create a SkyWcs,
203  otherwise return the metadata-based SkyWcs (always created, so that
204  the relevant metadata keywords are stripped).
205 
206  Parameters
207  ----------
208  visitInfo : `~lsst.afw.image.VisitInfo`
209  The information about the telescope boresight and camera
210  orientation angle for this exposure.
211  detector : `~lsst.afw.cameraGeom.Detector`
212  The detector used to acquire this exposure.
213 
214  Returns
215  -------
216  skyWcs : `~lsst.afw.geom.SkyWcs`
217  Reversible mapping from pixel coordinates to sky coordinates.
218 
219  Raises
220  ------
221  InitialSkyWcsError
222  Raised if there is an error generating the SkyWcs, chained from the
223  lower-level exception if available.
224  """
225  if not self.isOnSkyisOnSky():
226  # This is not an on-sky observation
227  return None
228 
229  skyWcs = self._createSkyWcsFromMetadata_createSkyWcsFromMetadata()
230 
231  log = lsst.log.Log.getLogger("fitsRawFormatter")
232  if visitInfo is None:
233  msg = "No VisitInfo; cannot access boresight information. Defaulting to metadata-based SkyWcs."
234  log.warn(msg)
235  if skyWcs is None:
236  raise InitialSkyWcsError("Failed to create both metadata and boresight-based SkyWcs."
237  "See warnings in log messages for details.")
238  return skyWcs
239 
240  return self.makeRawSkyWcsFromBoresightmakeRawSkyWcsFromBoresight(visitInfo.getBoresightRaDec(),
241  visitInfo.getBoresightRotAngle(),
242  detector)
243 
244  @classmethod
245  def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector):
246  """Class method to make a raw sky WCS from boresight and detector.
247 
248  Parameters
249  ----------
250  boresight : `lsst.geom.SpherePoint`
251  The ICRS boresight RA/Dec
252  orientation : `lsst.geom.Angle`
253  The rotation angle of the focal plane on the sky.
254  detector : `lsst.afw.cameraGeom.Detector`
255  Where to get the camera geomtry from.
256 
257  Returns
258  -------
259  skyWcs : `~lsst.afw.geom.SkyWcs`
260  Reversible mapping from pixel coordinates to sky coordinates.
261  """
262  return createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=cls.wcsFlipXwcsFlipX)
263 
264  def _createSkyWcsFromMetadata(self):
265  """Create a SkyWcs from the FITS header metadata in an Exposure.
266 
267  Returns
268  -------
269  skyWcs: `lsst.afw.geom.SkyWcs`, or None
270  The WCS that was created from ``self.metadata``, or None if that
271  creation fails due to invalid metadata.
272  """
273  if not self.isOnSkyisOnSky():
274  # This is not an on-sky observation
275  return None
276 
277  try:
278  return lsst.afw.geom.makeSkyWcs(self.metadatametadata, strip=True)
279  except TypeError as e:
280  log = lsst.log.Log.getLogger("fitsRawFormatter")
281  log.warn("Cannot create a valid WCS from metadata: %s", e.args[0])
282  return None
283 
284  # TODO: remove in DM-27177
285  @deprecated(reason="Replaced with makeFilterLabel. Will be removed after v22.", category=FutureWarning)
286  def makeFilter(self):
287  """Construct a Filter from metadata.
288 
289  Returns
290  -------
291  filter : `~lsst.afw.image.Filter`
292  Object that identifies the filter for this image.
293 
294  Raises
295  ------
296  NotFoundError
297  Raised if the physical filter was not registered via
298  `~lsst.afw.image.utils.defineFilter`.
299  """
300  return lsst.afw.image.Filter(self.observationInfoobservationInfo.physical_filter)
301 
302  # TODO: deprecate in DM-27177, remove in DM-27811
303  def makeFilterLabel(self):
304  """Construct a FilterLabel from metadata.
305 
306  Returns
307  -------
308  filter : `~lsst.afw.image.FilterLabel`
309  Object that identifies the filter for this image.
310  """
311  physical = self.observationInfoobservationInfo.physical_filter
312  band = self.filterDefinitionsfilterDefinitions.physical_to_band[physical]
313  return lsst.afw.image.FilterLabel(physical=physical, band=band)
314 
315  def readComponent(self, component, parameters=None):
316  """Read a component held by the Exposure.
317 
318  Parameters
319  ----------
320  component : `str`, optional
321  Component to read from the file.
322  parameters : `dict`, optional
323  If specified, a dictionary of slicing parameters that
324  overrides those in ``fileDescriptor``.
325 
326  Returns
327  -------
328  obj : component-dependent
329  In-memory component object.
330 
331  Raises
332  ------
333  KeyError
334  Raised if the requested component cannot be handled.
335  """
336  if component == "image":
337  return self.readImagereadImage()
338  elif component == "mask":
339  return self.readMaskreadMask()
340  elif component == "variance":
341  return self.readVariancereadVariance()
342  elif component == "filter":
343  return self.makeFiltermakeFilter()
344  elif component == "filterLabel":
345  return self.makeFilterLabelmakeFilterLabel()
346  elif component == "visitInfo":
347  return self.makeVisitInfomakeVisitInfo()
348  elif component == "wcs":
349  detector = self.getDetectorgetDetector(self.observationInfoobservationInfo.detector_num)
350  visitInfo = self.makeVisitInfomakeVisitInfo()
351  return self.makeWcsmakeWcs(visitInfo, detector)
352  return None
353 
354  def readFull(self, parameters=None):
355  """Read the full Exposure object.
356 
357  Parameters
358  ----------
359  parameters : `dict`, optional
360  If specified, a dictionary of slicing parameters that overrides
361  those in the `fileDescriptor` attribute.
362 
363  Returns
364  -------
365  exposure : `~lsst.afw.image.Exposure`
366  Complete in-memory exposure.
367  """
368  from lsst.afw.image import makeExposure, makeMaskedImage
369  full = makeExposure(makeMaskedImage(self.readImagereadImage()))
370  mask = self.readMaskreadMask()
371  if mask is not None:
372  full.setMask(mask)
373  variance = self.readVariancereadVariance()
374  if variance is not None:
375  full.setVariance(variance)
376  full.setDetector(self.getDetectorgetDetector(self.observationInfoobservationInfo.detector_num))
377  info = full.getInfo()
378  info.setFilterLabel(self.makeFilterLabelmakeFilterLabel())
379  info.setVisitInfo(self.makeVisitInfomakeVisitInfo())
380  info.setWcs(self.makeWcsmakeWcs(info.getVisitInfo(), info.getDetector()))
381  # We don't need to call stripMetadata() here because it has already
382  # been stripped during creation of the ObservationInfo, WCS, etc.
383  full.setMetadata(self.metadatametadata)
384  return full
385 
386  def readRawHeaderWcs(self, parameters=None):
387  """Read the SkyWcs stored in the un-modified raw FITS WCS header keys.
388  """
389  return lsst.afw.geom.makeSkyWcs(lsst.afw.fits.readMetadata(self.fileDescriptor.location.path))
390 
391  def write(self, inMemoryDataset):
392  """Write a Python object to a file.
393 
394  Parameters
395  ----------
396  inMemoryDataset : `object`
397  The Python object to store.
398 
399  Returns
400  -------
401  path : `str`
402  The `URI` where the primary file is stored.
403  """
404  raise NotImplementedError("Raw data cannot be `put`.")
405 
406  @property
407  def observationInfo(self):
408  """The `~astro_metadata_translator.ObservationInfo` extracted from
409  this file's metadata (`~astro_metadata_translator.ObservationInfo`,
410  read-only).
411  """
412  if self._observationInfo_observationInfo is None:
413  location = self.fileDescriptor.location
414  path = location.path if location is not None else None
415  self._observationInfo_observationInfo = ObservationInfo(self.metadatametadata, translator_class=self.translatorClasstranslatorClass,
416  filename=path)
417  return self._observationInfo_observationInfo
A group of labels for a filter in an exposure or coadd.
Definition: FilterLabel.h:58
static Log getLogger(Log const &logger)
Definition: Log.h:760
def fromMetadata(cls, metadata, obsInfo=None, storageClass=None, location=None)
def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector)
std::shared_ptr< daf::base::PropertyList > readMetadata(std::string const &fileName, int hdu=DEFAULT_HDU, bool strip=false)
Read FITS header.
Definition: fits.cc:1657
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition: SkyWcs.cc:526
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
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:462
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:1268
Definition: Log.h:706
def createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=False)
Definition: utils.py:80