Coverage for python/lsst/analysis/tools/tasks/photometricCatalogMatch.py: 28%
83 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-30 14:25 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-30 14:25 +0000
1# This file is part of analysis_tools.
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/>.
22__all__ = ("PhotometricCatalogMatchConfig", "PhotometricCatalogMatchTask")
25import lsst.geom
26import lsst.pipe.base as pipeBase
27import numpy as np
28from astropy.table import Table
29from astropy.time import Time
30from lsst.pipe.tasks.loadReferenceCatalog import LoadReferenceCatalogTask
32from ..actions.vector import VisitPlotFlagSelector
33from ..tasks.catalogMatch import CatalogMatchConfig, CatalogMatchConnections, CatalogMatchTask
36class PhotometricCatalogMatchConnections(CatalogMatchConnections):
37 pass
40class PhotometricCatalogMatchConfig(
41 CatalogMatchConfig, pipelineConnections=PhotometricCatalogMatchConnections
42):
43 def setDefaults(self):
44 super().setDefaults()
45 self.referenceCatalogLoader.doReferenceSelection = False
46 self.referenceCatalogLoader.doApplyColorTerms = True
49class PhotometricCatalogMatchTask(CatalogMatchTask):
50 """A wrapper task to provide the information that
51 is specific to the photometric reference catalog.
52 """
54 ConfigClass = PhotometricCatalogMatchConfig
55 _DefaultName = "analysisToolsPhotometricCatalogMatch"
57 def runQuantum(self, butlerQC, inputRefs, outputRefs):
58 """Run the matching to the photometric reference
59 catalog.
61 Parameters
62 ----------
63 `butlerQC` : lsst.pipe.base.butlerQuantumContext.ButlerQuantumContext
64 `inputRefs` : lsst.pipe.base.connections.InputQuantizedConnection
65 `outputRefs` : lsst.pipe.base.connections.OutputQuantizedConnection
67 """
69 inputs = butlerQC.get(inputRefs)
70 bands = []
71 for filterName in self.config.filterNames:
72 bands.append(self.config.referenceCatalogLoader.refObjLoader.filterMap[filterName])
74 columns = self.prepColumns(bands)
75 table = inputs["catalog"].get(parameters={"columns": columns})
77 tract = butlerQC.quantum.dataId["tract"]
79 loaderTask = LoadReferenceCatalogTask(
80 config=self.config.referenceCatalogLoader,
81 dataIds=[ref.dataId for ref in inputRefs.refCat],
82 name=inputs["refCat"][0].ref.datasetType.name,
83 refCats=inputs["refCat"],
84 )
86 skymap = inputs.pop("skymap")
87 loadedRefCat = self._loadRefCat(loaderTask, skymap[tract])
88 outputs = self.run(catalog=table, loadedRefCat=loadedRefCat, bands=bands)
90 butlerQC.put(outputs, outputRefs)
93class PhotometricCatalogMatchVisitConnections(
94 pipeBase.PipelineTaskConnections,
95 dimensions=("visit",),
96 defaultTemplates={"targetCatalog": "sourceTable_visit", "refCatalog": "ps1_pv3_3pi_20170110"},
97):
98 catalog = pipeBase.connectionTypes.Input(
99 doc="The visit-wide catalog to make plots from.",
100 storageClass="ArrowAstropy",
101 name="{targetCatalog}",
102 dimensions=("visit",),
103 deferLoad=True,
104 )
106 refCat = pipeBase.connectionTypes.PrerequisiteInput(
107 doc="The photometric reference catalog to match to.",
108 name="{refCatalog}",
109 storageClass="SimpleCatalog",
110 dimensions=("skypix",),
111 deferLoad=True,
112 multiple=True,
113 )
115 visitSummaryTable = pipeBase.connectionTypes.Input(
116 doc="A summary table of the ccds in the visit",
117 storageClass="ExposureCatalog",
118 name="visitSummary",
119 dimensions=("visit",),
120 )
122 matchedCatalog = pipeBase.connectionTypes.Output(
123 doc="Catalog with matched target and reference objects with separations",
124 name="{targetCatalog}_{refCatalog}_match",
125 storageClass="ArrowAstropy",
126 dimensions=("visit",),
127 )
130class PhotometricCatalogMatchVisitConfig(
131 PhotometricCatalogMatchConfig, pipelineConnections=PhotometricCatalogMatchVisitConnections
132):
133 def setDefaults(self):
134 self.filterNames = []
135 self.extraPerBandColumns = []
136 self.patchColumn = ""
137 self.selectorBands = []
138 self.selectorActions.flagSelector = VisitPlotFlagSelector
139 self.sourceSelectorActions.sourceSelector.vectorKey = "extendedness"
140 self.extraColumnSelectors.selector1.fluxType = "psfFlux"
141 self.extraColumnSelectors.selector2.vectorKey = "extendedness"
144class PhotometricCatalogMatchVisitTask(PhotometricCatalogMatchTask):
145 """A wrapper task to provide the information that
146 is specific to the photometric reference catalog.
147 """
149 ConfigClass = PhotometricCatalogMatchVisitConfig
150 _DefaultName = "analysisToolsPhotometricCatalogMatchVisit"
152 def runQuantum(self, butlerQC, inputRefs, outputRefs):
153 """Run the matching to the photometric reference
154 catalog.
156 Parameters
157 ----------
158 `butlerQC` : lsst.pipe.base.butlerQuantumContext.ButlerQuantumContext
159 `inputRefs` : lsst.pipe.base.connections.InputQuantizedConnection
160 `outputRefs` : lsst.pipe.base.connections.OutputQuantizedConnection
162 """
164 inputs = butlerQC.get(inputRefs)
165 physicalFilter = inputs["catalog"].dataId["physical_filter"]
166 bands = [self.config.referenceCatalogLoader.refObjLoader.filterMap[physicalFilter]]
167 # No bands needed for visit tables
168 # but we do need them later for the matching
169 columns = ["coord_ra", "coord_dec", "detector"] + self.config.extraColumns.list()
170 for selectorAction in [
171 self.config.selectorActions,
172 self.config.sourceSelectorActions,
173 self.config.extraColumnSelectors,
174 ]:
175 for selector in selectorAction:
176 selectorSchema = selector.getFormattedInputSchema()
177 columns += [s[0] for s in selectorSchema]
179 table = inputs["catalog"].get(parameters={"columns": columns})
181 loaderTask = LoadReferenceCatalogTask(
182 config=self.config.referenceCatalogLoader,
183 dataIds=[ref.dataId for ref in inputRefs.refCat],
184 name=inputs["refCat"][0].ref.datasetType.name,
185 refCats=inputs["refCat"],
186 )
188 visitSummaryTable = inputs.pop("visitSummaryTable")
189 loadedRefCat = self._loadRefCat(loaderTask, visitSummaryTable, physicalFilter)
190 outputs = self.run(catalog=table, loadedRefCat=loadedRefCat, bands=bands)
192 # The matcher adds the band to the front of the columns
193 # but the visit plots aren't expecting it
194 cols = list(outputs.matchedCatalog.columns)
195 for col in cols:
196 if col[:2] == bands[0] + "_":
197 outputs.matchedCatalog.rename_column(col, col[2:])
199 butlerQC.put(outputs, outputRefs)
201 def _loadRefCat(self, loaderTask, visitSummaryTable, physicalFilter):
202 """Make a reference catalog with coordinates in degrees
204 Parameters
205 ----------
206 visitSummaryTable : `lsst.afw.table.ExposureCatalog`
207 The table of visit information
208 """
209 # Get convex hull around the detectors, then get its center and radius
210 corners = []
211 for visSum in visitSummaryTable:
212 for ra, dec in zip(visSum["raCorners"], visSum["decCorners"]):
213 # If the coordinates are nan then don't keep going
214 # because it crashes later
215 if not np.isfinite(ra) or not np.isfinite(dec):
216 raise pipeBase.NoWorkFound("Visit summary corners not finite")
217 corners.append(lsst.geom.SpherePoint(ra, dec, units=lsst.geom.degrees).getVector())
218 visitBoundingCircle = lsst.sphgeom.ConvexPolygon.convexHull(corners).getBoundingCircle()
219 center = lsst.geom.SpherePoint(visitBoundingCircle.getCenter())
220 radius = visitBoundingCircle.getOpeningAngle()
222 # Get the observation date of the visit
223 obsDate = visSum.getVisitInfo().getDate()
224 epoch = Time(obsDate.toPython())
226 # Load the reference catalog in the skyCircle of the detectors, then
227 # convert the coordinates to degrees and convert the catalog to a
228 # dataframe
230 loadedRefCat = loaderTask.getSkyCircleCatalog(center, radius, [physicalFilter], epoch=epoch)
232 return Table(loadedRefCat)