Coverage for python/lsst/pipe/tasks/matchFakes.py : 46%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
23import numpy as np
24from scipy.spatial import cKDTree
26from lsst.geom import Box2D, radians, SpherePoint
27import lsst.pex.config as pexConfig
28from lsst.pipe.base import PipelineTask, PipelineTaskConnections, Struct
29import lsst.pipe.base.connectionTypes as connTypes
30from lsst.pipe.tasks.insertFakes import InsertFakesConfig
32__all__ = ["MatchFakesTask",
33 "MatchFakesConfig",
34 "MatchFakesConnections"]
37class MatchFakesConnections(PipelineTaskConnections,
38 defaultTemplates={"coaddName": "deep",
39 "fakesType": "fakes_"},
40 dimensions=("tract",
41 "skymap",
42 "instrument",
43 "visit",
44 "detector")):
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")
50 )
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"),
56 )
57 associatedDiaSources = connTypes.Input(
58 doc="Optional output storing the DiaSource catalog after matching and "
59 "SDMification.",
60 name="{fakesType}{coaddName}Diff_assocDiaSrc",
61 storageClass="DataFrame",
62 dimensions=("instrument", "visit", "detector"),
63 )
64 matchedDiaSources = connTypes.Output(
65 doc="",
66 name="{fakesType}{coaddName}Diff_matchDiaSrc",
67 storageClass="DataFrame",
68 dimensions=("instrument", "visit", "detector"),
69 )
72class MatchFakesConfig(
73 InsertFakesConfig,
74 pipelineConnections=MatchFakesConnections):
75 """Config for MatchFakesTask.
76 """
77 matchDistanceArcseconds = pexConfig.RangeField(
78 doc="Distance in arcseconds to ",
79 dtype=float,
80 default=0.5,
81 min=0,
82 max=10,
83 )
86class 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
90 template exposure.
91 """
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.
105 Parameters
106 ----------
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``.
114 Returns
115 -------
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`)
121 """
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(
133 fakeVects,
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))
141 return Struct(
142 matchedDiaSources=matchedFakes.merge(
143 associatedDiaSources.reset_index(drop=True), on="diaSourceId", how="left")
144 )
146 def _trimFakeCat(self, fakeCat, image):
147 """Trim the fake cat to about the size of the input image.
149 Parameters
150 ----------
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
156 Returns
157 -------
158 fakeCat : `pandas.core.frame.DataFrame`
159 The original fakeCat trimmed to the area of the image
160 """
161 wcs = image.getWcs()
163 bbox = Box2D(image.getBBox())
165 def trim(row):
166 coord = SpherePoint(row[self.config.raColName],
167 row[self.config.decColName],
168 radians)
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.
177 Parameters
178 ----------
179 ras : `numpy.ndarray`, (N,)
180 RA coordinates in radians.
181 decs : `numpy.ndarray`, (N,)
182 Dec coordinates in radians.
184 Returns
185 -------
186 vectors : `numpy.ndarray`, (N, 3)
187 Vectors on the unit sphere for the given RA/DEC values.
188 """
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)
195 return vectors