lsst.obs.base  18.1.0-12-gf30922b
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 # (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__ = ("FitsRawFormatterBase",)
23 
24 from abc import ABCMeta, abstractmethod
25 
26 from astro_metadata_translator import ObservationInfo
27 
28 import lsst.afw.image
29 import lsst.afw.geom
30 from lsst.daf.butler.formatters.fitsExposureFormatter import FitsExposureFormatter
31 import lsst.log
33 
34 from lsst.obs.base import MakeRawVisitInfoViaObsInfo
35 from lsst.obs.base.utils import createInitialSkyWcs, InitialSkyWcsError
36 
37 
38 class FitsRawFormatterBase(FitsExposureFormatter, metaclass=ABCMeta):
39  """Abstract base class for reading and writing raw data to and from
40  FITS files.
41  """
42 
43  @property
44  @abstractmethod
45  def translatorClass(self):
46  """`~astro_metadata_translator.MetadataTranslator` to translate
47  metadata header to `~astro_metadata_translator.ObservationInfo`.
48  """
49  return None
50 
51  _observationInfo = None
52 
53  def readImage(self):
54  """Read just the image component of the Exposure.
55 
56  Returns
57  -------
58  image : `~lsst.afw.image.Image`
59  In-memory image component.
60  """
61  return lsst.afw.image.ImageU(self.fileDescriptor.location.path)
62 
63  def readMask(self):
64  """Read just the mask component of the Exposure.
65 
66  May return None (as the default implementation does) to indicate that
67  there is no mask information to be extracted (at least not trivially)
68  from the raw data. This will prohibit direct reading of just the mask,
69  and set the mask of the full Exposure to zeros.
70 
71  Returns
72  -------
73  mask : `~lsst.afw.image.Mask`
74  In-memory mask component.
75  """
76  return None
77 
78  def readVariance(self):
79  """Read just the variance component of the Exposure.
80 
81  May return None (as the default implementation does) to indicate that
82  there is no variance information to be extracted (at least not
83  trivially) from the raw data. This will prohibit direct reading of
84  just the variance, and set the variance of the full Exposure to zeros.
85 
86  Returns
87  -------
88  image : `~lsst.afw.image.Image`
89  In-memory variance component.
90  """
91  return None
92 
93  def stripMetadata(self):
94  """Remove metadata entries that are parsed into components.
95  """
96  # NOTE: makeVisitInfo() may not strip any metadata itself, but calling
97  # it ensures that ObservationInfo is created from the metadata, which
98  # will strip the VisitInfo keys and more.
99  self.makeVisitInfo()
101 
102  def makeVisitInfo(self):
103  """Construct a VisitInfo from metadata.
104 
105  Returns
106  -------
107  visitInfo : `~lsst.afw.image.VisitInfo`
108  Structured metadata about the observation.
109  """
110  return MakeRawVisitInfoViaObsInfo.observationInfo2visitInfo(self.observationInfo)
111 
112  @abstractmethod
113  def getDetector(self, id):
114  """Return the detector that acquired this raw exposure.
115 
116  Parameters
117  ----------
118  id : `int`
119  The identifying number of the detector to get.
120 
121  Returns
122  -------
123  detector : `~lsst.afw.cameraGeom.Detector`
124  The detector associated with that ``id``.
125  """
126  raise NotImplementedError("Must be implemented by subclasses.")
127 
128  def makeWcs(self, visitInfo, detector):
129  """Create a SkyWcs from information about the exposure.
130 
131  If VisitInfo is not None, use it and the detector to create a SkyWcs,
132  otherwise return the metadata-based SkyWcs (always created, so that
133  the relevant metadata keywords are stripped).
134 
135  Parameters
136  ----------
137  visitInfo : `~lsst.afw.image.VisitInfo`
138  The information about the telescope boresight and camera
139  orientation angle for this exposure.
140  detector : `~lsst.afw.cameraGeom.Detector`
141  The detector used to acquire this exposure.
142 
143  Returns
144  -------
145  skyWcs : `~lsst.afw.geom.SkyWcs`
146  Reversible mapping from pixel coordinates to sky coordinates.
147 
148  Raises
149  ------
150  InitialSkyWcsError
151  Raised if there is an error generating the SkyWcs, chained from the
152  lower-level exception if available.
153  """
154  skyWcs = self._createSkyWcsFromMetadata()
155 
156  log = lsst.log.Log.getLogger("fitsRawFormatter")
157  if visitInfo is None:
158  msg = "No VisitInfo; cannot access boresight information. Defaulting to metadata-based SkyWcs."
159  log.warn(msg)
160  if skyWcs is None:
161  raise InitialSkyWcsError("Failed to create both metadata and boresight-based SkyWcs."
162  "See warnings in log messages for details.")
163  return skyWcs
164  skyWcs = createInitialSkyWcs(visitInfo, detector)
165 
166  return skyWcs
167 
168  def _createSkyWcsFromMetadata(self):
169  """Create a SkyWcs from the FITS header metadata in an Exposure.
170 
171  Returns
172  -------
173  skyWcs: `lsst.afw.geom.SkyWcs`, or None
174  The WCS that was created from ``self.metadata``, or None if that
175  creation fails due to invalid metadata.
176  """
177  try:
178  return lsst.afw.geom.makeSkyWcs(self.metadata, strip=True)
179  except lsst.pex.exceptions.TypeError as e:
180  log = lsst.log.Log.getLogger("fitsRawFormatter")
181  log.warn("Cannot create a valid WCS from metadata: %s", e.args[0])
182  return None
183 
184  def makeFilter(self):
185  """Construct a Filter from metadata.
186 
187  Returns
188  -------
189  filter : `~lsst.afw.image.Filter`
190  Object that identifies the filter for this image.
191  """
192  raise NotImplementedError("Must be implemented by subclasses.")
193 
194  def readImageComponent(self, component):
195  """Read the image, mask, or variance component of an Exposure.
196 
197  Parameters
198  ----------
199  component : `str`, optional
200  Component to read from the file. Always one of "image",
201  "variance", or "mask".
202 
203  Returns
204  -------
205  image : `~lsst.afw.image.Image` or `~lsst.afw.image.Mask`
206  In-memory image, variance, or mask component.
207  """
208  if component == "image":
209  return self.readImage()
210  elif component == "mask":
211  return self.readMask()
212  elif component == "variance":
213  return self.readVariance()
214 
215  def readInfoComponent(self, component):
216  """Read a component held by ExposureInfo.
217 
218  The implementation provided by FitsRawFormatter provides only "wcs"
219  and "visitInfo". When adding support for other components, subclasses
220  should delegate to `super()` for those and update `readFull` with
221  similar logic.
222 
223  Parameters
224  ----------
225  component : `str`, optional
226  Component to read from the file.
227 
228  Returns
229  -------
230  obj : component-dependent
231  In-memory component object.
232  """
233  if component == "filter":
234  return self.makeFilter()
235  elif component == "visitInfo":
236  return self.makeVisitInfo()
237  elif component == "wcs":
238  detector = self.getDetector(self.observationInfo.detector_num)
239  visitInfo = self.makeVisitInfo()
240  return self.makeWcs(visitInfo, detector)
241  return None
242 
243  def readFull(self, parameters=None):
244  """Read the full Exposure object.
245 
246  Parameters
247  ----------
248  parameters : `dict`, optional
249  If specified a dictionary of slicing parameters that overrides
250  those in ``self.fileDescriptor`.
251 
252  Returns
253  -------
254  exposure : `~lsst.afw.image.Exposure`
255  Complete in-memory exposure.
256  """
257  from lsst.afw.image import makeExposure, makeMaskedImage
258  full = makeExposure(makeMaskedImage(self.readImage()))
259  mask = self.readMask()
260  if mask is not None:
261  full.setMask(mask)
262  variance = self.readVariance()
263  if variance is not None:
264  full.setVariance(variance)
265  full.setDetector(self.getDetector(self.observationInfo.detector_num))
266  info = full.getInfo()
267  info.setFilter(self.makeFilter())
268  info.setVisitInfo(self.makeVisitInfo())
269  info.setWcs(self.makeWcs(info.getVisitInfo(), info.getDetector()))
270  # We don't need to call stripMetadata() here because it has already
271  # been stripped during creation of the ObservationInfo, WCS, etc.
272  full.setMetadata(self.metadata)
273  return full
274 
275  def write(self, inMemoryDataset):
276  """Write a Python object to a file.
277 
278  Parameters
279  ----------
280  inMemoryDataset : `object`
281  The Python object to store.
282 
283  Returns
284  -------
285  path : `str`
286  The `URI` where the primary file is stored.
287  """
288  raise NotImplementedError("Raw data cannot be `put`.")
289 
290  @property
291  def observationInfo(self):
292  """The `~astro_metadata_translator.ObservationInfo` extracted from
293  this file's metadata (`~astro_metadata_translator.ObservationInfo`,
294  read-only).
295  """
296  if self._observationInfo is None:
297  self._observationInfo = ObservationInfo(self.metadata, translator_class=self.translatorClass)
298  return self._observationInfo
def createInitialSkyWcs(visitInfo, detector, flipX=False)
Definition: utils.py:41