24 from scipy.spatial
import cKDTree
26 from lsst.geom import Box2D, radians, SpherePoint
28 from lsst.pipe.base import PipelineTask, PipelineTaskConnections, Struct
29 import lsst.pipe.base.connectionTypes
as connTypes
32 __all__ = [
"MatchFakesTask",
34 "MatchFakesConnections"]
38 defaultTemplates={
"coaddName":
"deep",
39 "fakesType":
"fakes_"},
45 fakeCat = connTypes.Input(
46 doc=
"Catalog of fake sources to draw inputs from.",
47 name=
"{fakesType}fakeSourceCat",
48 storageClass=
"DataFrame",
49 dimensions=(
"tract",
"skymap")
51 diffIm = connTypes.Input(
52 doc=
"Difference image on which the DiaSources were detected.",
53 name=
"{fakesType}{coaddName}Diff_differenceExp",
54 storageClass=
"ExposureF",
55 dimensions=(
"instrument",
"visit",
"detector"),
57 associatedDiaSources = connTypes.Input(
58 doc=
"Optional output storing the DiaSource catalog after matching and "
60 name=
"{fakesType}{coaddName}Diff_assocDiaSrc",
61 storageClass=
"DataFrame",
62 dimensions=(
"instrument",
"visit",
"detector"),
64 matchedDiaSources = connTypes.Output(
66 name=
"{fakesType}{coaddName}Diff_matchDiaSrc",
67 storageClass=
"DataFrame",
68 dimensions=(
"instrument",
"visit",
"detector"),
72 class MatchFakesConfig(
74 pipelineConnections=MatchFakesConnections):
75 """Config for MatchFakesTask.
77 matchDistanceArcseconds = pexConfig.RangeField(
78 doc=
"Distance in arcseconds to ",
86 class MatchFakesTask(PipelineTask):
87 """Create and store a set of spatially uniform star fakes over the sphere.
88 Additionally assign random magnitudes to said
89 fakes and assign them to be inserted into either a visit exposure or
93 _DefaultName =
"matchFakes"
94 ConfigClass = MatchFakesConfig
96 def runQuantum(self, butlerQC, inputRefs, outputRefs):
97 inputs = butlerQC.get(inputRefs)
99 outputs = self.run(**inputs)
100 butlerQC.put(outputs, outputRefs)
102 def run(self, fakeCat, diffIm, associatedDiaSources):
103 """Match fakes to detected diaSources within a difference image bound.
107 fakeCat : `pandas.DataFrame`
108 Catalog of fakes to match to detected diaSources.
109 diffIm : `lsst.afw.image.Exposure`
110 Difference image where ``associatedDiaSources`` were detected in.
111 associatedDiaSources : `pandas.DataFrame`
112 Catalog of difference image sources detected in ``diffIm``.
116 result : `lsst.pipe.base.Struct`
117 Results struct with components.
119 - ``matchedDiaSources`` : Fakes matched to input diaSources. Has
120 length of ``fakeCat``. (`pandas.DataFrame`)
122 trimmedFakes = self._trimFakeCat(fakeCat, diffIm)
123 nPossibleFakes = len(trimmedFakes)
125 fakeVects = self._getVectors(trimmedFakes[self.config.raColName],
126 trimmedFakes[self.config.decColName])
127 diaSrcVects = self._getVectors(
128 np.radians(associatedDiaSources.loc[:,
"ra"]),
129 np.radians(associatedDiaSources.loc[:,
"decl"]))
131 diaSrcTree = cKDTree(diaSrcVects)
132 dist, idxs = diaSrcTree.query(
134 distance_upper_bound=np.radians(self.config.matchDistanceArcseconds / 3600))
135 nFakesFound = np.isfinite(dist).sum()
137 self.log.info(f
"Found {nFakesFound} out of {nPossibleFakes} possible.")
138 diaSrcIds = associatedDiaSources.iloc[np.where(np.isfinite(dist), idxs, 0)][
"diaSourceId"].to_numpy()
139 matchedFakes = trimmedFakes.assign(diaSourceId=np.where(np.isfinite(dist), diaSrcIds, 0))
142 matchedDiaSources=matchedFakes.merge(
143 associatedDiaSources.reset_index(drop=
True), on=
"diaSourceId", how=
"left")
146 def _trimFakeCat(self, fakeCat, image):
147 """Trim the fake cat to about the size of the input image.
151 fakeCat : `pandas.core.frame.DataFrame`
152 The catalog of fake sources to be input
153 image : `lsst.afw.image.exposure.exposure.ExposureF`
154 The image into which the fake sources should be added
158 fakeCat : `pandas.core.frame.DataFrame`
159 The original fakeCat trimmed to the area of the image
163 bbox =
Box2D(image.getBBox())
167 row[self.config.decColName],
169 cent = wcs.skyToPixel(coord)
170 return bbox.contains(cent)
172 return fakeCat[fakeCat.apply(trim, axis=1)]
174 def _getVectors(self, ras, decs):
175 """Convert ra dec to unit vectors on the sphere.
179 ras : `numpy.ndarray`, (N,)
180 RA coordinates in radians.
181 decs : `numpy.ndarray`, (N,)
182 Dec coordinates in radians.
186 vectors : `numpy.ndarray`, (N, 3)
187 Vectors on the unit sphere for the given RA/DEC values.
189 vectors = np.empty((len(ras), 3))
191 vectors[:, 2] = np.sin(decs)
192 vectors[:, 0] = np.cos(decs) * np.cos(ras)
193 vectors[:, 1] = np.cos(decs) * np.sin(ras)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)