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`)
110 source_columns = DIA_COLUMNS.copy()
111 if 'diaSourceId' in diaSourceCatalog.columns:
112 source_column =
'diaSourceId'
115 source_columns[source_columns.index(
'diaSourceId')] = source_column
116 nSolarSystemObjects = len(ssObjects)
117 if nSolarSystemObjects <= 0:
118 return self.
_return_empty(diaSourceCatalog, ssObjects, source_column=source_column)
120 exposure_midpoint = visitInfo.date.toAstropy()
121 if 'obs_x_poly' in ssObjects.columns:
122 ref_time = exposure_midpoint.tai.mjd - ssObjects[
"tmin"].value[0]
123 ssObjects[
'obs_position'] = [
124 np.array([chebval(ref_time, row[
'obs_x_poly']),
125 chebval(ref_time, row[
'obs_y_poly']),
126 chebval(ref_time, row[
'obs_z_poly'])])
127 for row
in ssObjects]
128 ssObjects[
'obs_velocity'] = [
129 np.array([chebval(ref_time, Chebyshev(row[
'obs_x_poly']).deriv().coef),
130 chebval(ref_time, Chebyshev(row[
'obs_y_poly']).deriv().coef),
131 chebval(ref_time, Chebyshev(row[
'obs_z_poly']).deriv().coef)])
132 for row
in ssObjects]
133 ssObjects[
'obj_position'] = [
134 np.array([chebval(ref_time, row[
'obj_x_poly']),
135 chebval(ref_time, row[
'obj_y_poly']),
136 chebval(ref_time, row[
'obj_z_poly'])])
137 for row
in ssObjects]
138 ssObjects[
'obj_velocity'] = [
139 np.array([chebval(ref_time, Chebyshev(row[
'obj_x_poly']).deriv().coef),
140 chebval(ref_time, Chebyshev(row[
'obj_y_poly']).deriv().coef),
141 chebval(ref_time, Chebyshev(row[
'obj_z_poly']).deriv().coef)])
142 for row
in ssObjects]
143 vector = np.vstack(ssObjects[
'obj_position'].value - ssObjects[
'obs_position'].value)
144 ras, decs = np.vstack(hp.vec2ang(vector, lonlat=
True))
145 ssObjects[
'ephRa'] = ras
146 ssObjects[
'ephDec'] = decs
147 ssObjects[
'obs_position_x'], ssObjects[
'obs_position_y'], \
148 ssObjects[
'obs_position_z'] = ssObjects[
'obs_position'].value.T
149 ssObjects[
'helio_x'], ssObjects[
'helio_y'], \
150 ssObjects[
'helio_z'] = ssObjects[
'obj_position'].value.T
151 ssObjects[
'obs_velocity_x'], ssObjects[
'obs_velocity_y'], \
152 ssObjects[
'obs_velocity_z'] = ssObjects[
'obs_velocity'].value.T
153 ssObjects[
'helio_vx'], ssObjects[
'helio_vy'], \
154 ssObjects[
'helio_vz'] = ssObjects[
'obj_velocity'].value.T
155 ssObjects[
'topocentric_position'], ssObjects[
'topocentric_velocity'] = (
156 ssObjects[
'obj_position'] - ssObjects[
'obs_position'],
157 ssObjects[
'obj_velocity'] - ssObjects[
'obs_velocity'],
159 ssObjects[
'topo_x'], ssObjects[
'topo_y'], ssObjects[
'topo_z'] = (
160 np.array(list(ssObjects[
'topocentric_position'].value)).T
162 ssObjects[
'topo_vx'], ssObjects[
'topo_vy'], ssObjects[
'topo_vz'] = (
163 np.array(list(ssObjects[
'topocentric_velocity'].value)).T
165 ssObjects[
'helio_vx'], ssObjects[
'helio_vy'], \
166 ssObjects[
'helio_vz'] = np.array(list(ssObjects[
'obj_velocity'].value)).T
167 ssObjects[
'helioRange'] = np.linalg.norm(ssObjects[
'obj_position'], axis=1)
168 ssObjects[
'topoRange'] = np.linalg.norm(ssObjects[
'topocentric_position'], axis=1)
169 ssObjects[
'phaseAngle'] = np.degrees(np.arccos(np.sum(
170 ssObjects[
'obj_position'].T * ssObjects[
'topocentric_position'].T
171 / ssObjects[
'helioRange'] / ssObjects[
'topoRange'], axis=0
175 ssObjects[
'RARateCosDec_deg_day'] = 0
176 ssObjects[
'DecRate_deg_day'] = 0
177 ssObjects[
'trailedSourceMagTrue'] = 0
178 ssObjects[
'RangeRate_LTC_km_s'] = 0
180 marginArcsec = ssObjects[
"Err(arcsec)"].max()
183 "obs_position",
"obs_velocity",
"obj_position",
"obj_velocity",
"topocentric_position",
184 "topocentric_velocity",
"obs_x_poly",
"obs_y_poly",
"obs_z_poly",
"obj_x_poly",
"obj_y_poly",
185 "obj_z_poly",
"associated"
189 if 'trailedSourceMagTrue' not in ssObjects.columns:
190 ssObjects[
'trailedSourceMagTrue'] = 0
191 ssObjects.rename_columns(
192 [
'RATrue_deg',
'DecTrue_deg',
'phase_deg',
'Range_LTC_km',
'Obj_Sun_x_LTC_km',
193 'Obj_Sun_y_LTC_km',
'Obj_Sun_z_LTC_km',
'Obj_Sun_vx_LTC_km_s',
'Obj_Sun_vy_LTC_km_s',
194 'Obj_Sun_vz_LTC_km_s'],
195 [
'ephRa',
'ephDec',
'phaseAngle',
'topoRange',
'helio_x',
'helio_y',
196 'helio_z',
'helio_vx',
'helio_vy',
'helio_vz'])
197 ssObjects[
'ssObjectId'] = [obj_id_to_ss_object_id(v)
for v
in ssObjects[
'packed_desig']]
198 ssObjects[
'helioRange'] = (
199 np.sqrt(ssObjects[
'helio_x']**2 + ssObjects[
'helio_y']**2
200 + ssObjects[
'helio_z']**2)
202 for substring1, substring2
in [(
'x',
'x_km'), (
'y',
'y_km'), (
'z',
'z_km'),
203 (
'vx',
'vx_km_s'), (
'vy',
'vy_km_s'), (
'vz',
'vz_km_s')]:
204 topoName =
'topo_' + substring1
205 helioName =
'helio_' + substring1
206 obsName =
'Obs_Sun_' + substring2
207 ssObjects[topoName] = ssObjects[helioName] - ssObjects[obsName]
211 columns_to_drop = [
'FieldID',
'fieldMJD_TAI',
'fieldJD_TDB',
212 'Obs_Sun_x_km',
'Obs_Sun_y_km',
'Obs_Sun_z_km',
213 'Obs_Sun_vx_km_s',
'Obs_Sun_vy_km_s',
'Obs_Sun_vz_km_s',
216 stateVectorColumns = [
'helio_x',
'helio_y',
'helio_z',
'helio_vx',
217 'helio_vy',
'helio_vz',
'topo_x',
'topo_y',
218 'topo_z',
'topo_vx',
'topo_vy',
'topo_vz']
220 mpcorbColumns = [col
for col
in ssObjects.columns
if col[:7] ==
'MPCORB_']
227 nSolarSystemObjects = len(maskedObjects)
228 if nSolarSystemObjects <= 0:
229 return self.
_return_empty(diaSourceCatalog, maskedObjects, source_column=source_column)
231 maxRadius = np.deg2rad(self.config.maxDistArcSeconds / 3600)
235 diaSourceCatalog[
"dec"])
238 tree = cKDTree(vectors)
244 ssSourceData, ssObjectIds, prov_ids = [], [], []
245 ras, decs, residual_ras, residual_decs, dia_ids = [], [], [], [], []
246 diaSourceCatalog[
"ssObjectId"] = 0
247 maskedObjects[
'associated'] =
False
250 nearby_obj_source_pairs = []
251 for obj_idx, (ra, dec)
in enumerate(zip(maskedObjects[
"ephRa"].data, maskedObjects[
"ephDec"].data)):
253 dist, idx = tree.query(ssoVect, distance_upper_bound=maxRadius)
254 if not np.isfinite(dist):
256 for i
in range(len(dist)):
257 nearby_obj_source_pairs.append((dist, obj_idx, idx[i]))
258 nearby_obj_source_pairs = sorted(nearby_obj_source_pairs)
262 used_src_indices, used_obj_indices = set(), set()
263 maskedObjects[
'associated'] =
False
264 for dist, obj_idx, src_idx
in nearby_obj_source_pairs:
265 if src_idx
in used_src_indices
or obj_idx
in used_obj_indices:
267 maskedObject = maskedObjects[obj_idx]
268 used_src_indices.add(src_idx)
269 used_obj_indices.add(obj_idx)
270 diaSourceCatalog[src_idx][
"ssObjectId"] = maskedObject[
"ssObjectId"]
271 ssObjectIds.append(maskedObject[
"ssObjectId"])
273 [
"ObjID",
"phaseAngle",
"helioRange",
"topoRange"] + stateVectorColumns + mpcorbColumns
274 + [
"ephRa",
"ephDec",
"RARateCosDec_deg_day",
275 "DecRate_deg_day",
"trailedSourceMagTrue",
"RangeRate_LTC_km_s"]
277 ssSourceData.append(list(maskedObject[all_cols].values()))
278 dia_ra = diaSourceCatalog[src_idx][
"ra"]
279 dia_dec = diaSourceCatalog[src_idx][
"dec"]
280 dia_id = diaSourceCatalog[src_idx][source_column]
283 dia_ids.append(dia_id)
284 residual_ras.append(dia_ra - maskedObject[
"ephRa"])
285 residual_decs.append(dia_dec - maskedObject[
"ephDec"])
286 prov_ids.append(maskedObject[
'ObjID'])
287 maskedObjects[
'associated'][obj_idx] =
True
290 self.log.info(
"Successfully associated %d / %d SolarSystemObjects.", nFound, nSolarSystemObjects)
291 self.metadata[
'nAssociatedSsObjects'] = nFound
292 self.metadata[
'nExpectedSsObjects'] = nSolarSystemObjects
293 assocSourceMask = diaSourceCatalog[
"ssObjectId"] != 0
294 unAssocObjectMask = np.logical_not(maskedObjects[
'associated'].value)
295 ssSourceData = np.array(ssSourceData)
296 colnames = [
"designation",
"phaseAngle",
"helioRange",
"topoRange"]
297 colnames += stateVectorColumns + mpcorbColumns
298 colnames += [
"ephRa",
"ephDec",
"ephRateRa",
"ephRateDec",
"ephVmag",
"topoRangeRate"]
299 ssSourceData = Table(ssSourceData, names=colnames, dtype=[str] + [np.float64] * (len(colnames) - 1))
300 ssSourceData[
'ssObjectId'] = Column(data=ssObjectIds, dtype=int)
301 ssSourceData[
"ra"] = ras
302 ssSourceData[
"dec"] = decs
303 ephOffsetRa = np.array(residual_ras * np.cos(np.radians(ssSourceData[
"dec"]))) * 3600
304 ephOffsetDec = np.array(residual_decs) * 3600
305 ssSourceData[
"ephOffsetRa"] = ephOffsetRa
306 ssSourceData[
"ephOffsetDec"] = ephOffsetDec
307 ephOffsetVec = np.array([ephOffsetRa, ephOffsetDec])
308 ssSourceData[source_column] = dia_ids
309 coords = SkyCoord(ra=ssSourceData[
'ra'].value * u.deg, dec=ssSourceData[
'dec'].value * u.deg)
310 ssSourceData[
'galLon'] = coords.galactic.l.deg
311 ssSourceData[
'galLat '] = coords.galactic.b.deg
312 ssSourceData[
'eclLambda'] = coords.barycentrictrueecliptic.lon.deg
313 ssSourceData[
'eclBeta'] = coords.barycentrictrueecliptic.lat.deg
314 ssSourceData[
'designation'] = prov_ids
315 ssSourceData[
'ephRate'] = np.sqrt((ssSourceData[
'ephRateRa']) ** 2
316 + (ssSourceData[
'ephRateDec']) ** 2)
317 ssSourceData[
'ephOffset'] = np.sqrt((ssSourceData[
'ephOffsetRa']) ** 2
318 + (ssSourceData[
'ephOffsetDec']) ** 2)
319 ssSourceData[
'topo_vtot'] = np.sqrt(ssSourceData[
'topo_vx'] ** 2
320 + ssSourceData[
'topo_vy'] ** 2
321 + ssSourceData[
'topo_vy'] ** 2)
322 ssSourceData[
'helio_vtot'] = np.sqrt(ssSourceData[
'helio_vx'] ** 2
323 + ssSourceData[
'helio_vy'] ** 2
324 + ssSourceData[
'helio_vy'] ** 2)
325 skyMotionNormal0 = (ssSourceData[
'ephRateRa']/ssSourceData[
'ephRate']).data
326 skyMotionNormal1 = (ssSourceData[
'ephRateDec']/ssSourceData[
'ephRate']).data
327 skyMotionNormal = np.array([skyMotionNormal0, skyMotionNormal1])
328 skyMotionOrthogonal = np.array([[0, -1], [1, 0]]) @ skyMotionNormal
329 ssSourceData[
'ephOffsetAlongTrack'] = (ephOffsetVec * skyMotionNormal).sum(axis=0)
330 ssSourceData[
'ephOffsetCrossTrack'] = (ephOffsetVec * skyMotionOrthogonal).sum(axis=0)
331 ssSourceData[
'diaDistanceRank'] = 1
332 sun_obs_x = ssSourceData[
'topo_x'] - ssSourceData[
'helio_x']
333 sun_obs_y = ssSourceData[
'topo_y'] - ssSourceData[
'helio_y']
334 sun_obs_z = ssSourceData[
'topo_z'] - ssSourceData[
'helio_z']
335 sun_obs_range = np.sqrt(sun_obs_x**2 + sun_obs_y**2 + sun_obs_z**2)
336 sun_obj_dot = (sun_obs_x * ssSourceData[
'topo_x'] + sun_obs_y * ssSourceData[
'topo_y']
337 + sun_obs_z * ssSourceData[
'topo_z'])
338 ssSourceData[
"elongation"] = np.degrees(np.arccos(sun_obj_dot
339 / (sun_obs_range * ssSourceData[
'topoRange'])))
340 ssSourceData[
"helioRangeRate"] = ((ssSourceData[
"helio_vx"] * ssSourceData[
"helio_x"]
341 + ssSourceData[
"helio_vy"] * ssSourceData[
"helio_y"]
342 + ssSourceData[
"helio_vz"] * ssSourceData[
"helio_z"])
343 / ssSourceData[
"helioRange"])
345 for distanceName
in [
'helio_x',
'helio_y',
'helio_z',
'topo_x',
'topo_y',
'topo_z',
346 'helioRange',
'topoRange']:
347 ssSourceData[distanceName] = ssSourceData[distanceName].astype(np.float64)
348 ssSourceData[distanceName] *= (u.km).to(u.AU)
349 unassociatedObjects = maskedObjects[unAssocObjectMask]
350 unassociatedObjects.remove_columns(columns_to_drop)
351 unassociatedObjects[
'ra'] = unassociatedObjects[
'ephRa']
352 unassociatedObjects[
'dec'] = unassociatedObjects[
'ephDec']
355 if len(ssSourceData):
358 dia = diaSourceCatalog[source_columns].copy()
361 for c
in source_columns:
362 if c != source_column:
363 dia.rename_column(c, f
"DIA_{c}")
366 ssSourceData = join(ssSourceData, dia, keys=source_column, join_type=
"left", uniq_col_name=
"")
368 return pipeBase.Struct(
369 ssoAssocDiaSources=diaSourceCatalog[assocSourceMask],
370 unAssocDiaSources=diaSourceCatalog[~assocSourceMask],
371 nTotalSsObjects=nSolarSystemObjects,
372 nAssociatedSsObjects=nFound,
373 associatedSsSources=ssSourceData,
374 unassociatedSsObjects=unassociatedObjects)