29 from astro_metadata_translator
import fix_header, DecamTranslator
36 from .makeDecamRawVisitInfo
import MakeDecamRawVisitInfo
37 from .decamFilters
import DECAM_FILTER_DEFINITIONS
38 from ._instrument
import DarkEnergyCamera
40 np.seterr(divide=
"ignore")
42 __all__ = [
"DecamMapper"]
46 packageName =
'obs_decam'
47 _gen3instrument = DarkEnergyCamera
49 MakeRawVisitInfoClass = MakeDecamRawVisitInfo
52 1:
'S29', 2:
'S30', 3:
'S31', 4:
'S25', 5:
'S26', 6:
'S27', 7:
'S28', 8:
'S20', 9:
'S21',
53 10:
'S22', 11:
'S23', 12:
'S24', 13:
'S14', 14:
'S15', 15:
'S16', 16:
'S17', 17:
'S18',
54 18:
'S19', 19:
'S8', 20:
'S9', 21:
'S10', 22:
'S11', 23:
'S12', 24:
'S13', 25:
'S1', 26:
'S2',
55 27:
'S3', 28:
'S4', 29:
'S5', 30:
'S6', 31:
'S7', 32:
'N1', 33:
'N2', 34:
'N3', 35:
'N4',
56 36:
'N5', 37:
'N6', 38:
'N7', 39:
'N8', 40:
'N9', 41:
'N10', 42:
'N11', 43:
'N12', 44:
'N13',
57 45:
'N14', 46:
'N15', 47:
'N16', 48:
'N17', 49:
'N18', 50:
'N19', 51:
'N20', 52:
'N21',
58 53:
'N22', 54:
'N23', 55:
'N24', 56:
'N25', 57:
'N26', 58:
'N27', 59:
'N28', 60:
'N29',
61 def __init__(self, inputPolicy=None, **kwargs):
62 policyFile = Policy.defaultPolicyFile(self.
packageName,
"DecamMapper.yaml",
"policy")
63 policy =
Policy(policyFile)
65 super(DecamMapper, self).
__init__(policy, os.path.dirname(policyFile), **kwargs)
67 DECAM_FILTER_DEFINITIONS.defineFilters()
74 for datasetType
in (
"raw",
"instcal",
"dqmask",
"wtmap",
"cpIllumcor"):
75 self.
mappings[datasetType].keyDict.update({
'ccdnum': int})
76 self.
mappings[
"raw"].keyDict.update({
'object': str})
80 DecamMapper._nbit_tract = 10
81 DecamMapper._nbit_patch = 10
82 DecamMapper._nbit_filter = 4
83 DecamMapper._nbit_id = 64 - (DecamMapper._nbit_tract
84 + 2*DecamMapper._nbit_patch
85 + DecamMapper._nbit_filter)
87 def _extractDetectorName(self, dataId):
90 return DecamMapper.detectorNames[copyId[
'ccdnum']]
92 raise RuntimeError(
"No name found for dataId: %s"%(dataId))
94 def _transformId(self, dataId):
95 copyId = CameraMapper._transformId(self, dataId)
97 copyId.setdefault(
"ccdnum", copyId[
"ccd"])
106 def _computeCcdExposureId(self, dataId):
107 """Compute the 64-bit (long) identifier for a CCD exposure.
112 Data identifier with visit, ccd.
117 Integer identifier for a CCD exposure.
120 visit = copyId[
'visit']
121 ccdnum = copyId[
'ccdnum']
122 return int(
"%07d%02d" % (visit, ccdnum))
124 def _computeCoaddExposureId(self, dataId, singleFilter):
125 """Compute the 64-bit (long) identifier for a coadd.
130 Data identifier with tract and patch.
131 singleFilter : `bool`
132 True means the desired ID is for a single-filter coadd,
133 in which case the dataId must contain filter.
138 Unique integer identifier.
140 tract = int(dataId[
'tract'])
141 if tract < 0
or tract >= 2**DecamMapper._nbit_tract:
142 raise RuntimeError(
'tract not in range [0,%d)' % (2**DecamMapper._nbit_tract))
143 patchX, patchY = [int(x)
for x
in dataId[
'patch'].split(
',')]
144 for p
in (patchX, patchY):
145 if p < 0
or p >= 2**DecamMapper._nbit_patch:
146 raise RuntimeError(
'patch component not in range [0, %d)' % 2**DecamMapper._nbit_patch)
147 oid = (((tract << DecamMapper._nbit_patch) + patchX) << DecamMapper._nbit_patch) + patchY
149 return (oid << DecamMapper._nbit_filter) +
afwImage.Filter(dataId[
'filter']).getId()
156 return 64 - DecamMapper._nbit_id
162 return 64 - DecamMapper._nbit_id
179 dqmArr = dqmask.getArray()
181 mArr = mask.getArray()
182 idxBad = np.where(dqmArr & 1)
183 idxSat = np.where(dqmArr & 2)
184 idxIntrp = np.where(dqmArr & 4)
185 idxCr = np.where(dqmArr & 16)
186 idxBleed = np.where(dqmArr & 64)
187 idxEdge = np.where(dqmArr & 512)
188 mArr[idxBad] |= mask.getPlaneBitMask(
"BAD")
189 mArr[idxSat] |= mask.getPlaneBitMask(
"SAT")
190 mArr[idxIntrp] |= mask.getPlaneBitMask(
"INTRP")
191 mArr[idxCr] |= mask.getPlaneBitMask(
"CR")
192 mArr[idxBleed] |= mask.getPlaneBitMask(
"SAT")
193 mArr[idxEdge] |= mask.getPlaneBitMask(
"EDGE")
197 wtmArr = wtmap.getArray()
198 idxUndefWeight = np.where(wtmArr <= 0)
200 wtmArr[idxUndefWeight] =
min(1e-14, np.min(wtmArr[np.where(wtmArr > 0)]))
202 varim = afwImage.ImageF(var)
207 instcalMap = self.map_instcal(dataId)
208 dqmaskMap = self.map_dqmask(dataId)
209 wtmapMap = self.map_wtmap(dataId)
210 instcalType = getattr(afwImage, instcalMap.getPythonType().split(
".")[-1])
211 dqmaskType = getattr(afwImage, dqmaskMap.getPythonType().split(
".")[-1])
212 wtmapType = getattr(afwImage, wtmapMap.getPythonType().split(
".")[-1])
213 instcal = instcalType(instcalMap.getLocationsWithRoot()[0])
214 dqmask = dqmaskType(dqmaskMap.getLocationsWithRoot()[0])
215 wtmap = wtmapType(wtmapMap.getLocationsWithRoot()[0])
220 mi = afwImage.MaskedImageF(afwImage.ImageF(instcal.getImage()), mask, variance)
222 fix_header(md, translator_class=DecamTranslator)
224 exp = afwImage.ExposureF(mi, wcs)
228 exp.getInfo().setVisitInfo(visitInfo)
230 for kw
in (
'LTV1',
'LTV2'):
237 """Standardize a raw dataset by converting it to an Exposure.
239 Raw images are MEF files with one HDU for each detector.
243 item : `lsst.afw.image.DecoratedImage`
244 The image read by the butler.
250 result : `lsst.afw.image.Exposure`
251 The standardized Exposure.
256 def _createInitialSkyWcs(self, exposure):
263 if exposure.getInfo().getVisitInfo()
is None:
264 msg =
"No VisitInfo; cannot access boresight information. Defaulting to metadata-based SkyWcs."
270 exposure.setWcs(newSkyWcs)
271 except InitialSkyWcsError
as e:
272 msg =
"Cannot create SkyWcs using VisitInfo and Detector, using metadata-based SkyWcs: %s"
274 self.
log.
debug(
"Exception was: %s", traceback.TracebackException.from_exception(e))
275 if e.__context__
is not None:
276 self.
log.
debug(
"Root-cause Exception was: %s",
277 traceback.TracebackException.from_exception(e.__context__))
281 rawPath = self.map_raw(dataId).getLocations()[0]
282 headerPath = re.sub(
r'[\[](\d+)[\]]$',
"[0]", rawPath)
284 fix_header(md0, translator_class=DecamTranslator)
286 exp.getInfo().setVisitInfo(visitInfo)
301 def _standardizeCpMasterCal(self, datasetType, item, dataId, setFilter=False):
302 """Standardize a MasterCal image obtained from NOAO archive into Exposure
304 These MasterCal images are MEF files with one HDU for each detector.
305 Some WCS header, eg CTYPE1, exists only in the zeroth extensionr,
306 so info in the zeroth header need to be copied over to metadata.
311 Dataset type ("bias", "flat", or "illumcor").
312 item : `lsst.afw.image.DecoratedImage`
313 The image read by the butler.
317 Whether to set the filter in the Exposure.
321 result : `lsst.afw.image.Exposure`
322 The standardized Exposure.
325 md = item.getMetadata()
326 masterCalMap = getattr(self,
"map_" + datasetType)
327 masterCalPath = masterCalMap(dataId).getLocationsWithRoot()[0]
328 headerPath = re.sub(
r'[\[](\d+)[\]]$',
"[0]", masterCalPath)
330 fix_header(md0, translator_class=DecamTranslator)
331 for kw
in (
'CTYPE1',
'CTYPE2',
'CRVAL1',
'CRVAL2',
'CUNIT1',
'CUNIT2',
332 'CD1_1',
'CD1_2',
'CD2_1',
'CD2_2'):
333 if kw
in md0.paramNames()
and kw
not in md.paramNames():
334 md.add(kw, md0.getScalar(kw))
355 """Directory containing linearizers"""
358 return os.path.join(packageDir,
"decam",
"linearizer")
361 """Map a linearizer"""
363 location =
"%02d.fits" % (dataId[
"ccdnum"])
365 pythonType=
"lsst.ip.isr.LinearizeSquared",
367 storageName=
"PickleStorage",
368 locationList=[location],
376 """Directory containing crosstalk tables.
380 return os.path.join(packageDir,
"decam",
"crosstalk")