LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
decamMapper.py
Go to the documentation of this file.
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#
22import os
23import re
24import traceback
25
26import numpy as np
27
28from lsst.utils import getPackageDir
29from astro_metadata_translator import fix_header, DecamTranslator
30import lsst.afw.image as afwImage
31from lsst.afw.fits import readMetadata
32from lsst.afw.geom import makeSkyWcs
33from lsst.obs.base import CameraMapper
34from lsst.obs.base.utils import createInitialSkyWcs, InitialSkyWcsError
35from lsst.daf.persistence import ButlerLocation, Storage, Policy
36from .makeDecamRawVisitInfo import MakeDecamRawVisitInfo
37from ._instrument import DarkEnergyCamera
38
39np.seterr(divide="ignore")
40
41__all__ = ["DecamMapper"]
42
43
44class DecamMapper(CameraMapper):
45 packageName = 'obs_decam'
46 _gen3instrument = DarkEnergyCamera
47
48 MakeRawVisitInfoClass = MakeDecamRawVisitInfo
49
50 detectorNames = {
51 1: 'S29', 2: 'S30', 3: 'S31', 4: 'S25', 5: 'S26', 6: 'S27', 7: 'S28', 8: 'S20', 9: 'S21',
52 10: 'S22', 11: 'S23', 12: 'S24', 13: 'S14', 14: 'S15', 15: 'S16', 16: 'S17', 17: 'S18',
53 18: 'S19', 19: 'S8', 20: 'S9', 21: 'S10', 22: 'S11', 23: 'S12', 24: 'S13', 25: 'S1', 26: 'S2',
54 27: 'S3', 28: 'S4', 29: 'S5', 30: 'S6', 31: 'S7', 32: 'N1', 33: 'N2', 34: 'N3', 35: 'N4',
55 36: 'N5', 37: 'N6', 38: 'N7', 39: 'N8', 40: 'N9', 41: 'N10', 42: 'N11', 43: 'N12', 44: 'N13',
56 45: 'N14', 46: 'N15', 47: 'N16', 48: 'N17', 49: 'N18', 50: 'N19', 51: 'N20', 52: 'N21',
57 53: 'N22', 54: 'N23', 55: 'N24', 56: 'N25', 57: 'N26', 58: 'N27', 59: 'N28', 60: 'N29',
58 62: 'N31'}
59
60 def __init__(self, inputPolicy=None, **kwargs):
61 policyFile = Policy.defaultPolicyFile(self.packageNamepackageName, "DecamMapper.yaml", "policy")
62 policy = Policy(policyFile)
63
64 super(DecamMapper, self).__init__(policy, os.path.dirname(policyFile), **kwargs)
65
66 # The data ID key ccdnum is not directly used in the current policy
67 # template of the raw and instcal et al. datasets, so is not in its
68 # keyDict automatically. Add it so the butler know about the data ID key
69 # ccdnum.
70 # Similarly, add "object" for raws.
71 for datasetType in ("raw", "instcal", "dqmask", "wtmap", "cpIllumcor"):
72 self.mappings[datasetType].keyDict.update({'ccdnum': int})
73 self.mappings["raw"].keyDict.update({'object': str})
74
75 # The number of bits allocated for fields in object IDs
76 # TODO: This needs to be updated; also see Trac #2797
77 DecamMapper._nbit_tract = 10
78 DecamMapper._nbit_patch = 10
79 DecamMapper._nbit_filter = 4
80 DecamMapper._nbit_id = 64 - (DecamMapper._nbit_tract
81 + 2*DecamMapper._nbit_patch
82 + DecamMapper._nbit_filter)
83
84 def _extractDetectorName(self, dataId):
85 copyId = self._transformId_transformId(dataId)
86 try:
87 return DecamMapper.detectorNames[copyId['ccdnum']]
88 except KeyError:
89 raise RuntimeError("No name found for dataId: %s"%(dataId))
90
91 def _transformId(self, dataId):
92 copyId = CameraMapper._transformId(self, dataId)
93 if "ccd" in copyId:
94 copyId.setdefault("ccdnum", copyId["ccd"])
95 return copyId
96
97 def bypass_ccdExposureId(self, datasetType, pythonType, location, dataId):
98 return self._computeCcdExposureId_computeCcdExposureId(dataId)
99
100 def bypass_ccdExposureId_bits(self, datasetType, pythonType, location, dataId):
101 return 32 # not really, but this leaves plenty of space for sources
102
103 def _computeCcdExposureId(self, dataId):
104 """Compute the 64-bit (long) identifier for a CCD exposure.
105
106 Parameters
107 ----------
108 dataId : `dict`
109 Data identifier with visit, ccd.
110
111 Returns
112 -------
113 result : `int`
114 Integer identifier for a CCD exposure.
115 """
116 copyId = self._transformId_transformId(dataId)
117 visit = copyId['visit']
118 ccdnum = copyId['ccdnum']
119 return int("%07d%02d" % (visit, ccdnum))
120
121 def _computeCoaddExposureId(self, dataId, singleFilter):
122 """Compute the 64-bit (long) identifier for a coadd.
123
124 Parameters
125 ----------
126 dataId : `dict`
127 Data identifier with tract and patch.
128 singleFilter : `bool`
129 True means the desired ID is for a single-filter coadd,
130 in which case the dataId must contain filter.
131
132 Returns
133 -------
134 oid : `int`
135 Unique integer identifier.
136 """
137 tract = int(dataId['tract'])
138 if tract < 0 or tract >= 2**DecamMapper._nbit_tract:
139 raise RuntimeError('tract not in range [0,%d)' % (2**DecamMapper._nbit_tract))
140 patchX, patchY = [int(x) for x in dataId['patch'].split(',')]
141 for p in (patchX, patchY):
142 if p < 0 or p >= 2**DecamMapper._nbit_patch:
143 raise RuntimeError('patch component not in range [0, %d)' % 2**DecamMapper._nbit_patch)
144 oid = (((tract << DecamMapper._nbit_patch) + patchX) << DecamMapper._nbit_patch) + patchY
145 if singleFilter:
146 return (oid << DecamMapper._nbit_filter) + afwImage.Filter(dataId['filter']).getId()
147 return oid
148
149 def bypass_deepCoaddId(self, datasetType, pythonType, location, dataId):
150 return self._computeCoaddExposureId_computeCoaddExposureId(dataId, True)
151
152 def bypass_deepCoaddId_bits(self, *args, **kwargs):
153 return 64 - DecamMapper._nbit_id
154
155 def bypass_deepMergedCoaddId(self, datasetType, pythonType, location, dataId):
156 return self._computeCoaddExposureId_computeCoaddExposureId(dataId, False)
157
158 def bypass_deepMergedCoaddId_bits(self, *args, **kwargs):
159 return 64 - DecamMapper._nbit_id
160
161 def bypass_dcrCoaddId(self, datasetType, pythonType, location, dataId):
162 return self.bypass_deepCoaddIdbypass_deepCoaddId(datasetType, pythonType, location, dataId)
163
164 def bypass_dcrCoaddId_bits(self, *args, **kwargs):
165 return self.bypass_deepCoaddId_bitsbypass_deepCoaddId_bits(*args, **kwargs)
166
167 def bypass_dcrMergedCoaddId(self, datasetType, pythonType, location, dataId):
168 return self.bypass_deepMergedCoaddIdbypass_deepMergedCoaddId(datasetType, pythonType, location, dataId)
169
170 def bypass_dcrMergedCoaddId_bits(self, *args, **kwargs):
171 return self.bypass_deepMergedCoaddId_bitsbypass_deepMergedCoaddId_bits(*args, **kwargs)
172
173 def translate_dqmask(self, dqmask):
174 # TODO: make a class member variable that knows the mappings
175 # below instead of hard-coding them
176 dqmArr = dqmask.getArray()
177 mask = afwImage.Mask(dqmask.getDimensions())
178 mArr = mask.getArray()
179 idxBad = np.where(dqmArr & 1)
180 idxSat = np.where(dqmArr & 2)
181 idxIntrp = np.where(dqmArr & 4)
182 idxCr = np.where(dqmArr & 16)
183 idxBleed = np.where(dqmArr & 64)
184 idxEdge = np.where(dqmArr & 512)
185 mArr[idxBad] |= mask.getPlaneBitMask("BAD")
186 mArr[idxSat] |= mask.getPlaneBitMask("SAT")
187 mArr[idxIntrp] |= mask.getPlaneBitMask("INTRP")
188 mArr[idxCr] |= mask.getPlaneBitMask("CR")
189 mArr[idxBleed] |= mask.getPlaneBitMask("SAT")
190 mArr[idxEdge] |= mask.getPlaneBitMask("EDGE")
191 return mask
192
193 def translate_wtmap(self, wtmap):
194 wtmArr = wtmap.getArray()
195 idxUndefWeight = np.where(wtmArr <= 0)
196 # Reassign weights to be finite but small:
197 wtmArr[idxUndefWeight] = min(1e-14, np.min(wtmArr[np.where(wtmArr > 0)]))
198 var = 1.0 / wtmArr
199 varim = afwImage.ImageF(var)
200 return varim
201
202 def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId):
203 # Workaround until I can access the butler
204 instcalMap = self.map_instcal(dataId)
205 dqmaskMap = self.map_dqmask(dataId)
206 wtmapMap = self.map_wtmap(dataId)
207 instcalType = getattr(afwImage, instcalMap.getPythonType().split(".")[-1])
208 dqmaskType = getattr(afwImage, dqmaskMap.getPythonType().split(".")[-1])
209 wtmapType = getattr(afwImage, wtmapMap.getPythonType().split(".")[-1])
210 instcal = instcalType(instcalMap.getLocationsWithRoot()[0])
211 dqmask = dqmaskType(dqmaskMap.getLocationsWithRoot()[0])
212 wtmap = wtmapType(wtmapMap.getLocationsWithRoot()[0])
213
214 mask = self.translate_dqmasktranslate_dqmask(dqmask)
215 variance = self.translate_wtmaptranslate_wtmap(wtmap)
216
217 mi = afwImage.MaskedImageF(afwImage.ImageF(instcal.getImage()), mask, variance)
218 md = readMetadata(instcalMap.getLocationsWithRoot()[0])
219 fix_header(md, translator_class=DecamTranslator)
220 wcs = makeSkyWcs(md, strip=True)
221 exp = afwImage.ExposureF(mi, wcs)
222
223 exp.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(10**(0.4 * md.getScalar("MAGZERO")), 0))
224 visitInfo = self.makeRawVisitInfo(md=md)
225 exp.getInfo().setVisitInfo(visitInfo)
226 if 'EXPID' in md:
227 exp.info.id = md['EXPID']
228
229 for kw in ('LTV1', 'LTV2'):
230 md.remove(kw)
231
232 exp.setMetadata(md)
233 return exp
234
235 def std_raw(self, item, dataId):
236 """Standardize a raw dataset by converting it to an Exposure.
237
238 Raw images are MEF files with one HDU for each detector.
239
240 Parameters
241 ----------
243 The image read by the butler.
244 dataId : data ID
245 Data identifier.
246
247 Returns
248 -------
249 result : `lsst.afw.image.Exposure`
250 The standardized Exposure.
251 """
252 return self._standardizeExposure(self.exposures['raw'], item, dataId,
253 trimmed=False, setExposureId=True)
254
255 def _createInitialSkyWcs(self, exposure):
256 # DECam has a coordinate system flipped on X with respect to our
257 # VisitInfo definition of the field angle orientation.
258 # We have to override this method until RFC-605 is implemented, to pass
259 # `flipX=True` to createInitialSkyWcs below.
260 self._createSkyWcsFromMetadata(exposure)
261
262 if exposure.getInfo().getVisitInfo() is None:
263 msg = "No VisitInfo; cannot access boresight information. Defaulting to metadata-based SkyWcs."
264 self.log.warn(msg)
265 return
266 try:
267 newSkyWcs = createInitialSkyWcs(exposure.getInfo().getVisitInfo(), exposure.getDetector(),
268 flipX=True)
269 exposure.setWcs(newSkyWcs)
270 except InitialSkyWcsError as e:
271 msg = "Cannot create SkyWcs using VisitInfo and Detector, using metadata-based SkyWcs: %s"
272 self.log.warn(msg, e)
273 self.log.debug("Exception was: %s", traceback.TracebackException.from_exception(e))
274 if e.__context__ is not None:
275 self.log.debug("Root-cause Exception was: %s",
276 traceback.TracebackException.from_exception(e.__context__))
277
278 def std_dark(self, item, dataId):
280 rawPath = self.map_raw(dataId).getLocations()[0]
281 headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", rawPath)
282 md0 = readMetadata(headerPath)
283 fix_header(md0, translator_class=DecamTranslator)
284 visitInfo = self.makeRawVisitInfo(md0)
285 exp.getInfo().setVisitInfo(visitInfo)
286 return self._standardizeExposure(self.calibrations["dark"], exp, dataId, filter=False)
287
288 def std_bias(self, item, dataId):
290 return self._standardizeExposure(self.calibrations["bias"], exp, dataId, filter=False)
291
292 def std_flat(self, item, dataId):
294 return self._standardizeExposure(self.calibrations["flat"], exp, dataId, filter=True)
295
296 def std_illumcor(self, item, dataId):
298 return self._standardizeExposure(self.calibrations["illumcor"], exp, dataId, filter=True)
299
300 def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False):
301 """Standardize a MasterCal image obtained from NOAO archive into Exposure
302
303 These MasterCal images are MEF files with one HDU for each detector.
304 Some WCS header, eg CTYPE1, exists only in the zeroth extensionr,
305 so info in the zeroth header need to be copied over to metadata.
306
307 Parameters
308 ----------
309 datasetType : `str`
310 Dataset type ("bias", "flat", or "illumcor").
312 The image read by the butler.
313 dataId : data ID
314 Data identifier.
315 setFilter : `bool`
316 Whether to set the filter in the Exposure.
317
318 Returns
319 -------
320 result : `lsst.afw.image.Exposure`
321 The standardized Exposure.
322 """
323 mi = afwImage.makeMaskedImage(item.getImage())
324 md = item.getMetadata()
325 masterCalMap = getattr(self, "map_" + datasetType)
326 masterCalPath = masterCalMap(dataId).getLocationsWithRoot()[0]
327 headerPath = re.sub(r'[\[](\d+)[\]]$', "[0]", masterCalPath)
328 md0 = readMetadata(headerPath)
329 fix_header(md0, translator_class=DecamTranslator)
330 for kw in ('CTYPE1', 'CTYPE2', 'CRVAL1', 'CRVAL2', 'CUNIT1', 'CUNIT2',
331 'CD1_1', 'CD1_2', 'CD2_1', 'CD2_2'):
332 if kw in md0.paramNames() and kw not in md.paramNames():
333 md.add(kw, md0.getScalar(kw))
334 wcs = makeSkyWcs(md, strip=True)
335 exp = afwImage.makeExposure(mi, wcs)
336 exp.setMetadata(md)
337 return self._standardizeExposure(self.calibrations[datasetType], exp, dataId, filter=setFilter)
338
339 def std_cpBias(self, item, dataId):
340 return self._standardizeCpMasterCal_standardizeCpMasterCal("cpBias", item, dataId, setFilter=False)
341
342 def std_cpFlat(self, item, dataId):
343 return self._standardizeCpMasterCal_standardizeCpMasterCal("cpFlat", item, dataId, setFilter=True)
344
345 def std_fringe(self, item, dataId):
347 return self._standardizeExposure(self.calibrations["fringe"], exp, dataId)
348
349 def std_cpIllumcor(self, item, dataId):
350 return self._standardizeCpMasterCal_standardizeCpMasterCal("cpIllumcor", item, dataId, setFilter=True)
351
352 @classmethod
354 """Directory containing linearizers"""
355 packageName = cls.getPackageName()
356 packageDir = getPackageDir(packageName)
357 return os.path.join(packageDir, "decam", "linearizer")
358
359 def map_linearizer(self, dataId, write=False):
360 """Map a linearizer"""
361 actualId = self._transformId_transformId(dataId)
362 location = "%02d.fits" % (dataId["ccdnum"])
363 return ButlerLocation(
364 pythonType="lsst.ip.isr.LinearizeSquared",
365 cppType="Config",
366 storageName="PickleStorage",
367 locationList=[location],
368 dataId=actualId,
369 mapper=self,
370 storage=Storage.makeFromURI(self.getLinearizerDirgetLinearizerDir())
371 )
372
373 @classmethod
375 """Directory containing crosstalk tables.
376 """
377 packageName = cls.getPackageName()
378 packageDir = getPackageDir(packageName)
379 return os.path.join(packageDir, "decam", "crosstalk")
int min
A container for an Image and its associated metadata.
Definition: Image.h:430
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
def bypass_deepMergedCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:155
def bypass_deepMergedCoaddId_bits(self, *args, **kwargs)
Definition: decamMapper.py:158
def std_cpIllumcor(self, item, dataId)
Definition: decamMapper.py:349
def std_dark(self, item, dataId)
Definition: decamMapper.py:278
def std_fringe(self, item, dataId)
Definition: decamMapper.py:345
def bypass_dcrMergedCoaddId_bits(self, *args, **kwargs)
Definition: decamMapper.py:170
def bypass_dcrCoaddId_bits(self, *args, **kwargs)
Definition: decamMapper.py:164
def bypass_ccdExposureId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:97
def bypass_deepCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:149
def std_illumcor(self, item, dataId)
Definition: decamMapper.py:296
def bypass_dcrCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:161
def bypass_ccdExposureId_bits(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:100
def bypass_dcrMergedCoaddId(self, datasetType, pythonType, location, dataId)
Definition: decamMapper.py:167
def std_raw(self, item, dataId)
Definition: decamMapper.py:235
def map_linearizer(self, dataId, write=False)
Definition: decamMapper.py:359
def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False)
Definition: decamMapper.py:300
def __init__(self, inputPolicy=None, **kwargs)
Definition: decamMapper.py:60
def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId)
Definition: decamMapper.py:202
def std_bias(self, item, dataId)
Definition: decamMapper.py:288
def _computeCoaddExposureId(self, dataId, singleFilter)
Definition: decamMapper.py:121
def std_flat(self, item, dataId)
Definition: decamMapper.py:292
def std_cpBias(self, item, dataId)
Definition: decamMapper.py:339
def std_cpFlat(self, item, dataId)
Definition: decamMapper.py:342
def bypass_deepCoaddId_bits(self, *args, **kwargs)
Definition: decamMapper.py:152
std::shared_ptr< daf::base::PropertyList > readMetadata(std::string const &fileName, int hdu=DEFAULT_HDU, bool strip=false)
Read FITS header.
Definition: fits.cc:1676
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition: SkyWcs.cc:521
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
std::shared_ptr< PhotoCalib > makePhotoCalibFromCalibZeroPoint(double instFluxMag0, double instFluxMag0Err)
Construct a PhotoCalib from the deprecated Calib-style instFluxMag0/instFluxMag0Err values.
Definition: PhotoCalib.cc:613
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:1240
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:454
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package
Definition: packaging.cc:33