Hide keyboard shortcuts

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"] 

23 

24import os 

25import re 

26 

27from astropy.coordinates import Angle 

28from astropy import units as u 

29 

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 

38 

39 

40class Ctio0m9MakeRawVisitInfo(MakeRawVisitInfo): 

41 """functor to make a VisitInfo from the FITS header of a raw image 

42 """ 

43 

44 def setArgDict(self, md, argDict): 

45 """Fill an argument dict with arguments for makeVisitInfo and pop 

46 associated metadata 

47 

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") 

53 

54 def getDateAvg(self, md, exposureTime): 

55 """Return date at the middle of the exposure 

56 

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) 

65 

66 

67class Ctio0m9Mapper(CameraMapper): 

68 """Mapper class for the 0.9m telescope at CTIO 

69 """ 

70 packageName = 'obs_ctio0m9' 

71 MakeRawVisitInfoClass = Ctio0m9MakeRawVisitInfo 

72 

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 ] 

125 

126 # default no-filter name used for biases and darks - must appear 

127 afwImageUtils.defineFilter('NONE', 0.0, alias=[]) 

128 

129 for pairing in filter_pairings: 

130 afwImageUtils.defineFilter(pairing, 0.0, alias=[]) 

131 

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() 

137 

138 def _extractDetectorName(self, dataId): 

139 return 'SITE2K' 

140 

141 def _computeCcdExposureId(self, dataId): 

142 """Compute the 64-bit (long) identifier for a CCD exposure. 

143 

144 @param dataId (dict) Data identifier with visit 

145 """ 

146 visit = dataId['visit'] 

147 return int(visit) 

148 

149 def bypass_ccdExposureId(self, datasetType, pythonType, location, dataId): 

150 """Hook to retrieve identifier for CCD""" 

151 return self._computeCcdExposureId(dataId) 

152 

153 def bypass_ccdExposureId_bits(self, datasetType, pythonType, location, dataId): 

154 """Hook to retrieve number of bits in identifier for CCD""" 

155 return 32 

156 

157 def std_raw_md(self, md, dataId): 

158 """Method for performing any necessary sanitization of metadata. 

159 

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 

166 

167 def std_raw(self, item, dataId): 

168 """Method for performing any necessary manipulation of the raw files. 

169 

170 @param[in,out] item afwImage exposure object with associated metadata 

171 and detector info 

172 @param[in] dataId 

173 """ 

174 md = item.getMetadata() 

175 

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) 

187 

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() 

200 

201 if rawBBoxFromMetadata != rawBBox: 

202 extraSerialOverscan = rawBBoxFromMetadata.getWidth() - rawBBox.getWidth() # extra overscan pixels 

203 extraParallelOverscan = rawBBoxFromMetadata.getHeight() - rawBBox.getHeight() # vertical 

204 

205 ccd = cameraGeom.copyDetector(ccd, ampInfoCatalog=ccd.getAmpInfoCatalog().copy(deep=True)) 

206 item.setDetector(ccd) 

207 

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))) 

213 

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() 

221 

222 rawHorizontalOverscanBBox.shift(afwGeom.ExtentI((ix - 1)*extraSerialOverscan, 

223 (iy - 1)*extraParallelOverscan)) 

224 

225 xy0 = rawHorizontalOverscanBBox.getMin() 

226 xy1 = rawHorizontalOverscanBBox.getMax() 

227 

228 xy1.shift(afwGeom.ExtentI(extraSerialOverscan, extraParallelOverscan)) 

229 

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)) 

238 

239 xy0 = rawPrescanBBox.getMin() 

240 xy1 = rawPrescanBBox.getMax() 

241 

242 xy1.shift(afwGeom.ExtentI(0, extraParallelOverscan)) 

243 a.setRawPrescanBBox(afwGeom.BoxI(xy0, xy1)) 

244 

245 return item 

246 

247 def std_dark(self, item, dataId): 

248 """Standardiation of master dark frame. Must only be called on master 

249 darks. 

250 

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 

260 

261 

262def sanitize_date(md): 

263 '''Take a metadata object, fix corrupted dates in DATE-OBS field, and 

264 return the fixed md object. 

265 

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 

268 

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 = "" 

281 

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