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

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)
9__all__ = ("astromRms", "astromResiduals", "calcRmsDistances", "calcSepOutliers",
10 "matchVisitComputeDistance", "calcRmsDistancesVsRef")
13def astromRms(matchedCatalog, mag_bright_cut, mag_faint_cut, annulus_r, width, **filterargs):
14 filteredCat = filterMatches(matchedCatalog, **filterargs)
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])
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}
33def astromResiduals(matchedCatalog, mag_bright_cut, mag_faint_cut, annulus_r, width, **filterargs):
34 filteredCat = filterMatches(matchedCatalog, **filterargs)
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])
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}
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__)
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']]
79 minMag, maxMag = magRange.to(u.mag).value
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
87 groupViewInMagRange = groupView.where(magInRange)
89 # List of lists of id, importantValue
90 matchKeyOutput = [obj.get(key)
91 for key in importantKeys
92 for obj in groupViewInMagRange.groups]
94 jump = len(groupViewInMagRange)
96 ra = matchKeyOutput[1*jump:2*jump]
97 dec = matchKeyOutput[2*jump:3*jump]
98 visit = matchKeyOutput[4*jump:5*jump]
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)
105 annulusRadians = arcminToRadians(annulus.to(u.arcmin).value)
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
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)
130 # return quantity
131 rmsDistances = np.array(rmsDistances) * u.radian
132 return rmsDistances
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 """
155 log = logging.getLogger(__name__)
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']]
162 minMag, maxMag = magRange.to(u.mag).value
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
170 groupViewInMagRange = groupView.where(magInRange)
172 # List of lists of id, importantValue
173 matchKeyOutput = [obj.get(key)
174 for key in importantKeys
175 for obj in groupViewInMagRange.groups]
177 jump = len(groupViewInMagRange)
179 ra = matchKeyOutput[1*jump:2*jump]
180 dec = matchKeyOutput[2*jump:3*jump]
181 visit = matchKeyOutput[4*jump:5*jump]
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)
188 annulusRadians = arcminToRadians(annulus.to(u.arcmin).value)
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
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])))
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
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
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 """
282 minMag, maxMag = magRange.to(u.mag).value
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
290 groupViewInMagRange = groupView.where(magInRange)
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)
301 uniqVisits = list(uniqVisits)
303 if not isinstance(refVisit, int):
304 refVisit = int(refVisit)
306 if refVisit in uniqVisits:
307 # Remove the reference visit from the set of visits:
308 uniqVisits.remove(refVisit)
310 rmsDistances = list()
312 # Loop over visits, calculating the RMS for each:
313 for vis in uniqVisits:
315 distancesVisit = list()
317 for obj in uniqObj:
318 visMatch = np.where(groupViewInMagRange[obj].get('visit') == vis)
319 refMatch = np.where(groupViewInMagRange[obj].get('visit') == refVisit)
321 raObj = groupViewInMagRange[obj].get('coord_ra')
322 decObj = groupViewInMagRange[obj].get('coord_dec')
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])
329 distancesVisit.append(distances)
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)
340 else:
341 rmsDistances.append(np.nan)
343 rmsDistances = np.array(rmsDistances) * u.marcsec
344 return rmsDistances
347def radiansToMilliarcsec(rad):
348 return np.rad2deg(rad)*3600*1000
351def arcminToRadians(arcmin):
352 return np.deg2rad(arcmin/60)