22"""Simple association algorithm for DRP.
23Adapted from http://github.com/LSSTDESC/dia_pipe
25__all__ = [
"SimpleAssociationConfig",
"SimpleAssociationTask"]
35from lsst.obs.base
import ExposureIdInfo
37from .associationUtils
import query_disc, eq2xyz, toIndex
41 """Configuration parameters for the SimpleAssociationTask
43 tolerance = pexConfig.Field(
45 doc='maximum distance to match sources together in arcsec',
48 nside = pexConfig.Field(
50 doc=
'Healpix nside value used for indexing',
56 """Construct DiaObjects from a DataFrame of DIASources by spatially
57 associating the sources.
59 Represents a simple, brute force algorithm, 2-way matching of DiaSources
60 into. DiaObjects. Algorithm picks the nearest, first match within the
61 matching radius of a DiaObject to associate a source to for simplicity.
63 ConfigClass = SimpleAssociationConfig
64 _DefaultName = "simpleAssociation"
66 def run(self, diaSources, tractPatchId=None, skymapBits=None, idGenerator=None):
67 """Associate DiaSources into a collection of DiaObjects using a
68 brute force matching algorithm.
70 Reproducible is for the same input data
is assured by ordering the
71 DiaSource data by ccdVisit ordering.
75 diaSources : `pandas.DataFrame`
76 DiaSources grouped by CcdVisitId to spatially associate into
78 tractPatchId : `int`, optional
79 Unique identifier
for the tract patch. Deprecated
in favor of
80 ``idGenerator`` along
with `lsst.obs.base.ExposureIdInfo`.
82 Maximum number of bits used the ``tractPatchId`` integer
83 identifier. Deprecated
in favor of ``idGenerator`` along
with
84 `lsst.obs.base.ExposureIdInfo`.
86 Object that generates Object IDs
and random number generator seeds.
90 results : `lsst.pipe.base.Struct`
91 Results struct
with attributes:
94 Table of DiaSources
with updated values
for the DiaObjects
95 they are spatially associated to (`pandas.DataFrame`).
97 Table of DiaObjects
from matching DiaSources
104 doDropIndex = diaSources.index.names[0]
is None
105 diaSources.reset_index(inplace=
True, drop=doDropIndex)
109 diaSources.set_index([
"ccdVisitId",
"diaSourceId"], inplace=
True)
117 if idGenerator
is None:
118 if tractPatchId
is not None and skymapBits
is not None:
119 exposureIdInfo = ExposureIdInfo(tractPatchId, skymapBits)
120 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
123 idCat = idGenerator.make_source_catalog(afwTable.SourceTable.makeMinimalSchema())
125 for ccdVisit
in diaSources.index.levels[0]:
128 ccdVisitSources = diaSources.loc[ccdVisit]
129 if len(diaObjectCat) == 0:
130 for diaSourceId, diaSrc
in ccdVisitSources.iterrows():
141 usedMatchIndicies = []
143 for diaSourceId, diaSrc
in ccdVisitSources.iterrows():
147 2*self.config.tolerance,
150 dists = matchResult.dists
151 matches = matchResult.matches
164 if np.min(dists) < np.deg2rad(self.config.tolerance/3600):
165 matchDistArg = np.argmin(dists)
166 matchIndex = matches[matchDistArg]
168 if np.isin([matchIndex], usedMatchIndicies).sum() < 1:
177 usedMatchIndicies.append(matchIndex)
202 diaSources.reset_index(inplace=
True)
203 diaSources.set_index(
"diaSourceId", inplace=
True, verify_integrity=
True)
205 objs = diaObjectCat
if diaObjectCat
else np.array([], dtype=[(
'diaObjectId',
'int64'),
208 (
'nDiaSources',
'int64')])
209 diaObjects = pd.DataFrame(data=objs)
211 if "diaObjectId" in diaObjects.columns:
212 diaObjects.set_index(
"diaObjectId", inplace=
True, verify_integrity=
True)
214 return pipeBase.Struct(
215 assocDiaSources=diaSources,
216 diaObjects=diaObjects)
227 """Create a new DiaObject and append its data.
231 diaSrc : `pandas.Series`
232 Full unassociated DiaSource to create a DiaObject from.
233 diaSources : `pandas.DataFrame`
234 DiaSource catalog to update information
in. The catalog
is
237 Unique identifier of the ccdVisit where ``diaSrc`` was observed.
239 Unique identifier of the DiaSource.
240 diaObjectCat : `list` of `dict`s
241 Catalog of diaObjects to append the new object o.
243 Catalog
with the IdFactory used to generate unique DiaObject
246 Set of coordinates of DiaSource locations that make up the
247 DiaObject average coordinate.
248 healPixIndices : `list` of `int`s
249 HealPix indices representing the locations of each currently
252 hpIndex = toIndex(self.config.nside,
255 healPixIndices.append(hpIndex)
260 diaObjCoords.append([sphPoint])
262 diaObjId = idCat.addNew().get(
"id")
266 diaSources.loc[(ccdVisit, diaSourceId),
"diaObjectId"] = diaObjId
277 """Update DiaObject and DiaSource values after an association.
282 Array index location of the DiaObject that ``diaSrc`` was
284 diaSrc : `pandas.Series`
285 Full unassociated DiaSource to create a DiaObject from.
286 diaSources : `pandas.DataFrame`
287 DiaSource catalog to update information
in. The catalog
is
290 Unique identifier of the ccdVisit where ``diaSrc`` was observed.
292 Unique identifier of the DiaSource.
293 diaObjectCat : `list` of `dict`s
294 Catalog of diaObjects to append the new object o.
296 Set of coordinates of DiaSource locations that make up the
297 DiaObject average coordinate.
298 healPixIndices : `list` of `int`s
299 HealPix indices representing the locations of each currently
306 diaObjCoords[matchIndex].append(sphPoint)
308 diaObjCat[matchIndex][
"ra"] = aveCoord.getRa().asDegrees()
309 diaObjCat[matchIndex][
"dec"] = aveCoord.getDec().asDegrees()
310 nSources = diaObjCat[matchIndex][
"nDiaSources"]
311 diaObjCat[matchIndex][
"nDiaSources"] = nSources + 1
312 healPixIndices[matchIndex] = toIndex(self.config.nside,
313 diaObjCat[matchIndex][
"ra"],
314 diaObjCat[matchIndex][
"dec"])
316 diaSources.loc[(ccdVisit, diaSourceId),
"diaObjectId"] = \
317 diaObjCat[matchIndex][
"diaObjectId"]
320 """Search healPixels around DiaSource locations for DiaObjects.
325 DiaSource RA location.
327 DiaSource Dec location.
329 Size of annulus to convert to covering healPixels and search
for
331 hpIndices : `list` of `int`s
332 List of heal pix indices containing the DiaObjects
in ``diaObjs``.
333 diaObjs : `list` of `dict`s
334 Catalog diaObjects to
with full location information
for comparing
339 results : `lsst.pipe.base.Struct`
340 Results struct containing
343 Array of distances between the current DiaSource diaObjects.
344 (`numpy.ndarray`
or `
None`)
346 Array of array indices of diaObjects this DiaSource matches to.
347 (`numpy.ndarray`
or `
None`)
349 match_indices = query_disc(self.config.nside,
352 np.deg2rad(tol/3600.))
353 matchIndices = np.argwhere(np.isin(hpIndices, match_indices)).flatten()
355 if len(matchIndices) < 1:
356 return pipeBase.Struct(dists=
None, matches=
None)
359 [np.sqrt(np.sum((eq2xyz(src_ra, src_dec)
360 - eq2xyz(diaObjs[match][
"ra"],
361 diaObjs[match][
"dec"]))**2))
362 for match
in matchIndices])
363 return pipeBase.Struct(
365 matches=matchIndices)
368 """Create a simple empty DiaObject with location and id information.
373 Unique ID for this new DiaObject.
375 RA location of this DiaObject.
377 Dec location of this DiaObject
382 Dictionary of values representing a DiaObject.
384 new_dia_object = {"diaObjectId": objId,
388 return new_dia_object
Point in an unspecified spherical coordinate system.
addNewDiaObject(self, diaSrc, diaSources, ccdVisit, diaSourceId, diaObjCat, idCat, diaObjCoords, healPixIndices)
createDiaObject(self, objId, ra, dec)
findMatches(self, src_ra, src_dec, tol, hpIndices, diaObjs)
updateCatalogs(self, matchIndex, diaSrc, diaSources, ccdVisit, diaSourceId, diaObjCat, diaObjCoords, healPixIndices)
SpherePoint averageSpherePoint(std::vector< SpherePoint > const &coords)
Return the average of a list of coordinates.