LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
fitsUtils.py
Go to the documentation of this file.
1 from __future__ import division
2 from builtins import object
3 import re, warnings
4 import lsst.afw.image as afwImage
5 import lsst.afw.table as afwTable
6 import lsst.afw.cameraGeom as afwCameraGeom
7 import lsst.afw.geom as afwGeom
8 
9 def getByKey(metadata, key):
10  """Wrapper for getting a value from a metadata object by key.
11  @param[in] metadata metadata object to query for value
12  @param[in] key key to use for value lookup
13  @returns value associated with key, None if key does not exist
14  """
15  mdKeys = metadata.paramNames()
16  if key in mdKeys:
17  return metadata.get(key)
18  else:
19  return None
20 
21 def setByKey(metadata, key, value, clobber):
22  """Wrapper for setting a value in a metadata object. Deals with case
23  where the key already exists.
24  @param[in, out] metadata metadata object ot modify in place.
25  @param[in] key key to associate with value
26  @param[in] value value to assign in the metadata object
27  @param[in] clobber Clobber the value if the key already exisists?
28  """
29  mdKeys = metadata.paramNames()
30  if key not in mdKeys or (key in mdKeys and clobber):
31  metadata.set(key, value)
32 
33 class HeaderMap(dict):
34  """ Class to hold mapping of header cards to attributes"""
35  def addEntry(self, keyname, attribute_name, default=None, transform=lambda x: x):
36  """Adds an entry to the registr
37  @param[in] keyname Key used to retrieve the header record
38  @param[in] attribute_name Name of the attribute to store the value in
39  @param[jn] default Default velue to store if the header card is not available
40  @param[in] transform Transform to apply to the header value before assigning it to the
41  attribute.
42  """
43  self.__setitem__(attribute_name, {'keyName':keyname,
44  'default':default,
45  'transform':transform})
46 
47  def setAttributes(self, obj, metadata, doRaise=True):
48  """Sets the attributes on the give object given a metadata object.
49  @param[in, out] obj Object on which to operate in place
50  @param[in] metadata Metadata object used for applying the mapping
51  @param[in] doRaise Raise exceptions on calling methods on the input object that do not exist?
52  """
53  for key, attrDict in self.items():
54  try:
55  value = getByKey(metadata, attrDict['keyName'])
56  if value is not None:
57  self._applyVal(obj, value, key, attrDict['transform'])
58  else:
59  #Only apply transform if the metadata has a value for this key
60  #otherwise assume the default value is transformed.
61  value = attrDict['default']
62  self._applyVal(obj, value, key, lambda x: x)
63  except Exception as e:
64  if doRaise:
65  raise
66  else:
67  warnings.warn('WARNING: Failed to set %s attribute with %s value: %s'%
68  (key, value, str(e)))
69 
70  def _applyVal(self, obj, value, attrName, transform):
71  raise NotImplementedError('Must be implemented in sub-class')
72 
74  """ Class to hold mapping of header cards to AmpInfoTable attributes
75  The amp info is stored using setters, thus calling the attribute as a function.
76  """
77  def _applyVal(self, obj, value, attrName, transform):
78  getattr(obj, attrName)(transform(value))
79 
81  """ Class to hold mapping of header cards to Detector attributes
82  Detector information is stored as attributes on a Config object.
83  """
84  def _applyVal(self, obj, value, attrName, transform):
85  obj.__setattr__(attrName, transform(value))
86 
87 class DetectorBuilder(object):
88  def __init__(self, detectorFileName, ampFileNameList, inAmpCoords=True, plateScale=1.,
89  radialCoeffs=(0., 1.), clobberMetadata=False, doRaise=True):
90  ''' @param[in] detectorFileName FITS file containing the detector description.
91  May use [] notation to specify an extension in an MEF.
92  @param[in] ampFileNameList List of FITS file names to use in building the amps.
93  May contain duplicate entries if the raw data are assembled.
94  @param[in] inAmpCoords Boolean, True if raw data are in amp coordinates, False if raw data
95  are assembled into pseudo detector pixel arrays
96  @param[in] plateScale Nominal platescale (arcsec/mm)
97  @param[in] radialCoeffs Radial distortion coefficients for a radial polynomial in normalized
98  units.
99  @param[in] clobberMetadata Clobber metadata from input files if overridden in the _sanitizeMetadata method
100  @param[in] doRaise Raise exception if not all non-defaulted keywords are defined? Default is True.
101  '''
102  self.inAmpCoords = inAmpCoords
105  self.detectorMetadata = afwImage.readMetadata(detectorFileName)
106  self._sanitizeHeaderMetadata(self.detectorMetadata, clobber=clobberMetadata)
107  self.ampMetadataList = []
108  self.detector = None
109  self.doRaise = doRaise
110  for fileName in ampFileNameList:
111  self.ampMetadataList.append(afwImage.readMetadata(fileName))
112  self._sanitizeHeaderMetadata(self.ampMetadataList[-1], clobber=clobberMetadata)
113  self.plateScale = plateScale
114  self.focalPlaneToPupil = self._makeRadialTransform(radialCoeffs)
115 
116  def _sanitizeHeaderMetadata(self, metadata, clobber):
117  """This method is called for all metadata and gives an opportunity to add/modify
118  header information for use downstream.
119  Override this method if more than the default is needed.
120  @param[in, out] metadata Metadata to read/modify
121  @param[in] clobber Clobber keys that exist with default keys?
122  """
123  self._defaultSanitization(metadata, clobber)
124 
125  def _defaultSanitization(self, metadata, clobber):
126  """Does the default sanitization of the header metadata.
127  @param[in,out] metadata Header metadata to extend/modify
128  @param[in] clobber Override values in existing header cards?
129  """
130 
131  if self.inAmpCoords:
132  #Deal with DTM to get flipX and flipY for assembly and add as 'FLIPX', 'FLIPY'
133  #The DTM array is a transformation matrix. As I understand it, it transforms between
134  #electronic and assembled coordintates. As such, a negative value in the DTM1_1 spot
135  #corresponds to a flip of the x-axis and a negative value in the DTM2_2 spot
136  #corresponds to a flip of the y-axis.
137  dtm1 = getByKey(metadata, 'DTM1_1')
138  dtm2 = getByKey(metadata, 'DTM2_2')
139  if dtm1 is not None and dtm2 is not None:
140  setByKey(metadata, 'FLIPX', dtm1 < 0, clobber)
141  setByKey(metadata, 'FLIPY', dtm2 < 0, clobber)
142  setByKey(metadata, 'RDCRNR', afwTable.LL, clobber)
143  else:
144  setByKey(metadata, 'FLIPX', False, clobber)
145  setByKey(metadata, 'FLIPY', True, clobber)
146  #I don't know how to figure out the read corner if already assembled
147  setByKey(metadata, 'RDCRNR', None, clobber)
148 
149  #Deal with NAXIS1, NAXIS2 to make rawBBox as 'RAWBBOX'
150  xext = getByKey(metadata, 'NAXIS1')
151  yext = getByKey(metadata, 'NAXIS2')
152  if xext is not None and yext is not None:
153  setByKey(metadata, 'RAWBBOX', '[%i:%i,%i:%i]'%(1, xext, 1, yext), clobber)
154  #Deal with DTV1, DTV2 to make 'XYOFF
155  dtv1 = getByKey(metadata, 'DTV1')
156  dtv2 = getByKey(metadata, 'DTV2')
157  if dtv1 is not None and dtv2 is not None:
158  setByKey(metadata, 'XYOFF', [dtv1, dtv2], clobber)
159  #map biassec[1] to HOSCAN
160  #map biassec[3] to VOSCAN
161  #map biassec[2] to PRESCAN
162  if metadata.isArray('BIASSEC'):
163  keylist = ['HOSCAN', 'PRESCAN', 'VOSCAN']
164  biassecs = getByKey(metadata, 'BIASSEC')
165  for i, biassec in enumerate(biassecs):
166  setByKey(metadata, keylist[i], biassec, clobber)
167  else:
168  biassec = getByKey(metadata, 'BIASSEC')
169  if biassec is not None:
170  setByKey(metadata, 'HOSCAN', biassec, clobber)
171 
173  """Make the default map from header information to amplifier information
174  @return The HeaderAmpMap object containing the mapping
175  """
176  hMap = HeaderAmpMap()
177  emptyBBox = afwGeom.BoxI()
178  mapList = [('EXTNAME', 'setName'),
179  ('DETSEC', 'setBBox', None, self._makeBbox),
180  ('GAIN', 'setGain', 1.),
181  ('RDNOISE', 'setReadNoise', 0.),
182  ('SATURATE', 'setSaturation', 2<<15),
183  ('RDCRNR', 'setReadoutCorner', afwTable.LL),
184  ('LINCOEFF', 'setLinearityCoeffs', [0., 1.]),
185  ('LINTYPE', 'setLinearityType', 'POLY'),
186  ('RAWBBOX', 'setRawBBox', None, self._makeBbox),
187  ('DATASEC', 'setRawDataBBox', None, self._makeBbox),
188  ('FLIPX', 'setRawFlipX', False),
189  ('FLIPY', 'setRawFlipY', False),
190  ('XYOFF', 'setRawXYOffset', afwGeom.ExtentI(0,0), self._makeExt),
191  ('HOSCAN', 'setRawHorizontalOverscanBBox', emptyBBox, self._makeBbox),
192  ('VOSCAN', 'setRawVerticalOverscanBBox', emptyBBox, self._makeBbox),
193  ('PRESCAN', 'setRawPrescanBBox', emptyBBox, self._makeBbox),
194  ]
195  for tup in mapList:
196  hMap.addEntry(*tup)
197  return hMap
198 
200  """Make the default map from header information to detector information
201  @return The HeaderDetectorMap object containing the mapping
202  """
203  hMap = HeaderDetectorMap()
204  mapList = [('CCDNAME', 'name', 'ccdName'),
205  ('DETSIZE', 'bbox_x0', 0, self._getBboxX0),
206  ('DETSIZE', 'bbox_y0', 0, self._getBboxY0),
207  ('DETSIZE', 'bbox_x1', 0, self._getBboxX1),
208  ('DETSIZE', 'bbox_y1', 0, self._getBboxY1),
209  ('DETID', 'id', 0),
210  ('OBSTYPE', 'detectorType', afwCameraGeom.SCIENCE),
211  ('SERSTR', 'serial', 'none'),
212  ('XPOS', 'offset_x', 0.),
213  ('YPOS', 'offset_y', 0.),
214  ('XPIX', 'refpos_x', 0.),
215  ('YPIX', 'refpos_y', 0.),
216  ('YAWDEG', 'yawDeg', 0.),
217  ('PITCHDEG', 'pitchDeg', 0.),
218  ('ROLLDEG', 'rollDeg', 0.),
219  ('XPIXSIZE', 'pixelSize_x', 1.),
220  ('YPIXSIZE', 'pixelSize_y', 1.),
221  ('TRNSPOSE', 'transposeDetector', False),
222  ]
223  for tup in mapList:
224  hMap.addEntry(*tup)
225  return hMap
226 
227  def _makeExt(self, extArr):
228  """Helper function to make an extent from an array
229  @param[in] extArr Length 2 array to use in creating the Extent object
230  @return Extent2I constructed from the input list
231  """
232  return afwGeom.ExtentI(*extArr)
233 
234  def _makeBbox(self, boxString):
235  """Helper funtion to make a bounding box from a string representing a FITS style bounding box
236  @param[in] boxString String describing the bounding box
237  @return Box2I for the bounding box
238  """
239  #strip off brackets and split into parts
240  x1, x2, y1, y2 = [int(el) for el in re.split('[:,]', boxString.strip()[1:-1])]
241  box = afwGeom.BoxI(afwGeom.PointI(x1, y1), afwGeom.PointI(x2, y2))
242  #account for the difference between FITS convention and LSST convention for
243  #index of LLC.
244  box.shift(afwGeom.Extent2I(-1, -1))
245  return box
246 
247 
248  def _getBboxX0(self, boxString):
249  return self._makeBbox(boxString).getMinX()
250 
251  def _getBboxX1(self, boxString):
252  return self._makeBbox(boxString).getMaxX()
253 
254  def _getBboxY0(self, boxString):
255  return self._makeBbox(boxString).getMinY()
256 
257  def _getBboxY1(self, boxString):
258  return self._makeBbox(boxString).getMaxY()
259 
260  def _makeRadialTransform(self, radialCoeffs):
261  """Helper function to get the radial transform given the radial polynomial coefficients given in
262  the constructor.
263  @param[in] radialCoeffs List of coefficients describing a polynomial radial distortion in
264  normalized units.
265  @return RadialXYTransform object describing the radial distortion
266  """
267  pScaleRad = afwGeom.arcsecToRad(self.plateScale)
268  return afwGeom.RadialXYTransform([el/pScaleRad for el in radialCoeffs])
269 
270  def buildDetector(self):
271  """Take all the information and build a Detector object. The Detector object is necessary for doing
272  things like assembly.
273  @return Detector object
274  """
275  if self.detector is not None:
276  return self.detector
277 
278  schema = afwTable.AmpInfoTable.makeMinimalSchema()
279  ampInfo = afwTable.AmpInfoCatalog(schema)
280  for ampMetadata in self.ampMetadataList:
281  record = ampInfo.addNew()
282  self.defaultAmpMap.setAttributes(record, ampMetadata, self.doRaise)
283  record.setHasRawInfo(True)
284 
285  detConfig = afwCameraGeom.DetectorConfig()
286  self.defaultDetectorMap.setAttributes(detConfig, self.detectorMetadata, self.doRaise)
287  self.detector = afwCameraGeom.makeDetector(detConfig, ampInfo, self.focalPlaneToPupil)
288  return self.detector
289 
290  def makeCalib(self):
291  """PLaceholder for subclasses to implement construction of a calib to associate with the exposure.
292  @return empty afwImage.Calib object
293  """
294  return afwImage.Calib()
295 
296  def makeExposure(self, im, mask=None, variance=None):
297  """Method for constructing an exposure object from an image and the information contained in this
298  class to construct the Detector and Calib objects.
299  @param[in] im Image used to construct the exposure
300  @param[in] mask Optional mask plane as a <askU
301  @param[in] variance Optional variance plance as an image of the same type as im
302  @param[out] Exposure object
303  """
304  if mask is None:
305  mask = afwImage.MaskU(im.getDimensions())
306  if variance is None:
307  variance = im
308  mi = afwImage.makeMaskedImage(im, mask, variance)
309  detector = self.buildDetector()
310 
312  calib = self.makeCalib()
313  exp = afwImage.makeExposure(mi, wcs)
314  exp.setCalib(calib)
315  exp.setDetector(detector)
316  return exp
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename Image< ImagePixelT >::Ptr image, typename Mask< MaskPixelT >::Ptr mask=typename Mask< MaskPixelT >::Ptr(), typename Image< VariancePixelT >::Ptr variance=typename Image< VariancePixelT >::Ptr())
A function to return a MaskedImage of the correct type (cf.
Definition: MaskedImage.h:1073
A custom container class for records, based on std::vector.
Definition: Catalog.h:95
Describe an exposure&#39;s calibration.
Definition: Calib.h:82
An integer coordinate rectangle.
Definition: Box.h:53
boost::shared_ptr< Wcs > makeWcs(coord::Coord const &crval, geom::Point2D const &crpix, double CD11, double CD12, double CD21, double CD22)
Create a Wcs object from crval, crpix, CD, using CD elements (useful from python) ...
Definition: makeWcs.cc:138
boost::shared_ptr< daf::base::PropertyList > readMetadata(std::string const &fileName, int hdu=0, bool strip=false)
Return the metadata (header entries) from a FITS file.
Definition: Utils.h:62
A purely radial polynomial distortion, up to 6th order.
Definition: XYTransform.h:186
Exposure< ImagePixelT, MaskPixelT, VariancePixelT >::Ptr makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, boost::shared_ptr< Wcs const > wcs=boost::shared_ptr< Wcs const >())
A function to return an Exposure of the correct type (cf.
Definition: Exposure.h:314
double arcsecToRad(double x)
Definition: Angle.h:40