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", "astromResiduals", "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 objectsInAnnulus += obj1 + 1 

111 for obj2 in objectsInAnnulus: 

112 distances = matchVisitComputeDistance( 

113 visit[obj1], ra[obj1], dec[obj1], 

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

115 if not distances: 

116 if verbose: 

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

118 (obj1, obj2)) 

119 continue 

120 

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

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

123 if len(finiteEntries) > 1: 

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

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

126 rmsDistances.append(rmsDist) 

127 

128 # return quantity 

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

130 return rmsDistances 

131 

132 

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

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

135 Parameters 

136 ---------- 

137 groupView : lsst.afw.table.GroupView 

138 GroupView object of matched observations from MultiMatch. 

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

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

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

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

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

144 Magnitude range from which to select objects. 

145 verbose : bool, optional 

146 Output additional information on the analysis steps. 

147 Returns 

148 ------- 

149 rmsDistances : `astropy.units.Quantity` 

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

151 """ 

152 

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

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

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

156 'object', 'visit', 'base_PsfFlux_mag']] 

157 

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

159 

160 def magInRange(cat): 

161 mag = cat.get('base_PsfFlux_mag') 

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

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

164 return minMag <= medianMag and medianMag < maxMag 

165 

166 groupViewInMagRange = groupView.where(magInRange) 

167 

168 # List of lists of id, importantValue 

169 matchKeyOutput = [obj.get(key) 

170 for key in importantKeys 

171 for obj in groupViewInMagRange.groups] 

172 

173 jump = len(groupViewInMagRange) 

174 

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

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

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

178 

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

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

181 meanRa = groupViewInMagRange.aggregate(averageRaFromCat) 

182 meanDec = groupViewInMagRange.aggregate(averageDecFromCat) 

183 

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

185 

186 sepResiduals = list() 

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

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

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

190 & (dist < annulusRadians[1])) 

191 objectsInAnnulus += obj1 + 1 

192 for obj2 in objectsInAnnulus: 

193 distances = matchVisitComputeDistance( 

194 visit[obj1], ra[obj1], dec[obj1], 

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

196 if not distances: 

197 if verbose: 

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

199 (obj1, obj2)) 

200 continue 

201 

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

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

204 if len(finiteEntries) >= 3: 

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

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

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

208 if np.size(realdist) > 0: 

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

210 

211 # return quantity 

212 # import pdb; pdb.set_trace() 

213 if len(sepResiduals) > 0: 

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

215 return sepResiduals 

216 

217 

218def matchVisitComputeDistance(visit_obj1, ra_obj1, dec_obj1, 

219 visit_obj2, ra_obj2, dec_obj2): 

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

221 For each visit shared between visit_obj1 and visit_obj2, 

222 calculate the spherical distance between the obj1 and obj2. 

223 visit_obj1 and visit_obj2 are assumed to be unsorted. 

224 Parameters 

225 ---------- 

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

227 List of visits for object 1. 

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

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

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

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

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

233 List of visits for object 2. 

234 ra_obj2 : list or numpy.array of float 

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

236 dec_obj2 : list or numpy.array of float 

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

238 Results 

239 ------- 

240 list of float 

241 spherical distances (in radians) for matching visits. 

242 """ 

243 distances = [] 

244 visit_obj1_idx = np.argsort(visit_obj1) 

245 visit_obj2_idx = np.argsort(visit_obj2) 

246 j_raw = 0 

247 j = visit_obj2_idx[j_raw] 

248 for i in visit_obj1_idx: 

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

250 j_raw += 1 

251 j = visit_obj2_idx[j_raw] 

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

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

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

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

256 ra_obj2[j], dec_obj2[j])) 

257 return distances 

258 

259 

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

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

262 Parameters 

263 ---------- 

264 groupView : lsst.afw.table.GroupView 

265 GroupView object of matched observations from MultiMatch. 

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

267 Magnitude range from which to select objects. 

268 verbose : bool, optional 

269 Output additional information on the analysis steps. 

270 Returns 

271 ------- 

272 rmsDistances : `astropy.units.Quantity` 

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

274 separations : `astropy.units.Quantity` 

275 Angular separations of the set a matched objects. 

276 """ 

277 

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

279 

280 def magInRange(cat): 

281 mag = cat.get('base_PsfFlux_mag') 

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

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

284 return minMag <= medianMag and medianMag < maxMag 

285 

286 groupViewInMagRange = groupView.where(magInRange) 

287 

288 # Get lists of the unique objects and visits: 

289 uniqObj = groupViewInMagRange.ids 

290 uniqVisits = set() 

291 for id in uniqObj: 

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

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

294 if f == band: 

295 uniqVisits.add(v) 

296 

297 uniqVisits = list(uniqVisits) 

298 

299 if not isinstance(refVisit, int): 

300 refVisit = int(refVisit) 

301 

302 if refVisit in uniqVisits: 

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

304 uniqVisits.remove(refVisit) 

305 

306 rmsDistances = list() 

307 

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

309 for vis in uniqVisits: 

310 

311 distancesVisit = list() 

312 

313 for obj in uniqObj: 

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

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

316 

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

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

319 

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

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

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

323 raObj[visMatch], decObj[visMatch]) 

324 

325 distancesVisit.append(distances) 

326 

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

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

329 if len(finiteEntries) > 1: 

330 # Calculate the RMS of these offsets: 

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

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

333 pos_rms_mas = geom.radToMas(pos_rms_rad) # milliarcsec 

334 rmsDistances.append(pos_rms_mas) 

335 

336 else: 

337 rmsDistances.append(np.nan) 

338 

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

340 return rmsDistances 

341 

342 

343def radiansToMilliarcsec(rad): 

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

345 

346 

347def arcminToRadians(arcmin): 

348 return np.deg2rad(arcmin/60)