Coverage for python/lsst/faro/base/MatchedCatalogBase.py : 22%

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
1import lsst.pipe.base as pipeBase
2import lsst.pex.config as pexConfig
3import lsst.geom as geom
4import numpy as np
6from lsst.faro.utils.matcher import matchCatalogs
8__all__ = (
9 "MatchedBaseTaskConnections",
10 "MatchedBaseConfig",
11 "MatchedBaseTask",
12 "MatchedTractBaseTask",
13)
16class MatchedBaseTaskConnections(
17 pipeBase.PipelineTaskConnections,
18 dimensions=(),
19 defaultTemplates={
20 "coaddName": "deep",
21 "photoCalibName": "calexp.photoCalib",
22 "wcsName": "calexp.wcs",
23 "externalPhotoCalibName": "fgcm",
24 "externalWcsName": "jointcal",
25 },
26):
27 sourceCatalogs = pipeBase.connectionTypes.Input(
28 doc="Source catalogs to match up.",
29 dimensions=("instrument", "visit", "detector", "band"),
30 storageClass="SourceCatalog",
31 name="src",
32 multiple=True,
33 )
34 photoCalibs = pipeBase.connectionTypes.Input(
35 doc="Photometric calibration object.",
36 dimensions=("instrument", "visit", "detector", "band"),
37 storageClass="PhotoCalib",
38 name="{photoCalibName}",
39 multiple=True,
40 )
41 astromCalibs = pipeBase.connectionTypes.Input(
42 doc="WCS for the catalog.",
43 dimensions=("instrument", "visit", "detector", "band"),
44 storageClass="Wcs",
45 name="{wcsName}",
46 multiple=True,
47 )
48 externalSkyWcsTractCatalog = pipeBase.connectionTypes.Input(
49 doc=(
50 "Per-tract, per-visit wcs calibrations. These catalogs use the detector "
51 "id for the catalog id, sorted on id for fast lookup."
52 ),
53 name="{externalWcsName}SkyWcsCatalog",
54 storageClass="ExposureCatalog",
55 dimensions=("instrument", "visit", "tract", "band"),
56 multiple=True,
57 )
58 externalSkyWcsGlobalCatalog = pipeBase.connectionTypes.Input(
59 doc=(
60 "Per-visit wcs calibrations computed globally (with no tract information). "
61 "These catalogs use the detector id for the catalog id, sorted on id for "
62 "fast lookup."
63 ),
64 name="{externalWcsName}SkyWcsCatalog",
65 storageClass="ExposureCatalog",
66 dimensions=("instrument", "visit", "band"),
67 multiple=True,
68 )
69 externalPhotoCalibTractCatalog = pipeBase.connectionTypes.Input(
70 doc=(
71 "Per-tract, per-visit photometric calibrations. These catalogs use the "
72 "detector id for the catalog id, sorted on id for fast lookup."
73 ),
74 name="{externalPhotoCalibName}PhotoCalibCatalog",
75 storageClass="ExposureCatalog",
76 dimensions=("instrument", "visit", "tract", "band"),
77 multiple=True,
78 )
79 externalPhotoCalibGlobalCatalog = pipeBase.connectionTypes.Input(
80 doc=(
81 "Per-visit photometric calibrations computed globally (with no tract "
82 "information). These catalogs use the detector id for the catalog id, "
83 "sorted on id for fast lookup."
84 ),
85 name="{externalPhotoCalibName}PhotoCalibCatalog",
86 storageClass="ExposureCatalog",
87 dimensions=("instrument", "visit", "band"),
88 multiple=True,
89 )
90 skyMap = pipeBase.connectionTypes.Input(
91 doc="Input definition of geometry/bbox and projection/wcs for warped exposures",
92 name="skyMap",
93 storageClass="SkyMap",
94 dimensions=("skymap",),
95 )
97 def __init__(self, *, config=None):
98 super().__init__(config=config)
99 if config.doApplyExternalSkyWcs:
100 if config.useGlobalExternalSkyWcs:
101 self.inputs.remove("externalSkyWcsTractCatalog")
102 else:
103 self.inputs.remove("externalSkyWcsGlobalCatalog")
104 else:
105 self.inputs.remove("externalSkyWcsTractCatalog")
106 self.inputs.remove("externalSkyWcsGlobalCatalog")
107 if config.doApplyExternalPhotoCalib:
108 if config.useGlobalExternalPhotoCalib:
109 self.inputs.remove("externalPhotoCalibTractCatalog")
110 else:
111 self.inputs.remove("externalPhotoCalibGlobalCatalog")
112 else:
113 self.inputs.remove("externalPhotoCalibTractCatalog")
114 self.inputs.remove("externalPhotoCalibGlobalCatalog")
117class MatchedBaseConfig(
118 pipeBase.PipelineTaskConfig, pipelineConnections=MatchedBaseTaskConnections
119):
120 match_radius = pexConfig.Field(
121 doc="Match radius in arcseconds.", dtype=float, default=1
122 )
123 doApplyExternalSkyWcs = pexConfig.Field(
124 doc="Whether or not to use the external wcs.", dtype=bool, default=False
125 )
126 useGlobalExternalSkyWcs = pexConfig.Field(
127 doc="Whether or not to use the global external wcs.", dtype=bool, default=False
128 )
129 doApplyExternalPhotoCalib = pexConfig.Field(
130 doc="Whether or not to use the external photoCalib.", dtype=bool, default=False
131 )
132 useGlobalExternalPhotoCalib = pexConfig.Field(
133 doc="Whether or not to use the global external photoCalib.",
134 dtype=bool,
135 default=False,
136 )
139class MatchedBaseTask(pipeBase.PipelineTask):
141 ConfigClass = MatchedBaseConfig
142 _DefaultName = "matchedBaseTask"
144 def __init__(self, config: MatchedBaseConfig, *args, **kwargs):
145 super().__init__(*args, config=config, **kwargs)
146 self.radius = self.config.match_radius
147 self.level = "patch"
149 def run(
150 self,
151 sourceCatalogs,
152 photoCalibs,
153 astromCalibs,
154 dataIds,
155 wcs,
156 box,
157 doApplyExternalSkyWcs=False,
158 doApplyExternalPhotoCalib=False,
159 ):
160 self.log.info("Running catalog matching")
161 radius = geom.Angle(self.radius, geom.arcseconds)
162 srcvis, matched = matchCatalogs(
163 sourceCatalogs, photoCalibs, astromCalibs, dataIds, radius, logger=self.log
164 )
165 # Trim the output to the patch bounding box
166 out_matched = type(matched)(matched.schema)
167 self.log.info("%s sources in matched catalog.", len(matched))
168 for record in matched:
169 if box.contains(wcs.skyToPixel(record.getCoord())):
170 out_matched.append(record)
171 self.log.info(
172 "%s sources when trimmed to %s boundaries.", len(out_matched), self.level
173 )
174 return pipeBase.Struct(outputCatalog=out_matched)
176 def get_box_wcs(self, skymap, oid):
177 tract_info = skymap.generateTract(oid["tract"])
178 wcs = tract_info.getWcs()
179 patch_info = tract_info.getPatchInfo(oid["patch"])
180 patch_box = patch_info.getInnerBBox()
181 self.log.info("Running tract: %s and patch: %s", oid["tract"], oid["patch"])
182 return patch_box, wcs
184 def runQuantum(self, butlerQC, inputRefs, outputRefs):
185 inputs = butlerQC.get(inputRefs)
186 oid = outputRefs.outputCatalog.dataId.byName()
187 skymap = inputs["skyMap"]
188 del inputs["skyMap"]
189 box, wcs = self.get_box_wcs(skymap, oid)
190 # Cast to float to handle fractional pixels
191 box = geom.Box2D(box)
192 inputs["dataIds"] = [
193 butlerQC.registry.expandDataId(el.dataId) for el in inputRefs.sourceCatalogs
194 ]
195 inputs["wcs"] = wcs
196 inputs["box"] = box
197 inputs["doApplyExternalSkyWcs"] = self.config.doApplyExternalSkyWcs
198 inputs["doApplyExternalPhotoCalib"] = self.config.doApplyExternalPhotoCalib
200 if self.config.doApplyExternalPhotoCalib:
201 if self.config.useGlobalExternalPhotoCalib:
202 externalPhotoCalibCatalog = inputs.pop(
203 "externalPhotoCalibGlobalCatalog"
204 )
205 else:
206 externalPhotoCalibCatalog = inputs.pop("externalPhotoCalibTractCatalog")
208 flatPhotoCalibList = np.hstack(externalPhotoCalibCatalog)
209 visitPhotoCalibList = np.array(
210 [calib["visit"] for calib in flatPhotoCalibList]
211 )
212 detectorPhotoCalibList = np.array(
213 [calib["id"] for calib in flatPhotoCalibList]
214 )
216 if self.config.doApplyExternalSkyWcs:
217 if self.config.useGlobalExternalSkyWcs:
218 externalSkyWcsCatalog = inputs.pop("externalSkyWcsGlobalCatalog")
219 else:
220 externalSkyWcsCatalog = inputs.pop("externalSkyWcsTractCatalog")
222 flatSkyWcsList = np.hstack(externalSkyWcsCatalog)
223 visitSkyWcsList = np.array([calib["visit"] for calib in flatSkyWcsList])
224 detectorSkyWcsList = np.array([calib["id"] for calib in flatSkyWcsList])
226 if self.config.doApplyExternalPhotoCalib:
227 for i in range(len(inputs["dataIds"])):
228 dataId = inputs["dataIds"][i]
229 detector = dataId["detector"]
230 visit = dataId["visit"]
231 calib_find = (visitPhotoCalibList == visit) & (
232 detectorPhotoCalibList == detector
233 )
234 row = flatPhotoCalibList[calib_find]
235 externalPhotoCalib = row[0].getPhotoCalib()
236 inputs["photoCalibs"][i] = externalPhotoCalib
238 if self.config.doApplyExternalSkyWcs:
239 for i in range(len(inputs["dataIds"])):
240 dataId = inputs["dataIds"][i]
241 detector = dataId["detector"]
242 visit = dataId["visit"]
243 calib_find = (visitSkyWcsList == visit) & (
244 detectorSkyWcsList == detector
245 )
246 row = flatSkyWcsList[calib_find]
247 externalSkyWcs = row[0].getWcs()
248 inputs["astromCalibs"][i] = externalSkyWcs
250 outputs = self.run(**inputs)
251 butlerQC.put(outputs, outputRefs)
254class MatchedTractBaseTask(MatchedBaseTask):
256 ConfigClass = MatchedBaseConfig
257 _DefaultName = "matchedTractBaseTask"
259 def __init__(self, config: MatchedBaseConfig, *args, **kwargs):
260 super().__init__(*args, config=config, **kwargs)
261 self.radius = self.config.match_radius
262 self.level = "tract"
264 def get_box_wcs(self, skymap, oid):
265 tract_info = skymap.generateTract(oid["tract"])
266 wcs = tract_info.getWcs()
267 tract_box = tract_info.getBBox()
268 self.log.info("Running tract: %s", oid["tract"])
269 return tract_box, wcs