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 logging 

4import lsst.geom as geom 

5from lsst.faro.utils.filtermatches import filterMatches 

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

7 sphDist) 

8 

9__all__ = ("astromRms", "astromResiduals", "calcRmsDistances", "calcSepOutliers", 

10 "matchVisitComputeDistance", "calcRmsDistancesVsRef") 

11 

12 

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

14 filteredCat = filterMatches(matchedCatalog, **filterargs) 

15 

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

17 D = annulus_r * u.arcmin 

18 width = width * u.arcmin 

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

20 

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

22 nMinMeas = 2 

23 if filteredCat.count > nMinMeas: 

24 astrom_resid_rms_meas = calcRmsDistances( 

25 filteredCat, 

26 annulus, 

27 magRange=magRange) 

28 return astrom_resid_rms_meas 

29 else: 

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

31 

32 

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

34 filteredCat = filterMatches(matchedCatalog, **filterargs) 

35 

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

37 D = annulus_r * u.arcmin 

38 width = width * u.arcmin 

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

40 

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

42 nMinMeas = 2 

43 if filteredCat.count > nMinMeas: 

44 astrom_resid_meas = calcSepOutliers( 

45 filteredCat, 

46 annulus, 

47 magRange=magRange) 

48 return astrom_resid_meas 

49 else: 

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

51 

52 

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

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

55 Parameters 

56 ---------- 

57 groupView : lsst.afw.table.GroupView 

58 GroupView object of matched observations from MultiMatch. 

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

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

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

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

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

64 Magnitude range from which to select objects. 

65 verbose : bool, optional 

66 Output additional information on the analysis steps. 

67 Returns 

68 ------- 

69 rmsDistances : `astropy.units.Quantity` 

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

71 """ 

72 log = logging.getLogger(__name__) 

73 

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

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

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

77 'object', 'visit', 'base_PsfFlux_mag']] 

78 

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

80 

81 def magInRange(cat): 

82 mag = cat.get('base_PsfFlux_mag') 

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

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

85 return minMag <= medianMag and medianMag < maxMag 

86 

87 groupViewInMagRange = groupView.where(magInRange) 

88 

89 # List of lists of id, importantValue 

90 matchKeyOutput = [obj.get(key) 

91 for key in importantKeys 

92 for obj in groupViewInMagRange.groups] 

93 

94 jump = len(groupViewInMagRange) 

95 

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

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

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

99 

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

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

102 meanRa = groupViewInMagRange.aggregate(averageRaFromCat) 

103 meanDec = groupViewInMagRange.aggregate(averageDecFromCat) 

104 

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

106 

107 rmsDistances = list() 

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

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

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

111 & (dist < annulusRadians[1])) 

112 objectsInAnnulus += obj1 + 1 

113 for obj2 in objectsInAnnulus: 

114 distances = matchVisitComputeDistance( 

115 visit[obj1], ra[obj1], dec[obj1], 

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

117 if not distances: 

118 if verbose: 

119 log.debug("No matching visits " 

120 "found for objs: %d and %d" % obj1, obj2) 

121 continue 

122 

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

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

125 if len(finiteEntries) > 1: 

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

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

128 rmsDistances.append(rmsDist) 

129 

130 # return quantity 

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

132 return rmsDistances 

133 

134 

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

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

137 Parameters 

138 ---------- 

139 groupView : lsst.afw.table.GroupView 

140 GroupView object of matched observations from MultiMatch. 

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

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

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

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

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

146 Magnitude range from which to select objects. 

147 verbose : bool, optional 

148 Output additional information on the analysis steps. 

149 Returns 

150 ------- 

151 rmsDistances : `astropy.units.Quantity` 

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

153 """ 

154 

155 log = logging.getLogger(__name__) 

156 

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

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

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

160 'object', 'visit', 'base_PsfFlux_mag']] 

161 

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

163 

164 def magInRange(cat): 

165 mag = cat.get('base_PsfFlux_mag') 

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

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

168 return minMag <= medianMag and medianMag < maxMag 

169 

170 groupViewInMagRange = groupView.where(magInRange) 

171 

172 # List of lists of id, importantValue 

173 matchKeyOutput = [obj.get(key) 

174 for key in importantKeys 

175 for obj in groupViewInMagRange.groups] 

176 

177 jump = len(groupViewInMagRange) 

178 

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

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

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

182 

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

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

185 meanRa = groupViewInMagRange.aggregate(averageRaFromCat) 

186 meanDec = groupViewInMagRange.aggregate(averageDecFromCat) 

187 

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

189 

190 sepResiduals = list() 

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

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

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

194 & (dist < annulusRadians[1])) 

195 objectsInAnnulus += obj1 + 1 

196 for obj2 in objectsInAnnulus: 

197 distances = matchVisitComputeDistance( 

198 visit[obj1], ra[obj1], dec[obj1], 

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

200 if not distances: 

201 if verbose: 

202 log.debug("No matching visits found " 

203 "for objs: %d and %d" % obj1, obj2) 

204 continue 

205 

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

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

208 if len(finiteEntries) >= 3: 

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

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

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

212 if np.size(realdist) > 0: 

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

214 

215 # return quantity 

216 # import pdb; pdb.set_trace() 

217 if len(sepResiduals) > 0: 

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

219 return sepResiduals 

220 

221 

222def matchVisitComputeDistance(visit_obj1, ra_obj1, dec_obj1, 

223 visit_obj2, ra_obj2, dec_obj2): 

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

225 For each visit shared between visit_obj1 and visit_obj2, 

226 calculate the spherical distance between the obj1 and obj2. 

227 visit_obj1 and visit_obj2 are assumed to be unsorted. 

228 Parameters 

229 ---------- 

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

231 List of visits for object 1. 

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

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

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

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

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

237 List of visits for object 2. 

238 ra_obj2 : list or numpy.array of float 

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

240 dec_obj2 : list or numpy.array of float 

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

242 Results 

243 ------- 

244 list of float 

245 spherical distances (in radians) for matching visits. 

246 """ 

247 distances = [] 

248 visit_obj1_idx = np.argsort(visit_obj1) 

249 visit_obj2_idx = np.argsort(visit_obj2) 

250 j_raw = 0 

251 j = visit_obj2_idx[j_raw] 

252 for i in visit_obj1_idx: 

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

254 j_raw += 1 

255 j = visit_obj2_idx[j_raw] 

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

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

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

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

260 ra_obj2[j], dec_obj2[j])) 

261 return distances 

262 

263 

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

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

266 Parameters 

267 ---------- 

268 groupView : lsst.afw.table.GroupView 

269 GroupView object of matched observations from MultiMatch. 

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

271 Magnitude range from which to select objects. 

272 verbose : bool, optional 

273 Output additional information on the analysis steps. 

274 Returns 

275 ------- 

276 rmsDistances : `astropy.units.Quantity` 

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

278 separations : `astropy.units.Quantity` 

279 Angular separations of the set a matched objects. 

280 """ 

281 

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

283 

284 def magInRange(cat): 

285 mag = cat.get('base_PsfFlux_mag') 

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

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

288 return minMag <= medianMag and medianMag < maxMag 

289 

290 groupViewInMagRange = groupView.where(magInRange) 

291 

292 # Get lists of the unique objects and visits: 

293 uniqObj = groupViewInMagRange.ids 

294 uniqVisits = set() 

295 for id in uniqObj: 

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

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

298 if f == band: 

299 uniqVisits.add(v) 

300 

301 uniqVisits = list(uniqVisits) 

302 

303 if not isinstance(refVisit, int): 

304 refVisit = int(refVisit) 

305 

306 if refVisit in uniqVisits: 

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

308 uniqVisits.remove(refVisit) 

309 

310 rmsDistances = list() 

311 

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

313 for vis in uniqVisits: 

314 

315 distancesVisit = list() 

316 

317 for obj in uniqObj: 

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

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

320 

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

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

323 

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

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

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

327 raObj[visMatch], decObj[visMatch]) 

328 

329 distancesVisit.append(distances) 

330 

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

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

333 if len(finiteEntries) > 1: 

334 # Calculate the RMS of these offsets: 

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

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

337 pos_rms_mas = geom.radToMas(pos_rms_rad) # milliarcsec 

338 rmsDistances.append(pos_rms_mas) 

339 

340 else: 

341 rmsDistances.append(np.nan) 

342 

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

344 return rmsDistances 

345 

346 

347def radiansToMilliarcsec(rad): 

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

349 

350 

351def arcminToRadians(arcmin): 

352 return np.deg2rad(arcmin/60)