Coverage for python/lsst/faro/utils/separations.py : 6%

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)
8__all__ = ("astromRms", "astromResiduals", "calcRmsDistances", "calcSepOutliers",
9 "matchVisitComputeDistance", "calcRmsDistancesVsRef")
12def astromRms(matchedCatalog, mag_bright_cut, mag_faint_cut, annulus_r, width, **filterargs):
13 filteredCat = filterMatches(matchedCatalog, **filterargs)
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])
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}
32def astromResiduals(matchedCatalog, mag_bright_cut, mag_faint_cut, annulus_r, width, **filterargs):
33 filteredCat = filterMatches(matchedCatalog, **filterargs)
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])
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}
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 """
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']]
77 minMag, maxMag = magRange.to(u.mag).value
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
85 groupViewInMagRange = groupView.where(magInRange)
87 # List of lists of id, importantValue
88 matchKeyOutput = [obj.get(key)
89 for key in importantKeys
90 for obj in groupViewInMagRange.groups]
92 jump = len(groupViewInMagRange)
94 ra = matchKeyOutput[1*jump:2*jump]
95 dec = matchKeyOutput[2*jump:3*jump]
96 visit = matchKeyOutput[4*jump:5*jump]
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)
103 annulusRadians = arcminToRadians(annulus.to(u.arcmin).value)
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
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)
128 # return quantity
129 rmsDistances = np.array(rmsDistances) * u.radian
130 return rmsDistances
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 """
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']]
158 minMag, maxMag = magRange.to(u.mag).value
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
166 groupViewInMagRange = groupView.where(magInRange)
168 # List of lists of id, importantValue
169 matchKeyOutput = [obj.get(key)
170 for key in importantKeys
171 for obj in groupViewInMagRange.groups]
173 jump = len(groupViewInMagRange)
175 ra = matchKeyOutput[1*jump:2*jump]
176 dec = matchKeyOutput[2*jump:3*jump]
177 visit = matchKeyOutput[4*jump:5*jump]
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)
184 annulusRadians = arcminToRadians(annulus.to(u.arcmin).value)
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
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])))
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
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
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 """
278 minMag, maxMag = magRange.to(u.mag).value
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
286 groupViewInMagRange = groupView.where(magInRange)
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)
297 uniqVisits = list(uniqVisits)
299 if not isinstance(refVisit, int):
300 refVisit = int(refVisit)
302 if refVisit in uniqVisits:
303 # Remove the reference visit from the set of visits:
304 uniqVisits.remove(refVisit)
306 rmsDistances = list()
308 # Loop over visits, calculating the RMS for each:
309 for vis in uniqVisits:
311 distancesVisit = list()
313 for obj in uniqObj:
314 visMatch = np.where(groupViewInMagRange[obj].get('visit') == vis)
315 refMatch = np.where(groupViewInMagRange[obj].get('visit') == refVisit)
317 raObj = groupViewInMagRange[obj].get('coord_ra')
318 decObj = groupViewInMagRange[obj].get('coord_dec')
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])
325 distancesVisit.append(distances)
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)
336 else:
337 rmsDistances.append(np.nan)
339 rmsDistances = np.array(rmsDistances) * u.marcsec
340 return rmsDistances
343def radiansToMilliarcsec(rad):
344 return np.rad2deg(rad)*3600*1000
347def arcminToRadians(arcmin):
348 return np.deg2rad(arcmin/60)