71 def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs):
72 """Create a searchable tree of unassociated DiaSources and match
73 to the nearest ssoObject.
77 diaSourceCatalog : `astropy.table.Table`
78 Catalog of DiaSources. Modified in place to add ssObjectId to
79 successfully associated DiaSources.
80 ssObjects : `astropy.table.Table`
81 Set of solar system objects that should be within the footprint
83 visitInfo : `lsst.afw.image.VisitInfo`
84 visitInfo of exposure used for exposure time
85 bbox : `lsst.geom.Box2I`
86 bbox of exposure used for masking
87 wcs : `lsst.afw.geom.SkyWcs`
88 wcs of exposure used for masking
92 resultsStruct : `lsst.pipe.base.Struct`
94 - ``ssoAssocDiaSources`` : DiaSources that were associated with
95 solar system objects in this visit. (`astropy.table.Table`)
96 - ``unAssocDiaSources`` : Set of DiaSources that were not
97 associated with any solar system object. (`astropy.table.Table`)
98 - ``nTotalSsObjects`` : Total number of SolarSystemObjects
99 contained in the CCD footprint. (`int`)
100 - ``nAssociatedSsObjects`` : Number of SolarSystemObjects
101 that were associated with DiaSources. (`int`)
102 - ``ssSourceData`` : ssSource table data. (`Astropy.table.Table`)
105 nSolarSystemObjects = len(ssObjects)
106 if nSolarSystemObjects <= 0:
109 mjd_midpoint = visitInfo.date.toAstropy().tai.mjd
110 if 'obs_x_poly' in ssObjects.columns:
111 ref_time = mjd_midpoint - ssObjects[
"tmin"].value[0]
112 ssObjects[
'obs_position'] = [
113 np.array([chebval(ref_time, row[
'obs_x_poly']),
114 chebval(ref_time, row[
'obs_y_poly']),
115 chebval(ref_time, row[
'obs_z_poly'])])
116 for row
in ssObjects]
117 ssObjects[
'obs_velocity'] = [
118 np.array([chebval(ref_time, Chebyshev(row[
'obs_x_poly']).deriv().coef),
119 chebval(ref_time, Chebyshev(row[
'obs_y_poly']).deriv().coef),
120 chebval(ref_time, Chebyshev(row[
'obs_z_poly']).deriv().coef)])
121 for row
in ssObjects]
122 ssObjects[
'obj_position'] = [
123 np.array([chebval(ref_time, row[
'obj_x_poly']),
124 chebval(ref_time, row[
'obj_y_poly']),
125 chebval(ref_time, row[
'obj_z_poly'])])
126 for row
in ssObjects]
127 ssObjects[
'obj_velocity'] = [
128 np.array([chebval(ref_time, Chebyshev(row[
'obj_x_poly']).deriv().coef),
129 chebval(ref_time, Chebyshev(row[
'obj_y_poly']).deriv().coef),
130 chebval(ref_time, Chebyshev(row[
'obj_z_poly']).deriv().coef)])
131 for row
in ssObjects]
132 vector = np.vstack(ssObjects[
'obj_position'].value - ssObjects[
'obs_position'].value)
133 ras, decs = np.vstack(hp.vec2ang(vector, lonlat=
True))
134 ssObjects[
'ra'] = ras
135 ssObjects[
'dec'] = decs
136 ssObjects[
'obs_position_x'], ssObjects[
'obs_position_y'], \
137 ssObjects[
'obs_position_z'] = ssObjects[
'obs_position'].value.T
138 ssObjects[
'heliocentricX'], ssObjects[
'heliocentricY'], \
139 ssObjects[
'heliocentricZ'] = ssObjects[
'obj_position'].value.T
140 ssObjects[
'obs_velocity_x'], ssObjects[
'obs_velocity_y'], \
141 ssObjects[
'obs_velocity_z'] = ssObjects[
'obs_velocity'].value.T
142 ssObjects[
'heliocentricVX'], ssObjects[
'heliocentricVY'], \
143 ssObjects[
'heliocentricVZ'] = ssObjects[
'obj_velocity'].value.T
144 ssObjects[
'topocentric_position'], ssObjects[
'topocentric_velocity'] = (
145 ssObjects[
'obj_position'] - ssObjects[
'obs_position'],
146 ssObjects[
'obj_velocity'] - ssObjects[
'obs_velocity'],
148 ssObjects[
'topocentricX'], ssObjects[
'topocentricY'], ssObjects[
'topocentricZ'] = (
149 np.array(list(ssObjects[
'topocentric_position'].value)).T
151 ssObjects[
'topocentricVX'], ssObjects[
'topocentricVY'], ssObjects[
'topocentricVZ'] = (
152 np.array(list(ssObjects[
'topocentric_velocity'].value)).T
154 ssObjects[
'heliocentricVX'], ssObjects[
'heliocentricVY'], \
155 ssObjects[
'heliocentricVZ'] = np.array(list(ssObjects[
'obj_velocity'].value)).T
156 ssObjects[
'heliocentricDist'] = np.linalg.norm(ssObjects[
'obj_position'], axis=1)
157 ssObjects[
'topocentricDist'] = np.linalg.norm(ssObjects[
'topocentric_position'], axis=1)
158 ssObjects[
'phaseAngle'] = np.degrees(np.arccos(np.sum(
159 ssObjects[
'obj_position'].T * ssObjects[
'topocentric_position'].T
160 / ssObjects[
'heliocentricDist'] / ssObjects[
'topocentricDist'], axis=0
162 marginArcsec = ssObjects[
"Err(arcsec)"].max()
165 "obs_position",
"obs_velocity",
"obj_position",
"obj_velocity",
"topocentric_position",
166 "topocentric_velocity",
"obs_x_poly",
"obs_y_poly",
"obs_z_poly",
"obj_x_poly",
"obj_y_poly",
167 "obj_z_poly",
"associated"
171 ssObjects.rename_columns(
172 [
'RA_deg',
'Dec_deg',
'phase_deg',
'Range_LTC_km',
'Obj_Sun_x_LTC_km',
'Obj_Sun_y_LTC_km',
173 'Obj_Sun_z_LTC_km',
'Obj_Sun_vx_LTC_km_s',
'Obj_Sun_vy_LTC_km_s',
'Obj_Sun_vz_LTC_km_s'],
174 [
'ra',
'dec',
'phaseAngle',
'topocentricDist',
'heliocentricX',
'heliocentricY',
175 'heliocentricZ',
'heliocentricVX',
'heliocentricVY',
'heliocentricVZ'])
176 ssObjects[
'ssObjectId'] = [obj_id_to_ss_object_id(v)
for v
in ssObjects[
'ObjID']]
177 ssObjects[
'heliocentricDist'] = (
178 np.sqrt(ssObjects[
'heliocentricX']**2 + ssObjects[
'heliocentricY']**2
179 + ssObjects[
'heliocentricZ']**2)
181 for substring1, substring2
in [(
'X',
'x_km'), (
'Y',
'y_km'), (
'Z',
'z_km'),
182 (
'VX',
'vx_km_s'), (
'VY',
'vy_km_s'), (
'VZ',
'vz_km_s')]:
183 topoName =
'topocentric' + substring1
184 helioName =
'heliocentric' + substring1
185 obsName =
'Obs_Sun_' + substring2
186 ssObjects[topoName] = ssObjects[helioName] - ssObjects[obsName]
190 columns_to_drop = [
'FieldID',
'fieldMJD_TAI',
'fieldJD_TDB',
191 'RangeRate_LTC_km_s',
'RARateCosDec_deg_day',
192 'DecRate_deg_day',
'Obs_Sun_x_km',
'Obs_Sun_y_km',
'Obs_Sun_z_km',
193 'Obs_Sun_vx_km_s',
'Obs_Sun_vy_km_s',
'Obs_Sun_vz_km_s',
196 stateVectorColumns = [
'heliocentricX',
'heliocentricY',
'heliocentricZ',
'heliocentricVX',
197 'heliocentricVY',
'heliocentricVZ',
'topocentricX',
'topocentricY',
198 'topocentricZ',
'topocentricVX',
'topocentricVY',
'topocentricVZ']
200 mpcorbColumns = [col
for col
in ssObjects.columns
if col[:7] ==
'MPCORB_']
207 nSolarSystemObjects = len(maskedObjects)
208 if nSolarSystemObjects <= 0:
211 maxRadius = np.deg2rad(self.config.maxDistArcSeconds / 3600)
215 diaSourceCatalog[
"dec"])
218 tree = cKDTree(vectors)
224 ssSourceData, ssObjectIds = [], []
225 ras, decs, residual_ras, residual_decs, dia_ids = [], [], [], [], []
226 diaSourceCatalog[
"ssObjectId"] = 0
228 maskedObjects[
'associated'] =
False
229 if 'diaSourceId' in diaSourceCatalog.columns:
230 source_column =
'diaSourceId'
231 for ssObject
in maskedObjects:
232 index = ssObject.index
233 ssoVect = self.
_radec_to_xyz(ssObject[
"ra"], ssObject[
"dec"])
235 dist, idx = tree.query(ssoVect, distance_upper_bound=maxRadius)
236 if len(idx) == 1
and np.isfinite(dist[0]):
238 diaSourceCatalog[idx[0]][
"ssObjectId"] = ssObject[
"ssObjectId"]
239 ssObjectIds.append(ssObject[
"ssObjectId"])
240 all_cols = [
"phaseAngle",
"heliocentricDist",
241 "topocentricDist"] + stateVectorColumns + mpcorbColumns
242 ssSourceData.append(list(ssObject[all_cols].values()))
243 dia_ra = diaSourceCatalog[idx[0]][
"ra"]
244 dia_dec = diaSourceCatalog[idx[0]][
"dec"]
245 dia_id = diaSourceCatalog[idx[0]][source_column]
248 dia_ids.append(dia_id)
249 residual_ras.append(dia_ra - ssObject[
"ra"])
250 residual_decs.append(dia_dec - ssObject[
"dec"])
251 maskedObjects[
'associated'][index] =
True
253 maskedObjects[
'associated'][index] =
False
255 self.log.info(
"Successfully associated %d / %d SolarSystemObjects.", nFound, nSolarSystemObjects)
256 self.metadata[
'nAssociatedSsObjects'] = nFound
257 self.metadata[
'nExpectedSsObjects'] = nSolarSystemObjects
258 assocSourceMask = diaSourceCatalog[
"ssObjectId"] != 0
259 unAssocObjectMask = np.logical_not(maskedObjects[
'associated'].value)
260 ssSourceData = np.array(ssSourceData)
261 ssSourceData = Table(ssSourceData,
263 "phaseAngle",
"heliocentricDist",
"topocentricDist"
264 ] + stateVectorColumns + mpcorbColumns)
265 ssSourceData[
'ssObjectId'] = Column(data=ssObjectIds, dtype=int)
266 ssSourceData[
"ra"] = ras
267 ssSourceData[
"dec"] = decs
268 ssSourceData[
"residualRa"] = residual_ras
269 ssSourceData[
"residualDec"] = residual_decs
270 ssSourceData[source_column] = dia_ids
271 coords = SkyCoord(ra=ssSourceData[
'ra'].value * u.deg, dec=ssSourceData[
'dec'].value * u.deg)
272 ssSourceData[
'galacticL'] = coords.galactic.l.deg
273 ssSourceData[
'galacticB'] = coords.galactic.b.deg
274 ssSourceData[
'eclipticLambda'] = coords.barycentrictrueecliptic.lon.deg
275 ssSourceData[
'eclipticBeta'] = coords.barycentrictrueecliptic.lat.deg
276 unassociatedObjects = maskedObjects[unAssocObjectMask]
277 unassociatedObjects.remove_columns(columns_to_drop)
278 return pipeBase.Struct(
279 ssoAssocDiaSources=diaSourceCatalog[assocSourceMask],
280 unAssocDiaSources=diaSourceCatalog[~assocSourceMask],
281 nTotalSsObjects=nSolarSystemObjects,
282 nAssociatedSsObjects=nFound,
283 associatedSsSources=ssSourceData,
284 unassociatedSsObjects=unassociatedObjects)