LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
matchFakes.py
Go to the documentation of this file.
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/>.
21 
22 import astropy.units as u
23 import numpy as np
24 from scipy.spatial import cKDTree
25 
26 from lsst.geom import Box2D, radians, SpherePoint
27 import lsst.pex.config as pexConfig
28 from lsst.pipe.base import PipelineTask, PipelineTaskConnections, Struct
29 import lsst.pipe.base.connectionTypes as connTypes
30 from lsst.pipe.tasks.insertFakes import InsertFakesConfig
31 
32 __all__ = ["MatchFakesTask",
33  "MatchFakesConfig",
34  "MatchVariableFakesConfig",
35  "MatchVariableFakesTask"]
36 
37 
38 class MatchFakesConnections(PipelineTaskConnections,
39  defaultTemplates={"coaddName": "deep",
40  "fakesType": "fakes_"},
41  dimensions=("tract",
42  "skymap",
43  "instrument",
44  "visit",
45  "detector")):
46  fakeCat = connTypes.Input(
47  doc="Catalog of fake sources inserted into an image.",
48  name="{fakesType}fakeSourceCat",
49  storageClass="DataFrame",
50  dimensions=("tract", "skymap")
51  )
52  diffIm = connTypes.Input(
53  doc="Difference image on which the DiaSources were detected.",
54  name="{fakesType}{coaddName}Diff_differenceExp",
55  storageClass="ExposureF",
56  dimensions=("instrument", "visit", "detector"),
57  )
58  associatedDiaSources = connTypes.Input(
59  doc="A DiaSource catalog to match against fakeCat. Assumed "
60  "to be SDMified.",
61  name="{fakesType}{coaddName}Diff_assocDiaSrc",
62  storageClass="DataFrame",
63  dimensions=("instrument", "visit", "detector"),
64  )
65  matchedDiaSources = connTypes.Output(
66  doc="A catalog of those fakeCat sources that have a match in "
67  "associatedDiaSources. The schema is the union of the schemas for "
68  "``fakeCat`` and ``associatedDiaSources``.",
69  name="{fakesType}{coaddName}Diff_matchDiaSrc",
70  storageClass="DataFrame",
71  dimensions=("instrument", "visit", "detector"),
72  )
73 
74 
75 class MatchFakesConfig(
76  InsertFakesConfig,
77  pipelineConnections=MatchFakesConnections):
78  """Config for MatchFakesTask.
79  """
80  matchDistanceArcseconds = pexConfig.RangeField(
81  doc="Distance in arcseconds to match within.",
82  dtype=float,
83  default=0.5,
84  min=0,
85  max=10,
86  )
87 
88 
89 class MatchFakesTask(PipelineTask):
90  """Match a pre-existing catalog of fakes to a catalog of detections on
91  a difference image.
92 
93  This task is generally for injected sources that cannot be easily
94  identified by their footprints such as in the case of detector sources
95  post image differencing.
96  """
97 
98  _DefaultName = "matchFakes"
99  ConfigClass = MatchFakesConfig
100 
101  def runQuantum(self, butlerQC, inputRefs, outputRefs):
102  inputs = butlerQC.get(inputRefs)
103 
104  outputs = self.run(**inputs)
105  butlerQC.put(outputs, outputRefs)
106 
107  def run(self, fakeCat, diffIm, associatedDiaSources):
108  """Match fakes to detected diaSources within a difference image bound.
109 
110  Parameters
111  ----------
112  fakeCat : `pandas.DataFrame`
113  Catalog of fakes to match to detected diaSources.
114  diffIm : `lsst.afw.image.Exposure`
115  Difference image where ``associatedDiaSources`` were detected.
116  associatedDiaSources : `pandas.DataFrame`
117  Catalog of difference image sources detected in ``diffIm``.
118 
119  Returns
120  -------
121  result : `lsst.pipe.base.Struct`
122  Results struct with components.
123 
124  - ``matchedDiaSources`` : Fakes matched to input diaSources. Has
125  length of ``fakeCat``. (`pandas.DataFrame`)
126  """
127  trimmedFakes = self._trimFakeCat(fakeCat, diffIm)
128  nPossibleFakes = len(trimmedFakes)
129 
130  fakeVects = self._getVectors(trimmedFakes[self.config.raColName],
131  trimmedFakes[self.config.decColName])
132  diaSrcVects = self._getVectors(
133  np.radians(associatedDiaSources.loc[:, "ra"]),
134  np.radians(associatedDiaSources.loc[:, "decl"]))
135 
136  diaSrcTree = cKDTree(diaSrcVects)
137  dist, idxs = diaSrcTree.query(
138  fakeVects,
139  distance_upper_bound=np.radians(self.config.matchDistanceArcseconds / 3600))
140  nFakesFound = np.isfinite(dist).sum()
141 
142  self.log.info("Found %d out of %d possible.", nFakesFound, nPossibleFakes)
143  diaSrcIds = associatedDiaSources.iloc[np.where(np.isfinite(dist), idxs, 0)]["diaSourceId"].to_numpy()
144  matchedFakes = trimmedFakes.assign(diaSourceId=np.where(np.isfinite(dist), diaSrcIds, 0))
145 
146  return Struct(
147  matchedDiaSources=matchedFakes.merge(
148  associatedDiaSources.reset_index(drop=True), on="diaSourceId", how="left")
149  )
150 
151  def _trimFakeCat(self, fakeCat, image):
152  """Trim the fake cat to about the size of the input image.
153 
154  Parameters
155  ----------
156  fakeCat : `pandas.core.frame.DataFrame`
157  The catalog of fake sources to be input
158  image : `lsst.afw.image.exposure.exposure.ExposureF`
159  The image into which the fake sources should be added
160 
161  Returns
162  -------
163  fakeCat : `pandas.core.frame.DataFrame`
164  The original fakeCat trimmed to the area of the image
165  """
166  wcs = image.getWcs()
167 
168  bbox = Box2D(image.getBBox())
169 
170  def trim(row):
171  coord = SpherePoint(row[self.config.raColName],
172  row[self.config.decColName],
173  radians)
174  cent = wcs.skyToPixel(coord)
175  return bbox.contains(cent)
176 
177  return fakeCat[fakeCat.apply(trim, axis=1)]
178 
179  def _getVectors(self, ras, decs):
180  """Convert ra dec to unit vectors on the sphere.
181 
182  Parameters
183  ----------
184  ras : `numpy.ndarray`, (N,)
185  RA coordinates in radians.
186  decs : `numpy.ndarray`, (N,)
187  Dec coordinates in radians.
188 
189  Returns
190  -------
191  vectors : `numpy.ndarray`, (N, 3)
192  Vectors on the unit sphere for the given RA/DEC values.
193  """
194  vectors = np.empty((len(ras), 3))
195 
196  vectors[:, 2] = np.sin(decs)
197  vectors[:, 0] = np.cos(decs) * np.cos(ras)
198  vectors[:, 1] = np.cos(decs) * np.sin(ras)
199 
200  return vectors
201 
202 
203 class MatchVariableFakesConnections(MatchFakesConnections):
204  ccdVisitFakeMagnitudes = connTypes.Input(
205  doc="Catalog of fakes with magnitudes scattered for this ccdVisit.",
206  name="{fakesType}ccdVisitFakeMagnitudes",
207  storageClass="DataFrame",
208  dimensions=("instrument", "visit", "detector"),
209  )
210 
211 
212 class MatchVariableFakesConfig(MatchFakesConfig,
213  pipelineConnections=MatchVariableFakesConnections):
214  """Config for MatchFakesTask.
215  """
216  pass
217 
218 
219 class MatchVariableFakesTask(MatchFakesTask):
220  """Match injected fakes to their detected sources in the catalog and
221  compute their expected brightness in a difference image assuming perfect
222  subtraction.
223 
224  This task is generally for injected sources that cannot be easily
225  identified by their footprints such as in the case of detector sources
226  post image differencing.
227  """
228  _DefaultName = "matchVariableFakes"
229  ConfigClass = MatchVariableFakesConfig
230 
231  def runQuantum(self, butlerQC, inputRefs, outputRefs):
232  inputs = butlerQC.get(inputRefs)
233  inputs["band"] = butlerQC.quantum.dataId["band"]
234 
235  outputs = self.run(**inputs)
236  butlerQC.put(outputs, outputRefs)
237 
238  def run(self, fakeCat, ccdVisitFakeMagnitudes, diffIm, associatedDiaSources, band):
239  """Match fakes to detected diaSources within a difference image bound.
240 
241  Parameters
242  ----------
243  fakeCat : `pandas.DataFrame`
244  Catalog of fakes to match to detected diaSources.
245  diffIm : `lsst.afw.image.Exposure`
246  Difference image where ``associatedDiaSources`` were detected in.
247  associatedDiaSources : `pandas.DataFrame`
248  Catalog of difference image sources detected in ``diffIm``.
249 
250  Returns
251  -------
252  result : `lsst.pipe.base.Struct`
253  Results struct with components.
254 
255  - ``matchedDiaSources`` : Fakes matched to input diaSources. Has
256  length of ``fakeCat``. (`pandas.DataFrame`)
257  """
258  self.computeExpectedDiffMag(fakeCat, ccdVisitFakeMagnitudes, band)
259  return super().run(fakeCat, diffIm, associatedDiaSources)
260 
261  def computeExpectedDiffMag(self, fakeCat, ccdVisitFakeMagnitudes, band):
262  """Compute the magnitude expected in the difference image for this
263  detector/visit. Modify fakeCat in place.
264 
265  Negative magnitudes indicate that the source should be detected as
266  a negative source.
267 
268  Parameters
269  ----------
270  fakeCat : `pandas.DataFrame`
271  Catalog of fake sources.
272  ccdVisitFakeMagnitudes : `pandas.DataFrame`
273  Magnitudes for variable sources in this specific ccdVisit.
274  band : `str`
275  Band that this ccdVisit was observed in.
276  """
277  magName = self.config.mag_col % band
278  magnitudes = fakeCat[magName].to_numpy()
279  visitMags = ccdVisitFakeMagnitudes["variableMag"].to_numpy()
280  diffFlux = (visitMags * u.ABmag).to_value(u.nJy) - (magnitudes * u.ABmag).to_value(u.nJy)
281  diffMag = np.where(diffFlux > 0,
282  (diffFlux * u.nJy).to_value(u.ABmag),
283  -(-diffFlux * u.nJy).to_value(u.ABmag))
284 
285  noVisit = ~fakeCat["isVisitSource"]
286  noTemplate = ~fakeCat["isTemplateSource"]
287  both = np.logical_and(fakeCat["isVisitSource"],
288  fakeCat["isTemplateSource"])
289 
290  fakeCat.loc[noVisit, magName] = -magnitudes[noVisit]
291  fakeCat.loc[noTemplate, magName] = visitMags[noTemplate]
292  fakeCat.loc[both, magName] = diffMag[both]
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
def run(self, coaddExposures, bbox, wcs)
Definition: getTemplate.py:603