Coverage for python/lsst/obs/ctio0m9/ctio0m9Mapper.py : 25%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# LSST Data Management System
3# Copyright 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__all__ = ["Ctio0m9Mapper"]
24import os
25import re
27from astropy.coordinates import Angle
28from astropy import units as u
30import lsst.afw.image as afwImage
31import lsst.afw.image.utils as afwImageUtils
32import lsst.afw.geom as afwGeom
33import lsst.afw.cameraGeom as cameraGeom
34from lsst.obs.base import CameraMapper, MakeRawVisitInfo, bboxFromIraf, exposureFromImage
35import lsst.daf.base as dafBase
36from lsst.daf.persistence import Policy
37from lsst.obs.ctio0m9 import makeCamera
40class Ctio0m9MakeRawVisitInfo(MakeRawVisitInfo):
41 """functor to make a VisitInfo from the FITS header of a raw image
42 """
44 def setArgDict(self, md, argDict):
45 """Fill an argument dict with arguments for makeVisitInfo and pop
46 associated metadata
48 @param[in] md image metadata
49 @param[in, out] md the argument dictionary for modification
50 """
51 super(Ctio0m9MakeRawVisitInfo, self).setArgDict(md, argDict)
52 argDict["darkTime"] = md.getScalar("DARKTIME")
54 def getDateAvg(self, md, exposureTime):
55 """Return date at the middle of the exposure
57 @param[in,out] md metadata, as an lsst.daf.base.PropertyList or
58 PropertySet; items that are used are stripped from the metadata
59 (except TIMESYS, because it may apply to more than one other
60 keyword).
61 @param[in] exposureTime exposure time (sec)
62 """
63 dateObs = self.popIsoDate(md, "DATE-OBS")
64 return self.offsetDate(dateObs, 0.5*exposureTime)
67class Ctio0m9Mapper(CameraMapper):
68 """Mapper class for the 0.9m telescope at CTIO
69 """
70 packageName = 'obs_ctio0m9'
71 MakeRawVisitInfoClass = Ctio0m9MakeRawVisitInfo
73 def __init__(self, inputPolicy=None, **kwargs):
74 policyFile = Policy.defaultPolicyFile(self.packageName, "ctio0m9Mapper.yaml", "policy")
75 policy = Policy(policyFile)
76 CameraMapper.__init__(self, policy, os.path.dirname(policyFile), **kwargs)
77 filter_pairings = ['NONE+SEMROCK', # list of all filter pairings found in data
78 'NONE+RONCHI200',
79 'RONCHI200+SEMROCK',
80 'NONE+NONE',
81 'NONE+g',
82 'NONE+r',
83 'NONE+i',
84 'NONE+z',
85 'RONCHI200+z',
86 'RONCHI200+g',
87 'FGB37+RONCHI200',
88 'NONE+RONCHI400',
89 'FGC715S+RONCHI400',
90 'FGC715S+RONCHI200',
91 'RONCHI400+ZG',
92 'Thor300+ZG',
93 'HoloPhP+ZG',
94 'HoloPhAg+ZG',
95 'HoloAmAg+ZG',
96 'HoloPhAg+NONE',
97 'Halfa+RONCHI400',
98 'Halfa+Thor300',
99 'Halfa+HoloPhP',
100 'Halfa+HoloPhAg',
101 'Halfa+HoloAmAg',
102 'HoloAmAg+NONE',
103 'FGB37+RONCHI400',
104 'FGB37+HoloPhP',
105 'FGB37+Thor300',
106 'FGB37+HoloPhAg',
107 'FGB37+HoloAmAg',
108 'NONE+Thor300',
109 'HoloPhP+NONE',
110 'HoloPhAg+RG715',
111 'RG715+Thor300',
112 'HoloPhP+RG715',
113 'RG715+RONCHI400',
114 'HoloAmAg+RG715',
115 'NONE+cb',
116 'NONE+RG715',
117 'FGB37+NONE',
118 'NONE+ZG',
119 'NONE+Halfa',
120 'NONE+z',
121 'NONE+f5025/1023',
122 'RG715+RONCHI200',
123 'FGB37+RONCHI200'
124 ]
126 # default no-filter name used for biases and darks - must appear
127 afwImageUtils.defineFilter('NONE', 0.0, alias=[])
129 for pairing in filter_pairings:
130 afwImageUtils.defineFilter(pairing, 0.0, alias=[])
132 def _makeCamera(self, policy, repositoryDir):
133 """Make a camera (instance of lsst.afw.cameraGeom.Camera) describing
134 the camera geometry
135 """
136 return makeCamera()
138 def _extractDetectorName(self, dataId):
139 return 'SITE2K'
141 def _computeCcdExposureId(self, dataId):
142 """Compute the 64-bit (long) identifier for a CCD exposure.
144 @param dataId (dict) Data identifier with visit
145 """
146 visit = dataId['visit']
147 return int(visit)
149 def bypass_ccdExposureId(self, datasetType, pythonType, location, dataId):
150 """Hook to retrieve identifier for CCD"""
151 return self._computeCcdExposureId(dataId)
153 def bypass_ccdExposureId_bits(self, datasetType, pythonType, location, dataId):
154 """Hook to retrieve number of bits in identifier for CCD"""
155 return 32
157 def std_raw_md(self, md, dataId):
158 """Method for performing any necessary sanitization of metadata.
160 @param[in,out] md metadata, as an lsst.daf.base.PropertyList or
161 PropertySet, to be sanitized
162 @param[in] dataId unused
163 """
164 md = sanitize_date(md)
165 return md
167 def std_raw(self, item, dataId):
168 """Method for performing any necessary manipulation of the raw files.
170 @param[in,out] item afwImage exposure object with associated metadata
171 and detector info
172 @param[in] dataId
173 """
174 md = item.getMetadata()
176 # Note that setting these must be done before the call to super below
177 md.set('CTYPE1', 'RA---TAN') # add missing keywords
178 md.set('CTYPE2', 'DEC--TAN') # add missing keywords
179 md.set('CRVAL2', Angle(md.getScalar('DEC'), unit=u.deg).degree) # translate RA/DEC from header
180 md.set('CRVAL1', Angle(md.getScalar('RA'), unit=u.hour).degree)
181 md.set('CRPIX1', 210.216) # set reference pixels
182 md.set('CRPIX2', 344.751)
183 md.set('CD1_1', -0.000111557869436) # set nominal CD matrix
184 md.set('CD1_2', 1.09444409144E-07)
185 md.set('CD2_1', 6.26180926869E-09)
186 md.set('CD2_2', -0.000111259259893)
188 item = super(Ctio0m9Mapper, self).std_raw(item, dataId)
189 #
190 # We may need to hack up the cameraGeom
191 #
192 # There doesn't seem to be a way to get the extended register, so I
193 # don't update it.
194 # We could do this by detecting extra overscan and adjusting things
195 # cleverly; probably we need to so so.
196 #
197 ccd = item.getDetector()
198 rawBBoxFromMetadata = bboxFromIraf(md.getScalar("ASEC11"))
199 rawBBox = ccd[0].getRawBBox()
201 if rawBBoxFromMetadata != rawBBox:
202 extraSerialOverscan = rawBBoxFromMetadata.getWidth() - rawBBox.getWidth() # extra overscan pixels
203 extraParallelOverscan = rawBBoxFromMetadata.getHeight() - rawBBox.getHeight() # vertical
205 ccd = cameraGeom.copyDetector(ccd, ampInfoCatalog=ccd.getAmpInfoCatalog().copy(deep=True))
206 item.setDetector(ccd)
208 for a in ccd:
209 ix, iy = [int(_) for _ in a.getName()]
210 irafName = "%d%d" % (iy, ix)
211 a.setRawBBox(bboxFromIraf(md.getScalar("ASEC%s" % irafName)))
212 a.setRawDataBBox(bboxFromIraf(md.getScalar("TSEC%s" % irafName)))
214 if extraSerialOverscan != 0 or extraParallelOverscan != 0:
215 #
216 # the number of overscan pixels has been changed from camera.yaml
217 #
218 # First adjust the overscan
219 #
220 rawHorizontalOverscanBBox = a.getRawHorizontalOverscanBBox()
222 rawHorizontalOverscanBBox.shift(afwGeom.ExtentI((ix - 1)*extraSerialOverscan,
223 (iy - 1)*extraParallelOverscan))
225 xy0 = rawHorizontalOverscanBBox.getMin()
226 xy1 = rawHorizontalOverscanBBox.getMax()
228 xy1.shift(afwGeom.ExtentI(extraSerialOverscan, extraParallelOverscan))
230 a.setRawHorizontalOverscanBBox(afwGeom.BoxI(xy0, xy1))
231 #
232 # And now move the extended register to allow for the extra
233 # overscan pixels
234 #
235 rawPrescanBBox = a.getRawPrescanBBox()
236 rawPrescanBBox.shift(afwGeom.ExtentI(2*(ix - 1)*extraSerialOverscan,
237 (iy - 1)*extraParallelOverscan))
239 xy0 = rawPrescanBBox.getMin()
240 xy1 = rawPrescanBBox.getMax()
242 xy1.shift(afwGeom.ExtentI(0, extraParallelOverscan))
243 a.setRawPrescanBBox(afwGeom.BoxI(xy0, xy1))
245 return item
247 def std_dark(self, item, dataId):
248 """Standardiation of master dark frame. Must only be called on master
249 darks.
251 @param[in,out] item the master dark, as an image-like object
252 @param[in] dataId unused
253 """
254 exp = exposureFromImage(item)
255 if not exp.getInfo().hasVisitInfo():
256 # hard-coded, but pipe_drivers always(?) normalises darks to a
257 # darktime of 1s so this is OK?
258 exp.getInfo().setVisitInfo(afwImage.VisitInfo(darkTime=1.0))
259 return exp
262def sanitize_date(md):
263 '''Take a metadata object, fix corrupted dates in DATE-OBS field, and
264 return the fixed md object.
266 We see corrupted dates like "2016-03-06T08:53:3.198" (should be 53:03.198);
267 fix these when they make dafBase.DateTime unhappy
269 @param md metadata in, to be fixed
270 @return md metadata returned, with DATE-OBS fixed
271 '''
272 date_obs = md.getScalar('DATE-OBS')
273 try: # see if compliant. Don't use, just a test with dafBase
274 dafBase.DateTime(date_obs, dafBase.DateTime.TAI)
275 except Exception: # if bad, sanitise
276 year, month, day, h, m, s = re.split(r"[-:T]", date_obs)
277 if re.search(r"[A-Z]$", s):
278 s, TZ = s[:-1], s[-1]
279 else:
280 TZ = ""
282 date_obs = "%4d-%02d-%02dT%02d:%02d:%06.3f%s" % (int(year), int(month), int(day),
283 int(h), int(m), float(s), TZ)
284 md.set('DATE-OBS', date_obs) # put santized version back
285 return md