LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
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  exp.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(10**(0.4 * md.getScalar("MAGZERO")), 0))
238  visitInfo = self.makeRawVisitInfo(md=md)
239  exp.getInfo().setVisitInfo(visitInfo)
240 
241  for kw in ('LTV1', 'LTV2'):
242  md.remove(kw)
243 
244  exp.setMetadata(md)
245  return exp
246 
247  def std_raw(self, item, dataId):
248  """Standardize a raw dataset by converting it to an Exposure.
249 
250  Raw images are MEF files with one HDU for each detector.
251 
252  Parameters
253  ----------
254  item : `lsst.afw.image.MaskedImage`
255  The image read by the butler.
256  dataId : data ID
257  Data identifier.
258 
259  Returns
260  -------
261  result : `lsst.afw.image.Exposure`
262  The standardized Exposure.
263  """
264  # Convert the raw DecoratedImage to an Exposure, set metadata and wcs.
265  exp = exposureFromImage(item, logger=self.log)
266  md = exp.getMetadata()
267  visitInfo = self.makeRawVisitInfo(md=md)
268  exp.getInfo().setVisitInfo(visitInfo)
269 
270  # Standardize an Exposure, including setting the photoCalib object
271  return self._standardizeExposure(self.exposures['raw'], exp, dataId,
272  trimmed=False)
273 
274  def std_dark(self, item, dataId):
276  rawPath = self.map_raw(dataId).getLocations()[0]
277  headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", rawPath)
278  md0 = readMetadata(headerPath)
279  visitInfo = self.makeRawVisitInfo(md0)
280  exp.getInfo().setVisitInfo(visitInfo)
281  return self._standardizeExposure(self.calibrations["dark"], exp, dataId, filter=False)
282 
283  def std_bias(self, item, dataId):
285  return self._standardizeExposure(self.calibrations["bias"], exp, dataId, filter=False)
286 
287  def std_flat(self, item, dataId):
289  return self._standardizeExposure(self.calibrations["flat"], exp, dataId, filter=True)
290 
291  def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False):
292  """Standardize a MasterCal image obtained from NOAO archive into Exposure
293 
294  These MasterCal images are MEF files with one HDU for each detector.
295  Some WCS header, eg CTYPE1, exists only in the zeroth extensionr,
296  so info in the zeroth header need to be copied over to metadata.
297 
298  Parameters
299  ----------
300  datasetType : `str`
301  Dataset type ("bias" or "flat").
302  item : `lsst.afw.image.MaskedImage`
303  The image read by the butler.
304  dataId : data ID
305  Data identifier.
306  setFilter : `bool`
307  Whether to set the filter in the Exposure.
308 
309  Returns
310  -------
311  result : `lsst.afw.image.Exposure`
312  The standardized Exposure.
313  """
314  mi = afwImage.makeMaskedImage(item.getImage())
315  md = item.getMetadata()
316  masterCalMap = getattr(self, "map_" + datasetType)
317  masterCalPath = masterCalMap(dataId).getLocationsWithRoot()[0]
318  headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", masterCalPath)
319  md0 = readMetadata(headerPath)
320  for kw in ('CTYPE1', 'CTYPE2', 'CRVAL1', 'CRVAL2', 'CUNIT1', 'CUNIT2',
321  'CD1_1', 'CD1_2', 'CD2_1', 'CD2_2'):
322  if kw in md0.paramNames() and kw not in md.paramNames():
323  md.add(kw, md0.getScalar(kw))
324  wcs = makeSkyWcs(md, strip=True)
325  exp = afwImage.makeExposure(mi, wcs)
326  exp.setMetadata(md)
327  return self._standardizeExposure(self.calibrations[datasetType], exp, dataId, filter=setFilter)
328 
329  def std_cpBias(self, item, dataId):
330  return self._standardizeCpMasterCal("cpBias", item, dataId, setFilter=False)
331 
332  def std_cpFlat(self, item, dataId):
333  return self._standardizeCpMasterCal("cpFlat", item, dataId, setFilter=True)
334 
335  def std_fringe(self, item, dataId):
337  return self._standardizeExposure(self.calibrations["fringe"], exp, dataId)
338 
339  def map_defects(self, dataId, write=False):
340  """Map defects dataset with the calibration registry.
341 
342  Overriding the method so to use CalibrationMapping policy,
343  instead of looking up the path in defectRegistry as currently
344  implemented in CameraMapper.
345 
346  Parameters
347  ----------
348  dataId : data ID
349  Dataset identifier.
350 
351  Returns
352  -------
353  result : `lsst.daf.persistence.ButlerLocation`
354  """
355  return self.mappings["defects"].map(self, dataId=dataId, write=write)
356 
357  def bypass_defects(self, datasetType, pythonType, butlerLocation, dataId):
358  """Return a defect list based on butlerLocation returned by map_defects.
359 
360  Use all nonzero pixels in the Community Pipeline Bad Pixel Masks.
361 
362  Parameters
363  ----------
364  butlerLocation : `lsst.daf.persistence.ButlerLocation`
365  Butler Location with path to defects FITS.
366  dataId : data ID
367  Data identifier.
368 
369  Returns
370  -------
371  result : `lsst.meas.algorithms.DefectListT`
372  """
373  bpmFitsPath = butlerLocation.getLocationsWithRoot()[0]
374  bpmImg = afwImage.ImageU(bpmFitsPath, allowUnsafe=True)
375  idxBad = np.nonzero(bpmImg.getArray())
376  mim = afwImage.MaskedImageU(bpmImg.getDimensions())
377  mim.getMask().getArray()[idxBad] |= mim.getMask().getPlaneBitMask("BAD")
378  return isr.getDefectListFromMask(mim, "BAD")
379 
380  def std_defects(self, item, dataId):
381  """Return the defect list as it is. Do not standardize it to Exposure.
382  """
383  return item
384 
385  @classmethod
387  """Directory containing linearizers"""
388  packageName = cls.getPackageName()
389  packageDir = getPackageDir(packageName)
390  return os.path.join(packageDir, "decam", "linearizer")
391 
392  def map_linearizer(self, dataId, write=False):
393  """Map a linearizer"""
394  actualId = self._transformId(dataId)
395  location = "%02d.fits" % (dataId["ccdnum"])
396  return ButlerLocation(
397  pythonType="lsst.ip.isr.LinearizeSquared",
398  cppType="Config",
399  storageName="PickleStorage",
400  locationList=[location],
401  dataId=actualId,
402  mapper=self,
403  storage=Storage.makeFromURI(self.getLinearizerDir())
404  )
def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId)
Definition: decamMapper.py:217
def bypass_deepCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:164
std::shared_ptr< PhotoCalib > makePhotoCalibFromCalibZeroPoint(double instFluxMag0, double instFluxMag0Err)
Construct a PhotoCalib from the deprecated Calib-style instFluxMag0/instFluxMag0Err values...
Definition: PhotoCalib.cc:644
def _standardizeExposure(self, mapping, item, dataId, filter=True, trimmed=True, setVisitInfo=True)
def std_defects(self, item, dataId)
Definition: decamMapper.py:380
def std_fringe(self, item, dataId)
Definition: decamMapper.py:335
def std_cpFlat(self, item, dataId)
Definition: decamMapper.py:332
def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False)
Definition: decamMapper.py:291
def std_flat(self, item, dataId)
Definition: decamMapper.py:287
def bypass_dcrMergedCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:182
def std_cpBias(self, item, dataId)
Definition: decamMapper.py:329
int min
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:457
def bypass_dcrCoaddId_bits(self, args, kwargs)
Definition: decamMapper.py:179
def std_bias(self, item, dataId)
Definition: decamMapper.py:283
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:274
def bypass_defects(self, datasetType, pythonType, butlerLocation, dataId)
Definition: decamMapper.py:357
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:486
def bypass_deepMergedCoaddId_bits(self, args, kwargs)
Definition: decamMapper.py:173
def map_linearizer(self, dataId, write=False)
Definition: decamMapper.py:392
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
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:247
def map_defects(self, dataId, write=False)
Definition: decamMapper.py:339