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

1import numpy as np 

2import astropy.units as u 

3import lsst.geom as geom 

4from lsst.faro.utils.filtermatches import filterMatches 

5from lsst.faro.utils.coord_util import (averageRaFromCat, averageDecFromCat, 

6 sphDist) 

7 

8__all__ = ("astromRms", "astromRms", "calcRmsDistances", "calcSepOutliers", 

9 "matchVisitComputeDistance", "calcRmsDistancesVsRef") 

10 

11 

12def astromRms(matchedCatalog, mag_bright_cut, mag_faint_cut, annulus_r, width, **filterargs): 

13 filteredCat = filterMatches(matchedCatalog, **filterargs) 

14 

15 magRange = np.array([mag_bright_cut, mag_faint_cut]) * u.mag 

16 D = annulus_r * u.arcmin 

17 width = width * u.arcmin 

18 annulus = D + (width/2)*np.array([-1, +1]) 

19 

20 # Require at least 2 measurements to calculate the repeatability: 

21 nMinMeas = 2 

22 if filteredCat.count > nMinMeas: 

23 astrom_resid_rms_meas = calcRmsDistances( 

24 filteredCat, 

25 annulus, 

26 magRange=magRange) 

27 return astrom_resid_rms_meas 

28 else: 

29 return {'nomeas': np.nan*u.marcsec} 

30 

31 

32def astromResiduals(matchedCatalog, mag_bright_cut, mag_faint_cut, annulus_r, width, **filterargs): 

33 filteredCat = filterMatches(matchedCatalog, **filterargs) 

34 

35 magRange = np.array([mag_bright_cut, mag_faint_cut]) * u.mag 

36 D = annulus_r * u.arcmin 

37 width = width * u.arcmin 

38 annulus = D + (width/2)*np.array([-1, +1]) 

39 

40 # Require at least 2 measurements to calculate the repeatability: 

41 nMinMeas = 2 

42 if filteredCat.count > nMinMeas: 

43 astrom_resid_meas = calcSepOutliers( 

44 filteredCat, 

45 annulus, 

46 magRange=magRange) 

47 return astrom_resid_meas 

48 else: 

49 return {'nomeas': np.nan*u.marcsec} 

50 

51 

52def calcRmsDistances(groupView, annulus, magRange, verbose=False): 

53 """Calculate the RMS distance of a set of matched objects over visits. 

54 Parameters 

55 ---------- 

56 groupView : lsst.afw.table.GroupView 

57 GroupView object of matched observations from MultiMatch. 

58 annulus : length-2 `astropy.units.Quantity` 

59 Distance range (i.e., arcmin) in which to compare objects. 

60 E.g., `annulus=np.array([19, 21]) * u.arcmin` would consider all 

61 objects separated from each other between 19 and 21 arcminutes. 

62 magRange : length-2 `astropy.units.Quantity` 

63 Magnitude range from which to select objects. 

64 verbose : bool, optional 

65 Output additional information on the analysis steps. 

66 Returns 

67 ------- 

68 rmsDistances : `astropy.units.Quantity` 

69 RMS angular separations of a set of matched objects over visits. 

70 """ 

71 

72 # First we make a list of the keys that we want the fields for 

73 importantKeys = [groupView.schema.find(name).key for 

74 name in ['id', 'coord_ra', 'coord_dec', 

75 'object', 'visit', 'base_PsfFlux_mag']] 

76 

77 minMag, maxMag = magRange.to(u.mag).value 

78 

79 def magInRange(cat): 

80 mag = cat.get('base_PsfFlux_mag') 

81 w, = np.where(np.isfinite(mag)) 

82 medianMag = np.median(mag[w]) 

83 return minMag <= medianMag and medianMag < maxMag 

84 

85 groupViewInMagRange = groupView.where(magInRange) 

86 

87 # List of lists of id, importantValue 

88 matchKeyOutput = [obj.get(key) 

89 for key in importantKeys 

90 for obj in groupViewInMagRange.groups] 

91 

92 jump = len(groupViewInMagRange) 

93 

94 ra = matchKeyOutput[1*jump:2*jump] 

95 dec = matchKeyOutput[2*jump:3*jump] 

96 visit = matchKeyOutput[4*jump:5*jump] 

97 

98 # Calculate the mean position of each object from its constituent visits 

99 # `aggregate` calulates a quantity for each object in the groupView. 

100 meanRa = groupViewInMagRange.aggregate(averageRaFromCat) 

101 meanDec = groupViewInMagRange.aggregate(averageDecFromCat) 

102 

103 annulusRadians = arcminToRadians(annulus.to(u.arcmin).value) 

104 

105 rmsDistances = list() 

106 for obj1, (ra1, dec1, visit1) in enumerate(zip(meanRa, meanDec, visit)): 

107 dist = sphDist(ra1, dec1, meanRa[obj1+1:], meanDec[obj1+1:]) 

108 objectsInAnnulus, = np.where((annulusRadians[0] <= dist) 

109 & (dist < annulusRadians[1])) 

110 for obj2 in objectsInAnnulus: 

111 distances = matchVisitComputeDistance( 

112 visit[obj1], ra[obj1], dec[obj1], 

113 visit[obj2], ra[obj2], dec[obj2]) 

114 if not distances: 

115 if verbose: 

116 print("No matching visits found for objs: %d and %d" % 

117 (obj1, obj2)) 

118 continue 

119 

120 finiteEntries, = np.where(np.isfinite(distances)) 

121 # Need at least 2 distances to get a finite sample stdev 

122 if len(finiteEntries) > 1: 

123 # ddof=1 to get sample standard deviation (e.g., 1/(n-1)) 

124 rmsDist = np.std(np.array(distances)[finiteEntries], ddof=1) 

125 rmsDistances.append(rmsDist) 

126 

127 # return quantity 

128 rmsDistances = np.array(rmsDistances) * u.radian 

129 return rmsDistances 

130 

131 

132def calcSepOutliers(groupView, annulus, magRange, verbose=False): 

133 """Calculate the RMS distance of a set of matched objects over visits. 

134 Parameters 

135 ---------- 

136 groupView : lsst.afw.table.GroupView 

137 GroupView object of matched observations from MultiMatch. 

138 annulus : length-2 `astropy.units.Quantity` 

139 Distance range (i.e., arcmin) in which to compare objects. 

140 E.g., `annulus=np.array([19, 21]) * u.arcmin` would consider all 

141 objects separated from each other between 19 and 21 arcminutes. 

142 magRange : length-2 `astropy.units.Quantity` 

143 Magnitude range from which to select objects. 

144 verbose : bool, optional 

145 Output additional information on the analysis steps. 

146 Returns 

147 ------- 

148 rmsDistances : `astropy.units.Quantity` 

149 RMS angular separations of a set of matched objects over visits. 

150 """ 

151 

152 # First we make a list of the keys that we want the fields for 

153 importantKeys = [groupView.schema.find(name).key for 

154 name in ['id', 'coord_ra', 'coord_dec', 

155 'object', 'visit', 'base_PsfFlux_mag']] 

156 

157 minMag, maxMag = magRange.to(u.mag).value 

158 

159 def magInRange(cat): 

160 mag = cat.get('base_PsfFlux_mag') 

161 w, = np.where(np.isfinite(mag)) 

162 medianMag = np.median(mag[w]) 

163 return minMag <= medianMag and medianMag < maxMag 

164 

165 groupViewInMagRange = groupView.where(magInRange) 

166 

167 # List of lists of id, importantValue 

168 matchKeyOutput = [obj.get(key) 

169 for key in importantKeys 

170 for obj in groupViewInMagRange.groups] 

171 

172 jump = len(groupViewInMagRange) 

173 

174 ra = matchKeyOutput[1*jump:2*jump] 

175 dec = matchKeyOutput[2*jump:3*jump] 

176 visit = matchKeyOutput[4*jump:5*jump] 

177 

178 # Calculate the mean position of each object from its constituent visits 

179 # `aggregate` calulates a quantity for each object in the groupView. 

180 meanRa = groupViewInMagRange.aggregate(averageRaFromCat) 

181 meanDec = groupViewInMagRange.aggregate(averageDecFromCat) 

182 

183 annulusRadians = arcminToRadians(annulus.to(u.arcmin).value) 

184 

185 sepResiduals = list() 

186 for obj1, (ra1, dec1, visit1) in enumerate(zip(meanRa, meanDec, visit)): 

187 dist = sphDist(ra1, dec1, meanRa[obj1+1:], meanDec[obj1+1:]) 

188 objectsInAnnulus, = np.where((annulusRadians[0] <= dist) 

189 & (dist < annulusRadians[1])) 

190 for obj2 in objectsInAnnulus: 

191 distances = matchVisitComputeDistance( 

192 visit[obj1], ra[obj1], dec[obj1], 

193 visit[obj2], ra[obj2], dec[obj2]) 

194 if not distances: 

195 if verbose: 

196 print("No matching visits found for objs: %d and %d" % 

197 (obj1, obj2)) 

198 continue 

199 

200 finiteEntries, = np.where(np.isfinite(distances)) 

201 # Need at least 3 matched pairs so that the median position makes sense 

202 if len(finiteEntries) >= 3: 

203 okdist = np.array(distances)[finiteEntries] 

204 # Get rid of zeros from stars measured against themselves: 

205 realdist, = np.where(okdist > 0.0) 

206 if np.size(realdist) > 0: 

207 sepResiduals.append(np.abs(okdist[realdist] - np.median(okdist[realdist]))) 

208 

209 # return quantity 

210 # import pdb; pdb.set_trace() 

211 if len(sepResiduals) > 0: 

212 sepResiduals = np.concatenate(np.array(sepResiduals)) * u.radian 

213 return sepResiduals 

214 

215 

216def matchVisitComputeDistance(visit_obj1, ra_obj1, dec_obj1, 

217 visit_obj2, ra_obj2, dec_obj2): 

218 """Calculate obj1-obj2 distance for each visit in which both objects are seen. 

219 For each visit shared between visit_obj1 and visit_obj2, 

220 calculate the spherical distance between the obj1 and obj2. 

221 visit_obj1 and visit_obj2 are assumed to be unsorted. 

222 Parameters 

223 ---------- 

224 visit_obj1 : scalar, list, or numpy.array of int or str 

225 List of visits for object 1. 

226 ra_obj1 : scalar, list, or numpy.array of float 

227 List of RA in each visit for object 1. [radians] 

228 dec_obj1 : scalar, list or numpy.array of float 

229 List of Dec in each visit for object 1. [radians] 

230 visit_obj2 : list or numpy.array of int or str 

231 List of visits for object 2. 

232 ra_obj2 : list or numpy.array of float 

233 List of RA in each visit for object 2. [radians] 

234 dec_obj2 : list or numpy.array of float 

235 List of Dec in each visit for object 2. [radians] 

236 Results 

237 ------- 

238 list of float 

239 spherical distances (in radians) for matching visits. 

240 """ 

241 distances = [] 

242 visit_obj1_idx = np.argsort(visit_obj1) 

243 visit_obj2_idx = np.argsort(visit_obj2) 

244 j_raw = 0 

245 j = visit_obj2_idx[j_raw] 

246 for i in visit_obj1_idx: 

247 while (visit_obj2[j] < visit_obj1[i]) and (j_raw < len(visit_obj2_idx)-1): 

248 j_raw += 1 

249 j = visit_obj2_idx[j_raw] 

250 if visit_obj2[j] == visit_obj1[i]: 

251 if np.isfinite([ra_obj1[i], dec_obj1[i], 

252 ra_obj2[j], dec_obj2[j]]).all(): 

253 distances.append(sphDist(ra_obj1[i], dec_obj1[i], 

254 ra_obj2[j], dec_obj2[j])) 

255 return distances 

256 

257 

258def calcRmsDistancesVsRef(groupView, refVisit, magRange, band, verbose=False): 

259 """Calculate the RMS distance of a set of matched objects over visits. 

260 Parameters 

261 ---------- 

262 groupView : lsst.afw.table.GroupView 

263 GroupView object of matched observations from MultiMatch. 

264 magRange : length-2 `astropy.units.Quantity` 

265 Magnitude range from which to select objects. 

266 verbose : bool, optional 

267 Output additional information on the analysis steps. 

268 Returns 

269 ------- 

270 rmsDistances : `astropy.units.Quantity` 

271 RMS angular separations of a set of matched objects over visits. 

272 separations : `astropy.units.Quantity` 

273 Angular separations of the set a matched objects. 

274 """ 

275 

276 minMag, maxMag = magRange.to(u.mag).value 

277 

278 def magInRange(cat): 

279 mag = cat.get('base_PsfFlux_mag') 

280 w, = np.where(np.isfinite(mag)) 

281 medianMag = np.median(mag[w]) 

282 return minMag <= medianMag and medianMag < maxMag 

283 

284 groupViewInMagRange = groupView.where(magInRange) 

285 

286 # Get lists of the unique objects and visits: 

287 uniqObj = groupViewInMagRange.ids 

288 uniqVisits = set() 

289 for id in uniqObj: 

290 for v, f in zip(groupViewInMagRange[id].get('visit'), 

291 groupViewInMagRange[id].get('filt')): 

292 if f == band: 

293 uniqVisits.add(v) 

294 

295 uniqVisits = list(uniqVisits) 

296 

297 if not isinstance(refVisit, int): 

298 refVisit = int(refVisit) 

299 

300 if refVisit in uniqVisits: 

301 # Remove the reference visit from the set of visits: 

302 uniqVisits.remove(refVisit) 

303 

304 rmsDistances = list() 

305 

306 # Loop over visits, calculating the RMS for each: 

307 for vis in uniqVisits: 

308 

309 distancesVisit = list() 

310 

311 for obj in uniqObj: 

312 visMatch = np.where(groupViewInMagRange[obj].get('visit') == vis) 

313 refMatch = np.where(groupViewInMagRange[obj].get('visit') == refVisit) 

314 

315 raObj = groupViewInMagRange[obj].get('coord_ra') 

316 decObj = groupViewInMagRange[obj].get('coord_dec') 

317 

318 # Require it to have a match in both the reference and visit image: 

319 if np.size(visMatch[0]) > 0 and np.size(refMatch[0]) > 0: 

320 distances = sphDist(raObj[refMatch], decObj[refMatch], 

321 raObj[visMatch], decObj[visMatch]) 

322 

323 distancesVisit.append(distances) 

324 

325 finiteEntries = np.where(np.isfinite(distancesVisit))[0] 

326 # Need at least 2 distances to get a finite sample stdev 

327 if len(finiteEntries) > 1: 

328 # Calculate the RMS of these offsets: 

329 # ddof=1 to get sample standard deviation (e.g., 1/(n-1)) 

330 pos_rms_rad = np.std(np.array(distancesVisit)[finiteEntries], ddof=1) 

331 pos_rms_mas = geom.radToMas(pos_rms_rad) # milliarcsec 

332 rmsDistances.append(pos_rms_mas) 

333 

334 else: 

335 rmsDistances.append(np.nan) 

336 

337 rmsDistances = np.array(rmsDistances) * u.marcsec 

338 return rmsDistances 

339 

340 

341def radiansToMilliarcsec(rad): 

342 return np.rad2deg(rad)*3600*1000 

343 

344 

345def arcminToRadians(arcmin): 

346 return np.deg2rad(arcmin/60)