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