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

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
5from lsst.faro.utils.matcher import match_catalogs
7__all__ = ('MatchedBaseTaskConnections', 'MatchedBaseTaskConfig', 'MatchedBaseTask', 'MatchedTractBaseTask')
10class MatchedBaseTaskConnections(pipeBase.PipelineTaskConnections,
11 dimensions=(),
12 defaultTemplates={"coaddName": "deep",
13 "photoCalibName": "calexp.photoCalib",
14 "wcsName": "calexp.wcs"}):
15 source_catalogs = pipeBase.connectionTypes.Input(doc="Source catalogs to match up.",
16 dimensions=("instrument", "visit",
17 "detector", "band"),
18 storageClass="SourceCatalog",
19 name="src",
20 multiple=True)
21 photo_calibs = pipeBase.connectionTypes.Input(doc="Photometric calibration object.",
22 dimensions=("instrument", "visit",
23 "detector", "band"),
24 storageClass="PhotoCalib",
25 name="{photoCalibName}",
26 multiple=True)
27 astrom_calibs = pipeBase.connectionTypes.Input(doc="WCS for the catalog.",
28 dimensions=("instrument", "visit",
29 "detector", "band"),
30 storageClass="Wcs",
31 name="{wcsName}",
32 multiple=True)
33 skyMap = pipeBase.connectionTypes.Input(
34 doc="Input definition of geometry/bbox and projection/wcs for warped exposures",
35 name="skyMap",
36 storageClass="SkyMap",
37 dimensions=("skymap",),
38 )
40 # Hack, this is the only way to get a connection without fixed dims
41 # Inspired by:
42 # https://github.com/lsst/verify/blob/4816a2c/python/lsst/verify/tasks/metadataMetricTask.py#L65-L101
43 def __init__(self, *, config=None):
44 """Customize connection for the astrometric calibrations
46 Parameters
47 ----------
48 config : `MatchedBaseTaskConfig`
49 A config for `MatchedBaseTask` or one of its subclasses
50 """
51 super().__init__(config=config)
52 if config and config.wcsDimensions != self.astrom_calibs.dimensions:
53 new_astrom_calibs = pipeBase.connectionTypes.Input(
54 doc=self.astrom_calibs.doc,
55 dimensions=config.wcsDimensions,
56 storageClass=self.astrom_calibs.storageClass,
57 name=self.astrom_calibs.name,
58 multiple=self.astrom_calibs.multiple
59 )
60 self.astrom_calibs = new_astrom_calibs
61 self.allConnections['astrom_calibs'] = self.astrom_calibs
64class MatchedBaseTaskConfig(pipeBase.PipelineTaskConfig,
65 pipelineConnections=MatchedBaseTaskConnections):
66 match_radius = pexConfig.Field(doc="Match radius in arcseconds.", dtype=float, default=1)
67 apply_external_wcs = pexConfig.Field(doc="Apply correction to coordinates with e.g. a jointcal WCS.",
68 dtype=bool, default=False)
69 wcsDimensions = pexConfig.ListField(doc="Override the dimensions of the astrometric calibration objects",
70 dtype=str,
71 default=MatchedBaseTaskConnections.astrom_calibs.dimensions)
74class MatchedBaseTask(pipeBase.PipelineTask):
76 ConfigClass = MatchedBaseTaskConfig
77 _DefaultName = "matchedBaseTask"
79 def __init__(self, config: pipeBase.PipelineTaskConfig, *args, **kwargs):
80 super().__init__(*args, config=config, **kwargs)
81 self.radius = self.config.match_radius
82 self.level = "patch"
84 def run(self, source_catalogs, photo_calibs, astrom_calibs, vIds, wcs, box, apply_external_wcs):
85 self.log.info("Running catalog matching")
86 radius = geom.Angle(self.radius, geom.arcseconds)
87 srcvis, matched = match_catalogs(source_catalogs, photo_calibs, astrom_calibs, vIds, radius,
88 apply_external_wcs, logger=self.log)
89 # Trim the output to the patch bounding box
90 out_matched = type(matched)(matched.schema)
91 self.log.info(f"{len(matched)} sources in matched catalog.")
92 for record in matched:
93 if box.contains(wcs.skyToPixel(record.getCoord())):
94 out_matched.append(record)
95 self.log.info(f"{len(out_matched)} sources when trimmed to {self.level} boundaries.")
96 return pipeBase.Struct(outputCatalog=out_matched)
98 def get_box_wcs(self, skymap, oid):
99 tract_info = skymap.generateTract(oid['tract'])
100 wcs = tract_info.getWcs()
101 patch_info = tract_info.getPatchInfo(oid['patch'])
102 patch_box = patch_info.getInnerBBox()
103 self.log.info(f"Running tract: {oid['tract']} and patch: {oid['patch']}")
104 return patch_box, wcs
106 def runQuantum(self, butlerQC,
107 inputRefs,
108 outputRefs):
109 inputs = butlerQC.get(inputRefs)
110 oid = outputRefs.outputCatalog.dataId.byName()
111 skymap = inputs['skyMap']
112 del inputs['skyMap']
113 box, wcs = self.get_box_wcs(skymap, oid)
114 # Cast to float to handle fractional pixels
115 box = geom.Box2D(box)
116 inputs['vIds'] = [butlerQC.registry.expandDataId(el.dataId) for el in inputRefs.source_catalogs]
117 inputs['wcs'] = wcs
118 inputs['box'] = box
119 inputs['apply_external_wcs'] = self.config.apply_external_wcs
120 if inputs['apply_external_wcs'] and not inputs['astrom_calibs']:
121 self.log.warn('Task configured to apply an external WCS, but no external WCS datasets found.')
122 if not inputs['astrom_calibs']: # Fill with None if jointcal wcs doesn't exist
123 inputs['astrom_calibs'] = [None for el in inputs['photo_calibs']]
124 outputs = self.run(**inputs)
125 butlerQC.put(outputs, outputRefs)
128class MatchedTractBaseTask(MatchedBaseTask):
130 ConfigClass = MatchedBaseTaskConfig
131 _DefaultName = "matchedTractBaseTask"
133 def __init__(self, config: pipeBase.PipelineTaskConfig, *args, **kwargs):
134 super().__init__(*args, config=config, **kwargs)
135 self.radius = self.config.match_radius
136 self.level = "tract"
138 def get_box_wcs(self, skymap, oid):
139 tract_info = skymap.generateTract(oid['tract'])
140 wcs = tract_info.getWcs()
141 tract_box = tract_info.getBBox()
142 self.log.info(f"Running tract: {oid['tract']}")
143 return tract_box, wcs