LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
decamMapper.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2012-2016 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import os
23 import re
24 
25 import numpy as np
26 
27 from lsst.utils import getPackageDir
28 import lsst.afw.image as afwImage
29 import lsst.afw.image.utils as afwImageUtils
30 from lsst.afw.fits import readMetadata
31 from lsst.afw.geom import makeSkyWcs
32 from lsst.obs.base import CameraMapper, exposureFromImage
33 from lsst.daf.persistence import ButlerLocation, Storage, Policy
34 import lsst.ip.isr as isr
35 from .makeDecamRawVisitInfo import MakeDecamRawVisitInfo
36 
37 np.seterr(divide="ignore")
38 
39 __all__ = ["DecamMapper"]
40 
41 
43  packageName = 'obs_decam'
44 
45  MakeRawVisitInfoClass = MakeDecamRawVisitInfo
46 
47  detectorNames = {
48  1: 'S29', 2: 'S30', 3: 'S31', 4: 'S25', 5: 'S26', 6: 'S27', 7: 'S28', 8: 'S20', 9: 'S21',
49  10: 'S22', 11: 'S23', 12: 'S24', 13: 'S14', 14: 'S15', 15: 'S16', 16: 'S17', 17: 'S18',
50  18: 'S19', 19: 'S8', 20: 'S9', 21: 'S10', 22: 'S11', 23: 'S12', 24: 'S13', 25: 'S1', 26: 'S2',
51  27: 'S3', 28: 'S4', 29: 'S5', 30: 'S6', 31: 'S7', 32: 'N1', 33: 'N2', 34: 'N3', 35: 'N4',
52  36: 'N5', 37: 'N6', 38: 'N7', 39: 'N8', 40: 'N9', 41: 'N10', 42: 'N11', 43: 'N12', 44: 'N13',
53  45: 'N14', 46: 'N15', 47: 'N16', 48: 'N17', 49: 'N18', 50: 'N19', 51: 'N20', 52: 'N21',
54  53: 'N22', 54: 'N23', 55: 'N24', 56: 'N25', 57: 'N26', 58: 'N27', 59: 'N28', 60: 'N29',
55  62: 'N31'}
56 
57  def __init__(self, inputPolicy=None, **kwargs):
58  policyFile = Policy.defaultPolicyFile(self.packageName, "DecamMapper.yaml", "policy")
59  policy = Policy(policyFile)
60 
61  super(DecamMapper, self).__init__(policy, os.path.dirname(policyFile), **kwargs)
62 
63  # lambdaMin and lambda max are chosen to be where the filter rises above 1%
64  # from http://www.ctio.noao.edu/noao/sites/default/files/DECam/DECam_filters_transmission.txt
65  afwImageUtils.defineFilter('u', lambdaEff=350, lambdaMin=305, lambdaMax=403,
66  alias=['u DECam c0006 3500.0 1000.0'])
67  afwImageUtils.defineFilter('g', lambdaEff=450, lambdaMin=394, lambdaMax=555,
68  alias=['g DECam SDSS c0001 4720.0 1520.0'])
69  afwImageUtils.defineFilter('r', lambdaEff=600, lambdaMin=562, lambdaMax=725,
70  alias=['r DECam SDSS c0002 6415.0 1480.0'])
71  afwImageUtils.defineFilter('i', lambdaEff=750, lambdaMin=699, lambdaMax=870,
72  alias=['i DECam SDSS c0003 7835.0 1470.0'])
73  afwImageUtils.defineFilter('z', lambdaEff=900, lambdaMin=837, lambdaMax=1016,
74  alias=['z DECam SDSS c0004 9260.0 1520.0'])
75  afwImageUtils.defineFilter('y', lambdaEff=1000, lambdaMin=941, lambdaMax=1080,
76  alias=['Y DECam c0005 10095.0 1130.0', 'Y'])
77  afwImageUtils.defineFilter('VR', lambdaEff=630, lambdaMin=490, lambdaMax=765,
78  alias=['VR DECam c0007 6300.0 2600.0'])
79  afwImageUtils.defineFilter('N964', lambdaEff=964, alias=['N964 DECam c0008 9645.0 94.0'])
80  afwImageUtils.defineFilter('SOLID', lambdaEff=0, alias=['solid'])
81 
82  # The data ID key ccdnum is not directly used in the current policy
83  # template of the raw and instcal et al. datasets, so is not in its
84  # keyDict automatically. Add it so the butler know about the data ID key
85  # ccdnum.
86  for datasetType in ("raw", "instcal", "dqmask", "wtmap"):
87  self.mappings[datasetType].keyDict.update({'ccdnum': int})
88  self.mappings["raw"].keyDict.update({'object': str})
89 
90  # The number of bits allocated for fields in object IDs
91  # TODO: This needs to be updated; also see Trac #2797
92  DecamMapper._nbit_tract = 10
93  DecamMapper._nbit_patch = 10
94  DecamMapper._nbit_filter = 4
95  DecamMapper._nbit_id = 64 - (DecamMapper._nbit_tract +
96  2*DecamMapper._nbit_patch +
97  DecamMapper._nbit_filter)
98 
99  def _extractDetectorName(self, dataId):
100  copyId = self._transformId(dataId)
101  try:
102  return DecamMapper.detectorNames[copyId['ccdnum']]
103  except KeyError:
104  raise RuntimeError("No name found for dataId: %s"%(dataId))
105 
106  def _transformId(self, dataId):
107  copyId = CameraMapper._transformId(self, dataId)
108  if "ccd" in copyId:
109  copyId.setdefault("ccdnum", copyId["ccd"])
110  return copyId
111 
112  def bypass_ccdExposureId(self, datasetType, pythonType, location, dataId):
113  return self._computeCcdExposureId(dataId)
114 
115  def bypass_ccdExposureId_bits(self, datasetType, pythonType, location, dataId):
116  return 32 # not really, but this leaves plenty of space for sources
117 
118  def _computeCcdExposureId(self, dataId):
119  """Compute the 64-bit (long) identifier for a CCD exposure.
120 
121  Parameters
122  ----------
123  dataId : `dict`
124  Data identifier with visit, ccd.
125 
126  Returns
127  -------
128  result : `int`
129  Integer identifier for a CCD exposure.
130  """
131  copyId = self._transformId(dataId)
132  visit = copyId['visit']
133  ccdnum = copyId['ccdnum']
134  return int("%07d%02d" % (visit, ccdnum))
135 
136  def _computeCoaddExposureId(self, dataId, singleFilter):
137  """Compute the 64-bit (long) identifier for a coadd.
138 
139  Parameters
140  ----------
141  dataId : `dict`
142  Data identifier with tract and patch.
143  singleFilter : `bool`
144  True means the desired ID is for a single-filter coadd,
145  in which case the dataId must contain filter.
146 
147  Returns
148  -------
149  oid : `int`
150  Unique integer identifier.
151  """
152  tract = int(dataId['tract'])
153  if tract < 0 or tract >= 2**DecamMapper._nbit_tract:
154  raise RuntimeError('tract not in range [0,%d)' % (2**DecamMapper._nbit_tract))
155  patchX, patchY = [int(x) for x in dataId['patch'].split(',')]
156  for p in (patchX, patchY):
157  if p < 0 or p >= 2**DecamMapper._nbit_patch:
158  raise RuntimeError('patch component not in range [0, %d)' % 2**DecamMapper._nbit_patch)
159  oid = (((tract << DecamMapper._nbit_patch) + patchX) << DecamMapper._nbit_patch) + patchY
160  if singleFilter:
161  return (oid << DecamMapper._nbit_filter) + afwImage.Filter(dataId['filter']).getId()
162  return oid
163 
164  def bypass_deepCoaddId(self, datasetType, pythonType, location, dataId):
165  return self._computeCoaddExposureId(dataId, True)
166 
167  def bypass_deepCoaddId_bits(self, *args, **kwargs):
168  return 64 - DecamMapper._nbit_id
169 
170  def bypass_deepMergedCoaddId(self, datasetType, pythonType, location, dataId):
171  return self._computeCoaddExposureId(dataId, False)
172 
173  def bypass_deepMergedCoaddId_bits(self, *args, **kwargs):
174  return 64 - DecamMapper._nbit_id
175 
176  def bypass_dcrCoaddId(self, datasetType, pythonType, location, dataId):
177  return self.bypass_deepCoaddId(datasetType, pythonType, location, dataId)
178 
179  def bypass_dcrCoaddId_bits(self, *args, **kwargs):
180  return self.bypass_deepCoaddId_bits(*args, **kwargs)
181 
182  def bypass_dcrMergedCoaddId(self, datasetType, pythonType, location, dataId):
183  return self.bypass_deepMergedCoaddId(datasetType, pythonType, location, dataId)
184 
185  def bypass_dcrMergedCoaddId_bits(self, *args, **kwargs):
186  return self.bypass_deepMergedCoaddId_bits(*args, **kwargs)
187 
188  def translate_dqmask(self, dqmask):
189  # TODO: make a class member variable that knows the mappings
190  # below instead of hard-coding them
191  dqmArr = dqmask.getArray()
192  mask = afwImage.Mask(dqmask.getDimensions())
193  mArr = mask.getArray()
194  idxBad = np.where(dqmArr & 1)
195  idxSat = np.where(dqmArr & 2)
196  idxIntrp = np.where(dqmArr & 4)
197  idxCr = np.where(dqmArr & 16)
198  idxBleed = np.where(dqmArr & 64)
199  idxEdge = np.where(dqmArr & 512)
200  mArr[idxBad] |= mask.getPlaneBitMask("BAD")
201  mArr[idxSat] |= mask.getPlaneBitMask("SAT")
202  mArr[idxIntrp] |= mask.getPlaneBitMask("INTRP")
203  mArr[idxCr] |= mask.getPlaneBitMask("CR")
204  mArr[idxBleed] |= mask.getPlaneBitMask("SAT")
205  mArr[idxEdge] |= mask.getPlaneBitMask("EDGE")
206  return mask
207 
208  def translate_wtmap(self, wtmap):
209  wtmArr = wtmap.getArray()
210  idxUndefWeight = np.where(wtmArr <= 0)
211  # Reassign weights to be finite but small:
212  wtmArr[idxUndefWeight] = min(1e-14, np.min(wtmArr[np.where(wtmArr > 0)]))
213  var = 1.0 / wtmArr
214  varim = afwImage.ImageF(var)
215  return varim
216 
217  def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId):
218  # Workaround until I can access the butler
219  instcalMap = self.map_instcal(dataId)
220  dqmaskMap = self.map_dqmask(dataId)
221  wtmapMap = self.map_wtmap(dataId)
222  instcalType = getattr(afwImage, instcalMap.getPythonType().split(".")[-1])
223  dqmaskType = getattr(afwImage, dqmaskMap.getPythonType().split(".")[-1])
224  wtmapType = getattr(afwImage, wtmapMap.getPythonType().split(".")[-1])
225  instcal = instcalType(instcalMap.getLocationsWithRoot()[0])
226  dqmask = dqmaskType(dqmaskMap.getLocationsWithRoot()[0])
227  wtmap = wtmapType(wtmapMap.getLocationsWithRoot()[0])
228 
229  mask = self.translate_dqmask(dqmask)
230  variance = self.translate_wtmap(wtmap)
231 
232  mi = afwImage.MaskedImageF(afwImage.ImageF(instcal.getImage()), mask, variance)
233  md = readMetadata(instcalMap.getLocationsWithRoot()[0])
234  wcs = makeSkyWcs(md, strip=True)
235  exp = afwImage.ExposureF(mi, wcs)
236 
237  calib = afwImage.Calib()
238  calib.setFluxMag0(10**(0.4 * md.getScalar("MAGZERO")))
239  exp.setCalib(calib)
240  visitInfo = self.makeRawVisitInfo(md=md)
241  exp.getInfo().setVisitInfo(visitInfo)
242 
243  for kw in ('LTV1', 'LTV2'):
244  md.remove(kw)
245 
246  exp.setMetadata(md)
247  return exp
248 
249  def std_raw(self, item, dataId):
250  """Standardize a raw dataset by converting it to an Exposure.
251 
252  Raw images are MEF files with one HDU for each detector.
253 
254  Parameters
255  ----------
256  item : `lsst.afw.image.MaskedImage`
257  The image read by the butler.
258  dataId : data ID
259  Data identifier.
260 
261  Returns
262  -------
263  result : `lsst.afw.image.Exposure`
264  The standardized Exposure.
265  """
266  # Convert the raw DecoratedImage to an Exposure, set metadata and wcs.
267  exp = exposureFromImage(item, logger=self.log)
268  md = exp.getMetadata()
269  visitInfo = self.makeRawVisitInfo(md=md)
270  exp.getInfo().setVisitInfo(visitInfo)
271 
272  # Standardize an Exposure, including setting the calib object
273  return self._standardizeExposure(self.exposures['raw'], exp, dataId,
274  trimmed=False)
275 
276  def std_dark(self, item, dataId):
278  rawPath = self.map_raw(dataId).getLocations()[0]
279  headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", rawPath)
280  md0 = readMetadata(headerPath)
281  visitInfo = self.makeRawVisitInfo(md0)
282  exp.getInfo().setVisitInfo(visitInfo)
283  return self._standardizeExposure(self.calibrations["dark"], exp, dataId, filter=False)
284 
285  def std_bias(self, item, dataId):
287  return self._standardizeExposure(self.calibrations["bias"], exp, dataId, filter=False)
288 
289  def std_flat(self, item, dataId):
291  return self._standardizeExposure(self.calibrations["flat"], exp, dataId, filter=True)
292 
293  def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False):
294  """Standardize a MasterCal image obtained from NOAO archive into Exposure
295 
296  These MasterCal images are MEF files with one HDU for each detector.
297  Some WCS header, eg CTYPE1, exists only in the zeroth extensionr,
298  so info in the zeroth header need to be copied over to metadata.
299 
300  Parameters
301  ----------
302  datasetType : `str`
303  Dataset type ("bias" or "flat").
304  item : `lsst.afw.image.MaskedImage`
305  The image read by the butler.
306  dataId : data ID
307  Data identifier.
308  setFilter : `bool`
309  Whether to set the filter in the Exposure.
310 
311  Returns
312  -------
313  result : `lsst.afw.image.Exposure`
314  The standardized Exposure.
315  """
316  mi = afwImage.makeMaskedImage(item.getImage())
317  md = item.getMetadata()
318  masterCalMap = getattr(self, "map_" + datasetType)
319  masterCalPath = masterCalMap(dataId).getLocationsWithRoot()[0]
320  headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", masterCalPath)
321  md0 = readMetadata(headerPath)
322  for kw in ('CTYPE1', 'CTYPE2', 'CRVAL1', 'CRVAL2', 'CUNIT1', 'CUNIT2',
323  'CD1_1', 'CD1_2', 'CD2_1', 'CD2_2'):
324  if kw in md0.paramNames() and kw not in md.paramNames():
325  md.add(kw, md0.getScalar(kw))
326  wcs = makeSkyWcs(md, strip=True)
327  exp = afwImage.makeExposure(mi, wcs)
328  exp.setMetadata(md)
329  return self._standardizeExposure(self.calibrations[datasetType], exp, dataId, filter=setFilter)
330 
331  def std_cpBias(self, item, dataId):
332  return self._standardizeCpMasterCal("cpBias", item, dataId, setFilter=False)
333 
334  def std_cpFlat(self, item, dataId):
335  return self._standardizeCpMasterCal("cpFlat", item, dataId, setFilter=True)
336 
337  def std_fringe(self, item, dataId):
339  return self._standardizeExposure(self.calibrations["fringe"], exp, dataId)
340 
341  def map_defects(self, dataId, write=False):
342  """Map defects dataset with the calibration registry.
343 
344  Overriding the method so to use CalibrationMapping policy,
345  instead of looking up the path in defectRegistry as currently
346  implemented in CameraMapper.
347 
348  Parameters
349  ----------
350  dataId : data ID
351  Dataset identifier.
352 
353  Returns
354  -------
355  result : `lsst.daf.persistence.ButlerLocation`
356  """
357  return self.mappings["defects"].map(self, dataId=dataId, write=write)
358 
359  def bypass_defects(self, datasetType, pythonType, butlerLocation, dataId):
360  """Return a defect list based on butlerLocation returned by map_defects.
361 
362  Use all nonzero pixels in the Community Pipeline Bad Pixel Masks.
363 
364  Parameters
365  ----------
366  butlerLocation : `lsst.daf.persistence.ButlerLocation`
367  Butler Location with path to defects FITS.
368  dataId : data ID
369  Data identifier.
370 
371  Returns
372  -------
373  result : `lsst.meas.algorithms.DefectListT`
374  """
375  bpmFitsPath = butlerLocation.getLocationsWithRoot()[0]
376  bpmImg = afwImage.ImageU(bpmFitsPath, allowUnsafe=True)
377  idxBad = np.nonzero(bpmImg.getArray())
378  mim = afwImage.MaskedImageU(bpmImg.getDimensions())
379  mim.getMask().getArray()[idxBad] |= mim.getMask().getPlaneBitMask("BAD")
380  return isr.getDefectListFromMask(mim, "BAD")
381 
382  def std_defects(self, item, dataId):
383  """Return the defect list as it is. Do not standardize it to Exposure.
384  """
385  return item
386 
387  @classmethod
389  """Directory containing linearizers"""
390  packageName = cls.getPackageName()
391  packageDir = getPackageDir(packageName)
392  return os.path.join(packageDir, "decam", "linearizer")
393 
394  def map_linearizer(self, dataId, write=False):
395  """Map a linearizer"""
396  actualId = self._transformId(dataId)
397  location = "%02d.fits" % (dataId["ccdnum"])
398  return ButlerLocation(
399  pythonType="lsst.ip.isr.LinearizeSquared",
400  cppType="Config",
401  storageName="PickleStorage",
402  locationList=[location],
403  dataId=actualId,
404  mapper=self,
405  storage=Storage.makeFromURI(self.getLinearizerDir())
406  )
def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId)
Definition: decamMapper.py:217
def bypass_deepCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:164
def _standardizeExposure(self, mapping, item, dataId, filter=True, trimmed=True, setVisitInfo=True)
def std_defects(self, item, dataId)
Definition: decamMapper.py:382
def std_fringe(self, item, dataId)
Definition: decamMapper.py:337
def std_cpFlat(self, item, dataId)
Definition: decamMapper.py:334
def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False)
Definition: decamMapper.py:293
def std_flat(self, item, dataId)
Definition: decamMapper.py:289
def bypass_dcrMergedCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:182
def std_cpBias(self, item, dataId)
Definition: decamMapper.py:331
int min
Describe an exposure&#39;s calibration.
Definition: Calib.h:95
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:1280
def bypass_ccdExposureId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:112
def bypass_dcrCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:176
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:446
def bypass_dcrCoaddId_bits(self, args, kwargs)
Definition: decamMapper.py:179
def std_bias(self, item, dataId)
Definition: decamMapper.py:285
def __init__(self, inputPolicy=None, kwargs)
Definition: decamMapper.py:57
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:78
def map(self, datasetType, dataId, write=False)
Definition: mapper.py:138
def std_dark(self, item, dataId)
Definition: decamMapper.py:276
def bypass_defects(self, datasetType, pythonType, butlerLocation, dataId)
Definition: decamMapper.py:359
def bypass_dcrMergedCoaddId_bits(self, args, kwargs)
Definition: decamMapper.py:185
Holds an integer identifier for an LSST filter.
Definition: Filter.h:141
def bypass_ccdExposureId_bits(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:115
def _computeCoaddExposureId(self, dataId, singleFilter)
def bypass_deepMergedCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:170
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition: SkyWcs.cc:476
def bypass_deepMergedCoaddId_bits(self, args, kwargs)
Definition: decamMapper.py:173
def map_linearizer(self, dataId, write=False)
Definition: decamMapper.py:394
def bypass_deepCoaddId_bits(self, args, kwargs)
Definition: decamMapper.py:167
def exposureFromImage(image, dataId=None, mapper=None, logger=None, setVisitInfo=True)
def std_raw(self, item, dataId)
Definition: decamMapper.py:249
def map_defects(self, dataId, write=False)
Definition: decamMapper.py:341