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