Coverage for bin/check_astrometry.py : 70%

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
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/>.
23from __future__ import division
24from __future__ import print_function
26import os.path
27import sys
29import numpy as np
31import lsst.daf.persistence as dafPersist
32import lsst.pipe.base as pipeBase
33import lsst.afw.table as afwTable
34import lsst.geom as geom
37def loadAndMatchData(repo, visits, fields, ref, ref_field, camcol, filter):
38 """Load data from specific visit+field pairs. Match with reference.
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
49 Return a pipeBase.Struct with mag, dist, and number of matches.
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 """
55 flags = ["base_PixelFlags_flag_saturated", "base_PixelFlags_flag_cr", "base_PixelFlags_flag_interpolated",
56 "base_PsfFlux_flag_edge"]
58 # setup butler
59 butler = dafPersist.Butler(repo)
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()
74 # create the new extented source catalog
75 srcRef = afwTable.SourceCatalog(newSchema)
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)
85 print(len(srcRef), "Sources in reference visit :", ref)
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)
100 match = afwTable.matchRaDec(srcRef, srcVis, geom.Angle(1, geom.arcseconds))
101 matchNum = len(match)
102 print("Visit :", v, matchNum, "matches found")
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)
116 for m in match:
117 mRef = m.first
118 mVis = m.second
120 for fl in flagKeysRef:
121 if mRef.get(fl):
122 continue
123 for fl in flagKeysVis:
124 if mVis.get(fl):
125 continue
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
131 ang = geom.radToMas(m.distance)
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'))
141 mag.append(refMag)
142 dist.append(ang)
144 return pipeBase.Struct(
145 mag=mag,
146 dist=dist,
147 match=matchNum
148 )
151def plotAstrometry(mag, dist, match, good_mag_limit=19.5):
152 """Plot angular distance between matched sources from different exposures."""
154 # Defer importing of matplotlib until we need it.
155 import matplotlib.pylab as plt
157 plt.rcParams['axes.linewidth'] = 2
158 plt.rcParams['mathtext.default'] = 'regular'
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)
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)
183 idxs = np.where(np.asarray(mag) < good_mag_limit)
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)
198 plt.suptitle("Astrometry Check", fontsize=30)
199 plotPath = os.path.join("astrometry_sdss.png")
200 plt.savefig(plotPath, format="png")
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.
208 @param medianRef Median reference astrometric scatter in arcseconds.
209 @param matchRef Should match at least matchRef stars.
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.
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 """
220 print("Median value of the astrometric scatter - all magnitudes:",
221 np.median(dist), "mas")
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")
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
237 return passed, astromScatter
240def main(repo, runs, fields, ref, ref_field, camcol, filter, plot=False):
241 """Main executable.
243 Returns True if the test passed, False otherwise.
244 """
246 struct = loadAndMatchData(repo, runs, fields, ref, ref_field, camcol, filter)
247 mag = struct.mag
248 dist = struct.dist
249 match = struct.match
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
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
262def defaultData(repo):
263 # List of SDSS runs to be considered
264 runs = [4192, 6377]
265 fields = [300, 399]
267 # Reference visit (the other visits will be compared to this one
268 ref = 4192
269 ref_field = 300
271 # List of camcol to be considered (source calalogs will be concateneted)
272 camcol = [4]
273 filter = 'i'
275 return runs, fields, ref, ref_field, camcol, filter
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)
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)
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)