From c688bd851aff375906abffc16a951e254bb893b4 Mon Sep 17 00:00:00 2001 From: gerenjie Date: Mon, 26 Jan 2026 22:22:31 -0800 Subject: [PATCH] Update sso association for new MPC columns --- python/lsst/pipe/tasks/associationUtils.py | 15 +++++++++--- python/lsst/pipe/tasks/ssoAssociation.py | 28 +++++++++++++++------- tests/test_associationUtils.py | 5 ++-- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/python/lsst/pipe/tasks/associationUtils.py b/python/lsst/pipe/tasks/associationUtils.py index 0568bd8e9..4a567d871 100644 --- a/python/lsst/pipe/tasks/associationUtils.py +++ b/python/lsst/pipe/tasks/associationUtils.py @@ -198,6 +198,12 @@ def query_disc(nside, ra, dec, max_rad, min_rad=0): def obj_id_to_ss_object_id(objID): + if ' ' in objID: + objID = pack_provisional_designation(objID) + return packed_obj_id_to_ss_object_id(objID) + + +def packed_obj_id_to_ss_object_id(objID): """Convert from Minor Planet Center packed provisional object ID to Rubin ssObjectID. @@ -232,7 +238,7 @@ def obj_id_to_ss_object_id(objID): return ssObjectID -def ss_object_id_to_obj_id(ssObjectID): +def ss_object_id_to_obj_id(ssObjectID, packed=False): """Convert from Rubin ssObjectID to Minor Planet Center packed provisional object ID. @@ -254,11 +260,14 @@ def ss_object_id_to_obj_id(ssObjectID): objID = ''.join([chr((ssObjectID >> (8 * i)) % 256) for i in reversed(range(0, 8))]) objID = objID.replace('\x00', '') - return objID + if packed: + return objID + else: + return unpack_provisional_designation(objID) # All the below designation-related code are copied from B612's adam_core # adam_core should eventually be added as an external dependency, and this -# should be replaced with imports. +# should be replaced with imports on DM-53907 def pack_mpc_designation(designation: str) -> str: diff --git a/python/lsst/pipe/tasks/ssoAssociation.py b/python/lsst/pipe/tasks/ssoAssociation.py index ea0cc7636..066c58243 100644 --- a/python/lsst/pipe/tasks/ssoAssociation.py +++ b/python/lsst/pipe/tasks/ssoAssociation.py @@ -170,11 +170,12 @@ def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs): ssObjects['obj_position'].T * ssObjects['topocentric_position'].T / ssObjects['helioRange'] / ssObjects['topoRange'], axis=0 ))) + # ssObjects['trailedSourceMagTrue'] should already be filled + # Add other required columns with dummy values until we compute them properly. # Fix in DM-53463 ssObjects['RARateCosDec_deg_day'] = 0 ssObjects['DecRate_deg_day'] = 0 - ssObjects['trailedSourceMagTrue'] = 0 ssObjects['RangeRate_LTC_km_s'] = 0 marginArcsec = ssObjects["Err(arcsec)"].max() @@ -218,7 +219,6 @@ def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs): 'topo_z', 'topo_vx', 'topo_vy', 'topo_vz'] mpcorbColumns = [col for col in ssObjects.columns if col[:7] == 'MPCORB_'] - maskedObjects = self._maskToCcdRegion( ssObjects, bbox, @@ -259,6 +259,12 @@ def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs): # From closest to farthest, associate diaSources to SSOs. # Skipping already-associated sources and objects. + all_cols = ( + ["ObjID", "phaseAngle", "helioRange", "topoRange"] + stateVectorColumns + mpcorbColumns + + ["ephRa", "ephDec", "RARateCosDec_deg_day", + "DecRate_deg_day", "trailedSourceMagTrue", "RangeRate_LTC_km_s"] + ) + used_src_indices, used_obj_indices = set(), set() maskedObjects['associated'] = False for dist, obj_idx, src_idx in nearby_obj_source_pairs: @@ -269,11 +275,6 @@ def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs): used_obj_indices.add(obj_idx) diaSourceCatalog[src_idx]["ssObjectId"] = maskedObject["ssObjectId"] ssObjectIds.append(maskedObject["ssObjectId"]) - all_cols = ( - ["ObjID", "phaseAngle", "helioRange", "topoRange"] + stateVectorColumns + mpcorbColumns - + ["ephRa", "ephDec", "RARateCosDec_deg_day", - "DecRate_deg_day", "trailedSourceMagTrue", "RangeRate_LTC_km_s"] - ) ssSourceData.append(list(maskedObject[all_cols].values())) dia_ra = diaSourceCatalog[src_idx]["ra"] dia_dec = diaSourceCatalog[src_idx]["dec"] @@ -292,11 +293,20 @@ def run(self, diaSourceCatalog, ssObjects, visitInfo, bbox, wcs): self.metadata['nExpectedSsObjects'] = nSolarSystemObjects assocSourceMask = diaSourceCatalog["ssObjectId"] != 0 unAssocObjectMask = np.logical_not(maskedObjects['associated'].value) - ssSourceData = np.array(ssSourceData) + dtypes = [type(d) if d is not np.ma.core.MaskedConstant else float + for d in maskedObjects[0][all_cols].values()] + ssSourceData = np.ma.filled(np.array(ssSourceData), np.nan) colnames = ["designation", "phaseAngle", "helioRange", "topoRange"] colnames += stateVectorColumns + mpcorbColumns colnames += ["ephRa", "ephDec", "ephRateRa", "ephRateDec", "ephVmag", "topoRangeRate"] - ssSourceData = Table(ssSourceData, names=colnames, dtype=[str] + [np.float64] * (len(colnames) - 1)) + ssSourceData = Table(ssSourceData, names=colnames, dtype=dtypes) + if 'MPCORB_created_at' in ssSourceData.columns: + ssSourceData['MPCORB_created_at'] = 0 + ssSourceData['MPCORB_updated_at'] = 0 + ssSourceData['MPCORB_fitting_datetime'] = 0 + for c in ['MPCORB_created_at', 'MPCORB_updated_at', 'MPCORB_fitting_datetime', + 'MPCORB_orbit_type_int', 'MPCORB_u_param']: + ssSourceData[c] = ssSourceData[c].astype(np.int64) ssSourceData['ssObjectId'] = Column(data=ssObjectIds, dtype=int) ssSourceData["ra"] = ras ssSourceData["dec"] = decs diff --git a/tests/test_associationUtils.py b/tests/test_associationUtils.py index 7f8b0e6c0..4f302cf33 100644 --- a/tests/test_associationUtils.py +++ b/tests/test_associationUtils.py @@ -60,9 +60,10 @@ def test_conversions_between_obj_id_and_ss_object_id(self): """ allowed_strings = ['J95X00A', 'J95X01L', 'J95F13B', 'J98SA8Q', 'J98SC7V', 'J98SG2S'] \ + ['K99AJ3Z', 'K08Aa0A', 'K07Tf8A', 'PLS2040', 'T1S3138', 'T2S1010', 'T3S4101'] \ - + [' ', 'PJ48Q010', 'AAAAAAAA'] + + ['PJ48Q010'] for allowed_string in allowed_strings: - returned_string = ss_object_id_to_obj_id(obj_id_to_ss_object_id(allowed_string)) + returned_string = ss_object_id_to_obj_id(obj_id_to_ss_object_id(allowed_string), packed=True) + print(allowed_string, returned_string) self.assertEqual(allowed_string, returned_string) def test_invalid_conversions_between_obj_id_and_ss_object_id(self):