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#!/Users/square/j/ws/release/tarball/59a01628fc/build/conda/miniconda3-4.7.12/envs/lsst-scipipe-1a1d771/bin/python # noqa 

2 

3# LSST Data Management System 

4# Copyright 2008-2016 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22 

23from __future__ import division 

24from __future__ import print_function 

25 

26import os.path 

27import sys 

28 

29import numpy as np 

30 

31import lsst.daf.persistence as dafPersist 

32import lsst.pipe.base as pipeBase 

33import lsst.afw.table as afwTable 

34import lsst.geom as geom 

35 

36 

37def loadAndMatchData(repo, visits, fields, ref, ref_field, camcol, filter): 

38 """Load data from specific visit+field pairs. Match with reference. 

39 

40 @param repo The repository. This is generally the directory on disk 

41 that contains the repository and mapper. 

42 @param visits 'runs' in SDSS nomenclature. List. 

43 @param fields Field within a camcol. List. 

44 @param ref The run of the reference image set. Scalar. 

45 @param ref_field The field of the reference image set. Scalar. 

46 @param camcol Camera column to use. List. 

47 @param filter Name of the filter. Scalar 

48 

49 Return a pipeBase.Struct with mag, dist, and number of matches. 

50 

51 Notes: {visit, filter, field, camcol} are sufficient to unique specfiy 

52 a data ID for the Butler in the obs_sdss camera mapping. 

53 """ 

54 

55 flags = ["base_PixelFlags_flag_saturated", "base_PixelFlags_flag_cr", "base_PixelFlags_flag_interpolated", 

56 "base_PsfFlux_flag_edge"] 

57 

58 # setup butler 

59 butler = dafPersist.Butler(repo) 

60 

61 for indx, c in enumerate(camcol): 

62 dataid = {'run': ref, 'filter': filter, 'field': ref_field, 'camcol': c} 

63 oldSrc = butler.get('src', dataid, immediate=True) 

64 print(len(oldSrc), "sources in camcol :", c) 

65 if indx == 0: 65 ↛ 78line 65 didn't jump to line 78, because the condition on line 65 was never false

66 # retrieve the schema of the source catalog and extend it in order 

67 # to add a field to record the camcol number 

68 oldSchema = oldSrc.getSchema() 

69 mapper = afwTable.SchemaMapper(oldSchema) 

70 mapper.addMinimalSchema(oldSchema) 

71 mapper.addOutputField(afwTable.Field[np.int32]("camcol", "camcol number")) 

72 newSchema = mapper.getOutputSchema() 

73 

74 # create the new extented source catalog 

75 srcRef = afwTable.SourceCatalog(newSchema) 

76 

77 # create temporary catalog 

78 tmpCat = afwTable.SourceCatalog(srcRef.table) 

79 tmpCat.extend(oldSrc, mapper=mapper) 

80 # fill in the camcol information in numpy mode in order to be efficient 

81 tmpCat['camcol'][:] = c 

82 # append the temporary catalog to the extended source catalog 

83 srcRef.extend(tmpCat, deep=False) 

84 

85 print(len(srcRef), "Sources in reference visit :", ref) 

86 

87 mag = [] 

88 dist = [] 

89 for v, f in zip(visits, fields): 

90 if v == ref: 

91 continue 

92 for indx, c in enumerate(camcol): 

93 dataid = {'run': v, 'filter': filter, 'field': f, 'camcol': c} 

94 if indx == 0: 94 ↛ 97line 94 didn't jump to line 97, because the condition on line 94 was never false

95 srcVis = butler.get('src', dataid, immediate=True) 

96 else: 

97 srcVis.extend(butler.get('src', dataid, immediate=True), False) 

98 print(len(srcVis), "sources in camcol : ", c) 

99 

100 match = afwTable.matchRaDec(srcRef, srcVis, geom.Angle(1, geom.arcseconds)) 

101 matchNum = len(match) 

102 print("Visit :", v, matchNum, "matches found") 

103 

104 schemaRef = srcRef.getSchema() 

105 schemaVis = srcVis.getSchema() 

106 extRefKey = schemaRef["base_ClassificationExtendedness_value"].asKey() 

107 extVisKey = schemaVis["base_ClassificationExtendedness_value"].asKey() 

108 flagKeysRef = [] 

109 flagKeysVis = [] 

110 for fl in flags: 

111 keyRef = schemaRef[fl].asKey() 

112 flagKeysRef.append(keyRef) 

113 keyVis = schemaVis[fl].asKey() 

114 flagKeysVis.append(keyVis) 

115 

116 for m in match: 

117 mRef = m.first 

118 mVis = m.second 

119 

120 for fl in flagKeysRef: 

121 if mRef.get(fl): 

122 continue 

123 for fl in flagKeysVis: 

124 if mVis.get(fl): 

125 continue 

126 

127 # cleanup the reference sources in order to keep only decent star-like objects 

128 if mRef.get(extRefKey) >= 1.0 or mVis.get(extVisKey) >= 1.0: 

129 continue 

130 

131 ang = geom.radToMas(m.distance) 

132 

133 # retrieve the camcol corresponding to the reference source 

134 camcolRef = mRef.get('camcol') 

135 # retrieve the calibration object associated to the camcol 

136 did = {'run': ref, 'filter': filter, 'field': ref_field, 'camcol': camcolRef} 

137 photoCalib = butler.get("calexp_photoCalib", did) 

138 # compute magnitude 

139 refMag = photoCalib.instFluxToMagnitude(mRef.get('base_PsfFlux_instFlux')) 

140 

141 mag.append(refMag) 

142 dist.append(ang) 

143 

144 return pipeBase.Struct( 

145 mag=mag, 

146 dist=dist, 

147 match=matchNum 

148 ) 

149 

150 

151def plotAstrometry(mag, dist, match, good_mag_limit=19.5): 

152 """Plot angular distance between matched sources from different exposures.""" 

153 

154 # Defer importing of matplotlib until we need it. 

155 import matplotlib.pylab as plt 

156 

157 plt.rcParams['axes.linewidth'] = 2 

158 plt.rcParams['mathtext.default'] = 'regular' 

159 

160 fig, ax = plt.subplots(ncols=2, nrows=3, figsize=(18, 22)) 

161 ax[0][0].hist(dist, bins=80) 

162 ax[0][1].scatter(mag, dist, s=10, color='b') 

163 ax[0][0].set_xlim([0., 900.]) 

164 ax[0][0].set_xlabel("Distance in mas", fontsize=20) 

165 ax[0][0].tick_params(labelsize=20) 

166 ax[0][0].set_title("Median : %.1f mas" % (np.median(dist)), fontsize=20, x=0.6, y=0.88) 

167 ax[0][1].set_xlabel("Magnitude", fontsize=20) 

168 ax[0][1].set_ylabel("Distance in mas", fontsize=20) 

169 ax[0][1].set_ylim([0., 900.]) 

170 ax[0][1].tick_params(labelsize=20) 

171 ax[0][1].set_title("Number of matches : %d" % match, fontsize=20) 

172 

173 ax[1][0].hist(dist, bins=150) 

174 ax[1][0].set_xlim([0., 400.]) 

175 ax[1][1].scatter(mag, dist, s=10, color='b') 

176 ax[1][1].set_ylim([0., 400.]) 

177 ax[1][0].set_xlabel("Distance in mas", fontsize=20) 

178 ax[1][1].set_xlabel("Magnitude", fontsize=20) 

179 ax[1][1].set_ylabel("Distance in mas", fontsize=20) 

180 ax[1][0].tick_params(labelsize=20) 

181 ax[1][1].tick_params(labelsize=20) 

182 

183 idxs = np.where(np.asarray(mag) < good_mag_limit) 

184 

185 ax[2][0].hist(np.asarray(dist)[idxs], bins=100) 

186 ax[2][0].set_xlabel("Distance in mas - mag < %.1f" % good_mag_limit, fontsize=20) 

187 ax[2][0].set_xlim([0, 200]) 

188 ax[2][0].set_title("Median (mag < %.1f) : %.1f mas" % 

189 (good_mag_limit, np.median(np.asarray(dist)[idxs])), 

190 fontsize=20, x=0.6, y=0.88) 

191 ax[2][1].scatter(np.asarray(mag)[idxs], np.asarray(dist)[idxs], s=10, color='b') 

192 ax[2][1].set_xlabel("Magnitude", fontsize=20) 

193 ax[2][1].set_ylabel("Distance in mas - mag < %.1f" % good_mag_limit, fontsize=20) 

194 ax[2][1].set_ylim([0., 200.]) 

195 ax[2][0].tick_params(labelsize=20) 

196 ax[2][1].tick_params(labelsize=20) 

197 

198 plt.suptitle("Astrometry Check", fontsize=30) 

199 plotPath = os.path.join("astrometry_sdss.png") 

200 plt.savefig(plotPath, format="png") 

201 

202 

203def checkAstrometry(mag, dist, match, 

204 good_mag_limit=19.5, 

205 medianRef=100, matchRef=500): 

206 """Print out the astrometric scatter for all stars, and for good stars. 

207 

208 @param medianRef Median reference astrometric scatter in arcseconds. 

209 @param matchRef Should match at least matchRef stars. 

210 

211 Return a boolean indicating whether the astrometric scatter was less than 

212 the supplied reference, and the astrometric scatter (RMS, arcsec) for all 

213 good stars. 

214 

215 Notes: 

216 The scatter and match defaults are appropriate to SDSS are stored here. 

217 For SDSS, stars with mag < 19.5 should be completely well measured. 

218 """ 

219 

220 print("Median value of the astrometric scatter - all magnitudes:", 

221 np.median(dist), "mas") 

222 

223 idxs = np.where(np.asarray(mag) < good_mag_limit) 

224 astromScatter = np.median(np.asarray(dist)[idxs]) 

225 print("Astrometric scatter (median) - mag < %.1f :" % good_mag_limit, astromScatter, "mas") 

226 

227 if astromScatter > medianRef: 227 ↛ 228line 227 didn't jump to line 228, because the condition on line 227 was never true

228 print("Median astrometric scatter %.1f mas is larger than reference : %.1f mas " % 

229 (astromScatter, medianRef)) 

230 passed = False 

231 else: 

232 passed = True 

233 if match < matchRef: 233 ↛ 234line 233 didn't jump to line 234, because the condition on line 233 was never true

234 print("Number of matched sources %d is too small (should be > %d)" % (match, matchRef)) 

235 passed = False 

236 

237 return passed, astromScatter 

238 

239 

240def main(repo, runs, fields, ref, ref_field, camcol, filter, plot=False): 

241 """Main executable. 

242 

243 Returns True if the test passed, False otherwise. 

244 """ 

245 

246 struct = loadAndMatchData(repo, runs, fields, ref, ref_field, camcol, filter) 

247 mag = struct.mag 

248 dist = struct.dist 

249 match = struct.match 

250 

251 # Limit depends on filter 

252 medianRef = 100 

253 if filter == 'i': 253 ↛ 256line 253 didn't jump to line 256, because the condition on line 253 was never false

254 medianRef = 105 

255 

256 passed, astromScatter = checkAstrometry(mag, dist, match, medianRef=medianRef) 

257 if plot: 257 ↛ 258line 257 didn't jump to line 258, because the condition on line 257 was never true

258 plotAstrometry(mag, dist, match) 

259 return passed 

260 

261 

262def defaultData(repo): 

263 # List of SDSS runs to be considered 

264 runs = [4192, 6377] 

265 fields = [300, 399] 

266 

267 # Reference visit (the other visits will be compared to this one 

268 ref = 4192 

269 ref_field = 300 

270 

271 # List of camcol to be considered (source calalogs will be concateneted) 

272 camcol = [4] 

273 filter = 'i' 

274 

275 return runs, fields, ref, ref_field, camcol, filter 

276 

277 

278if __name__ == "__main__": 278 ↛ exitline 278 didn't exit the module, because the condition on line 278 was never false

279 if len(sys.argv) != 2: 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true

280 print("""Usage: check_astrometry repo 

281where repo is the path to a repository containing the output of processCcd 

282""") 

283 sys.exit(1) 

284 

285 repo = sys.argv[1] 

286 if not os.path.isdir(repo): 286 ↛ 287line 286 didn't jump to line 287, because the condition on line 286 was never true

287 print("Could not find repo %r" % (repo,)) 

288 sys.exit(1) 

289 

290 runs, fields, ref, ref_field, camcol, filter = defaultData(repo) 

291 passed = main(repo, runs, fields, ref, ref_field, camcol, filter) 

292 if passed: 292 ↛ 295line 292 didn't jump to line 295, because the condition on line 292 was never false

293 print("Ok.") 

294 else: 

295 sys.exit(1)