74 def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs):
75 """Create a searchable tree of unassociated DiaSources and match
76 to the nearest ssoObject.
80 diaSourceCatalog : `astropy.table.Table`
81 Catalog of DiaSources. Modified in place to add ssObjectId to
82 successfully associated DiaSources.
83 ssObjects : `astropy.table.Table`
84 Set of solar system objects that should be within the footprint
86 visitInfo : `lsst.afw.image.VisitInfo`
87 visitInfo of exposure used for exposure time
88 bbox : `lsst.geom.Box2I`
89 bbox of exposure used for masking
90 wcs : `lsst.afw.geom.SkyWcs`
91 wcs of exposure used for masking
95 resultsStruct : `lsst.pipe.base.Struct`
97 - ``ssoAssocDiaSources`` : DiaSources that were associated with
98 solar system objects in this visit. (`astropy.table.Table`)
99 - ``unAssocDiaSources`` : Set of DiaSources that were not
100 associated with any solar system object. (`astropy.table.Table`)
101 - ``nTotalSsObjects`` : Total number of SolarSystemObjects
102 contained in the CCD footprint. (`int`)
103 - ``nAssociatedSsObjects`` : Number of SolarSystemObjects
104 that were associated with DiaSources. (`int`)
105 - ``ssSourceData`` : ssSource table data. (`Astropy.table.Table`)
108 nSolarSystemObjects = len(ssObjects)
109 if nSolarSystemObjects <= 0:
112 exposure_midpoint = visitInfo.date.toAstropy()
113 if 'obs_x_poly' in ssObjects.columns:
115 ref_time = exposure_midpoint.tai.mjd - ssObjects[
"tmin"].quantity.to_value(u.d)[0]
118 ssObjects[
'obs_position'] = [
119 np.array([chebval(ref_time, row[
'obs_x_poly']),
120 chebval(ref_time, row[
'obs_y_poly']),
121 chebval(ref_time, row[
'obs_z_poly'])])
122 for row
in ssObjects] * u.au
123 ssObjects[
'obs_velocity'] = [
124 np.array([chebval(ref_time, Chebyshev(row[
'obs_x_poly']).deriv().coef),
125 chebval(ref_time, Chebyshev(row[
'obs_y_poly']).deriv().coef),
126 chebval(ref_time, Chebyshev(row[
'obs_z_poly']).deriv().coef)])
127 for row
in ssObjects] * u.au/u.d
128 ssObjects[
'obj_position'] = [
129 np.array([chebval(ref_time, row[
'obj_x_poly']),
130 chebval(ref_time, row[
'obj_y_poly']),
131 chebval(ref_time, row[
'obj_z_poly'])])
132 for row
in ssObjects] * u.au
133 ssObjects[
'obj_velocity'] = [
134 np.array([chebval(ref_time, Chebyshev(row[
'obj_x_poly']).deriv().coef),
135 chebval(ref_time, Chebyshev(row[
'obj_y_poly']).deriv().coef),
136 chebval(ref_time, Chebyshev(row[
'obj_z_poly']).deriv().coef)])
137 for row
in ssObjects] * u.au/u.d
139 vector = np.vstack(ssObjects[
'obj_position'].quantity.to_value(u.au)
140 - ssObjects[
'obs_position'].quantity.to_value(u.au))
141 ras, decs = np.vstack(hp.vec2ang(vector, lonlat=
True))
143 ssObjects[
'ephRa'] = ras * u.deg
144 ssObjects[
'ephDec'] = decs * u.deg
146 ssObjects[
'obs_position_x'], ssObjects[
'obs_position_y'], \
147 ssObjects[
'obs_position_z'] = ssObjects[
'obs_position'].quantity.to_value(u.au).T
148 ssObjects[
'helio_x'], ssObjects[
'helio_y'], \
149 ssObjects[
'helio_z'] = ssObjects[
'obj_position'].quantity.to_value(u.au).T
150 ssObjects[
'obs_velocity_x'], ssObjects[
'obs_velocity_y'], \
151 ssObjects[
'obs_velocity_z'] = ssObjects[
'obs_velocity'].quantity.to_value(u.km/u.s).T
152 ssObjects[
'helio_vx'], ssObjects[
'helio_vy'], \
153 ssObjects[
'helio_vz'] = ssObjects[
'obj_velocity'].quantity.to_value(u.km/u.s).T
154 ssObjects[
'topocentric_position'], ssObjects[
'topocentric_velocity'] = (
155 ssObjects[
'obj_position'] - ssObjects[
'obs_position'],
156 ssObjects[
'obj_velocity'] - ssObjects[
'obs_velocity'],
158 ssObjects[
'topo_x'], ssObjects[
'topo_y'], ssObjects[
'topo_z'] = (
159 np.array(list(ssObjects[
'topocentric_position'].quantity.to_value(u.au))).T
161 ssObjects[
'topo_vx'], ssObjects[
'topo_vy'], ssObjects[
'topo_vz'] = (
162 np.array(list(ssObjects[
'topocentric_velocity'].quantity.to_value(u.km/u.s))).T
164 ssObjects[
'helioRange'] = np.linalg.norm(ssObjects[
'obj_position'], axis=1)
165 ssObjects[
'topoRange'] = np.linalg.norm(ssObjects[
'topocentric_position'], axis=1)
168 ssObjects[
'phaseAngle'] = np.degrees(np.arccos(np.sum(
169 ssObjects[
'obj_position'].T * ssObjects[
'topocentric_position'].T
170 / ssObjects[
'helioRange'] / ssObjects[
'topoRange'], axis=0
176 ssObjects[
'RARateCosDec_deg_day'] = 0
177 ssObjects[
'DecRate_deg_day'] = 0
178 ssObjects[
'RangeRate_LTC_km_s'] = 0
180 marginArcsec = ssObjects[
"Err(arcsec)"].max()
181 ssObjects[
'designation'] = ssObjects[
'MPCORB_unpacked_primary_provisional_designation']
184 "obs_position",
"obs_velocity",
"obj_position",
"obj_velocity",
"topocentric_position",
185 "topocentric_velocity",
"obs_x_poly",
"obs_y_poly",
"obs_z_poly",
"obj_x_poly",
"obj_y_poly",
186 "obj_z_poly",
"associated"
190 if 'trailedSourceMagTrue' not in ssObjects.columns:
191 ssObjects[
'trailedSourceMagTrue'] = 0
192 ssObjects.rename_columns(
193 [
'RATrue_deg',
'DecTrue_deg',
'phase_deg',
'Range_LTC_km',
'Obj_Sun_x_LTC_km',
194 'Obj_Sun_y_LTC_km',
'Obj_Sun_z_LTC_km',
'Obj_Sun_vx_LTC_km_s',
'Obj_Sun_vy_LTC_km_s',
195 'Obj_Sun_vz_LTC_km_s'],
196 [
'ephRa',
'ephDec',
'phaseAngle',
'topoRange',
'helio_x',
'helio_y',
197 'helio_z',
'helio_vx',
'helio_vy',
'helio_vz'])
198 for col
in [
'topoRange',
'helio_x',
'helio_y',
'helio_z']:
199 ssObjects[col] = ssObjects[col].quantity.to_value(u.au)
200 for col
in [
'Obs_Sun_x_km',
'Obs_Sun_y_km',
'Obs_Sun_z_km']:
201 ssObjects[col.replace(
'km',
'au')] = ssObjects[col].quantity.to_value(u.au)
202 for col
in [
'helio_vx',
'helio_vy',
'helio_vz',
'Obs_Sun_vx_km_s',
'Obs_Sun_vy_km_s',
204 ssObjects[col] = ssObjects[col].quantity.to_value(u.km/u.s)
206 ssObjects[
'ssObjectId'] = [obj_id_to_ss_object_id(v)
for v
in ssObjects[
'packed_desig']]
207 for substring1, substring2
in [(
'x',
'x_au'), (
'y',
'y_au'), (
'z',
'z_au'),
208 (
'vx',
'vx_km_s'), (
'vy',
'vy_km_s'), (
'vz',
'vz_km_s')]:
209 topoName =
'topo_' + substring1
210 helioName =
'helio_' + substring1
211 obsName =
'Obs_Sun_' + substring2
212 ssObjects[topoName] = ssObjects[helioName] - ssObjects[obsName]
214 ssObjects[
'helioRange'] = (
215 np.sqrt(ssObjects[
'helio_x']**2 + ssObjects[
'helio_y']**2
216 + ssObjects[
'helio_z']**2)
219 ssObjects[
'designation'] = ssObjects[
'ObjID']
223 columns_to_drop = [
'FieldID',
'fieldMJD_TAI',
'fieldJD_TDB',
224 'Obs_Sun_x_km',
'Obs_Sun_y_km',
'Obs_Sun_z_km',
225 'Obs_Sun_vx_km_s',
'Obs_Sun_vy_km_s',
'Obs_Sun_vz_km_s',
226 'Obs_Sun_x_au',
'Obs_Sun_y_au',
'Obs_Sun_z_au']
228 stateVectorColumns = [
'helio_x',
'helio_y',
'helio_z',
'helio_vx',
229 'helio_vy',
'helio_vz',
'topo_x',
'topo_y',
230 'topo_z',
'topo_vx',
'topo_vy',
'topo_vz']
232 mpcorbColumns = [col
for col
in ssObjects.columns
if col[:7] ==
'MPCORB_']
238 nSolarSystemObjects = len(maskedObjects)
239 if nSolarSystemObjects <= 0:
242 maxRadius = np.deg2rad(self.config.maxDistArcSeconds / 3600)
246 diaSourceCatalog[
"dec"])
249 tree = cKDTree(vectors)
255 ssSourceData, ssObjectIds, prov_ids = [], [], []
256 ras, decs, residual_ras, residual_decs, dia_ids = [], [], [], [], []
257 diaSourceCatalog[
"ssObjectId"] = 0
259 maskedObjects[
'associated'] =
False
260 if 'diaSourceId' in diaSourceCatalog.columns:
261 source_column =
'diaSourceId'
264 nearby_obj_source_pairs = []
265 for obj_idx, (ra, dec)
in enumerate(zip(maskedObjects[
"ephRa"].data, maskedObjects[
"ephDec"].data)):
267 dist, idx = tree.query(ssoVect, distance_upper_bound=maxRadius)
268 if not np.isfinite(dist):
270 for i
in range(len(dist)):
271 nearby_obj_source_pairs.append((dist, obj_idx, idx[i]))
272 nearby_obj_source_pairs = sorted(nearby_obj_source_pairs)
277 [
"designation",
"phaseAngle",
"helioRange",
"topoRange"] + stateVectorColumns + mpcorbColumns
278 + [
"ephRa",
"ephDec",
"RARateCosDec_deg_day",
279 "DecRate_deg_day",
"trailedSourceMagTrue",
"RangeRate_LTC_km_s"]
282 used_src_indices, used_obj_indices = set(), set()
283 maskedObjects[
'associated'] =
False
284 for dist, obj_idx, src_idx
in nearby_obj_source_pairs:
285 if src_idx
in used_src_indices
or obj_idx
in used_obj_indices:
287 maskedObject = maskedObjects[obj_idx]
288 used_src_indices.add(src_idx)
289 used_obj_indices.add(obj_idx)
290 diaSourceCatalog[src_idx][
"ssObjectId"] = maskedObject[
"ssObjectId"]
291 ssObjectIds.append(maskedObject[
"ssObjectId"])
292 ssSourceData.append(list(maskedObject[all_cols].values()))
293 dia_ra = diaSourceCatalog[src_idx][
"ra"]
294 dia_dec = diaSourceCatalog[src_idx][
"dec"]
295 dia_id = diaSourceCatalog[src_idx][source_column]
298 dia_ids.append(dia_id)
299 residual_ras.append(dia_ra - maskedObject[
"ephRa"])
300 residual_decs.append(dia_dec - maskedObject[
"ephDec"])
301 prov_ids.append(maskedObject[
'ObjID'])
302 maskedObjects[
'associated'][obj_idx] =
True
305 self.log.info(
"Successfully associated %d / %d SolarSystemObjects.", nFound, nSolarSystemObjects)
306 self.metadata[
'nAssociatedSsObjects'] = nFound
307 self.metadata[
'nExpectedSsObjects'] = nSolarSystemObjects
308 assocSourceMask = diaSourceCatalog[
"ssObjectId"] != 0
309 unAssocObjectMask = np.logical_not(maskedObjects[
'associated'].value)
310 dtypes = [float
if d
is np.ma.core.MaskedConstant
else type(d)
311 for d
in maskedObjects[0][all_cols].values()]
312 ssSourceData = np.ma.filled(np.array(ssSourceData), np.nan)
313 colnames = [
"designation",
"phaseAngle",
"helioRange",
"topoRange"]
314 colnames += stateVectorColumns + mpcorbColumns
315 colnames += [
"ephRa",
"ephDec",
"ephRateRa",
"ephRateDec",
"ephVmag",
"topoRangeRate"]
316 ssSourceData = Table(ssSourceData, names=colnames, dtype=dtypes)
317 if 'MPCORB_created_at' in ssSourceData.columns:
318 ssSourceData[
'MPCORB_created_at'] = 0
319 ssSourceData[
'MPCORB_updated_at'] = 0
320 ssSourceData[
'MPCORB_fitting_datetime'] = 0
321 for c
in [
'MPCORB_created_at',
'MPCORB_updated_at',
'MPCORB_fitting_datetime',
322 'MPCORB_orbit_type_int',
'MPCORB_u_param']:
323 ssSourceData[c] = ssSourceData[c].astype(np.int64)
324 ssSourceData[
'ssObjectId'] = Column(data=ssObjectIds, dtype=int)
325 ssSourceData[
"ra"] = ras
326 ssSourceData[
"dec"] = decs
327 ephOffsetRa = np.array(residual_ras * np.cos(np.radians(ssSourceData[
"dec"]))) * 3600
328 ephOffsetDec = np.array(residual_decs) * 3600
329 ssSourceData[
"ephOffsetRa"] = ephOffsetRa
330 ssSourceData[
"ephOffsetDec"] = ephOffsetDec
331 ephOffsetVec = np.array([ephOffsetRa, ephOffsetDec])
332 ssSourceData[source_column] = dia_ids
333 coords = SkyCoord(ra=ssSourceData[
'ra'] * u.deg, dec=ssSourceData[
'dec'] * u.deg)
334 ssSourceData[
'galLon'] = coords.galactic.l.deg
335 ssSourceData[
'galLat'] = coords.galactic.b.deg
336 ssSourceData[
'eclLambda'] = coords.barycentrictrueecliptic.lon.deg
337 ssSourceData[
'eclBeta'] = coords.barycentrictrueecliptic.lat.deg
338 ssSourceData[
'ephRate'] = np.sqrt((ssSourceData[
'ephRateRa']) ** 2
339 + (ssSourceData[
'ephRateDec']) ** 2)
340 ssSourceData[
'ephOffset'] = np.sqrt((ssSourceData[
'ephOffsetRa']) ** 2
341 + (ssSourceData[
'ephOffsetDec']) ** 2)
342 ssSourceData[
'topo_vtot'] = np.sqrt(ssSourceData[
'topo_vx'] ** 2
343 + ssSourceData[
'topo_vy'] ** 2
344 + ssSourceData[
'topo_vz'] ** 2)
345 ssSourceData[
'helio_vtot'] = np.sqrt(ssSourceData[
'helio_vx'] ** 2
346 + ssSourceData[
'helio_vy'] ** 2
347 + ssSourceData[
'helio_vz'] ** 2)
348 skyMotionNormal0 = (ssSourceData[
'ephRateRa']/ssSourceData[
'ephRate']).data
349 skyMotionNormal1 = (ssSourceData[
'ephRateDec']/ssSourceData[
'ephRate']).data
350 skyMotionNormal = np.array([skyMotionNormal0, skyMotionNormal1])
351 skyMotionOrthogonal = np.array([[0, -1], [1, 0]]) @ skyMotionNormal
352 ssSourceData[
'ephOffsetAlongTrack'] = (ephOffsetVec * skyMotionNormal).sum(axis=0)
353 ssSourceData[
'ephOffsetCrossTrack'] = (ephOffsetVec * skyMotionOrthogonal).sum(axis=0)
354 ssSourceData[
'diaDistanceRank'] = 1
355 sun_obs_x = ssSourceData[
'topo_x'] - ssSourceData[
'helio_x']
356 sun_obs_y = ssSourceData[
'topo_y'] - ssSourceData[
'helio_y']
357 sun_obs_z = ssSourceData[
'topo_z'] - ssSourceData[
'helio_z']
358 sun_obs_range = np.sqrt(sun_obs_x**2 + sun_obs_y**2 + sun_obs_z**2)
359 sun_obj_dot = (sun_obs_x * ssSourceData[
'topo_x'] + sun_obs_y * ssSourceData[
'topo_y']
360 + sun_obs_z * ssSourceData[
'topo_z'])
361 ssSourceData[
"elongation"] = np.degrees(np.arccos(sun_obj_dot
362 / (sun_obs_range * ssSourceData[
'topoRange'])))
363 ssSourceData[
"helioRangeRate"] = ((ssSourceData[
"helio_vx"] * ssSourceData[
"helio_x"]
364 + ssSourceData[
"helio_vy"] * ssSourceData[
"helio_y"]
365 + ssSourceData[
"helio_vz"] * ssSourceData[
"helio_z"])
366 / ssSourceData[
"helioRange"])
368 unassociatedObjects = maskedObjects[unAssocObjectMask]
369 unassociatedObjects.remove_columns(columns_to_drop)
370 unassociatedObjects[
'ra'] = unassociatedObjects[
'ephRa']
371 unassociatedObjects[
'dec'] = unassociatedObjects[
'ephDec']
374 if len(ssSourceData):
377 dia = diaSourceCatalog[DIA_COLUMNS].copy()
380 for c
in DIA_COLUMNS:
381 if c !=
"diaSourceId":
382 dia.rename_column(c, f
"DIA_{c}")
385 ssSourceData = join(ssSourceData, dia, keys=
"diaSourceId", join_type=
"left", uniq_col_name=
"")
387 return pipeBase.Struct(
388 ssoAssocDiaSources=diaSourceCatalog[assocSourceMask],
389 unAssocDiaSources=diaSourceCatalog[~assocSourceMask],
390 nTotalSsObjects=nSolarSystemObjects,
391 nAssociatedSsObjects=nFound,
392 associatedSsSources=ssSourceData,
393 unassociatedSsObjects=unassociatedObjects)