Coverage for python/lsst/faro/measurement/TractMeasurementTasks.py: 41%
68 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-05 01:03 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-05 01:03 -0700
1# This file is part of faro.
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/>.
22from lsst.afw.table import SourceCatalog
23from lsst.pex.config import Config, Field
24from lsst.pipe.base import Struct, Task
25from lsst.verify import Measurement, Datum
27from lsst.faro.utils.stellar_locus import stellarLocusResid, calcQuartileClippedStats
28from lsst.faro.utils.matcher import makeMatchedPhotom
29from lsst.faro.utils.extinction_corr import extinction_corr
30from lsst.faro.utils.tex import calculateTEx
31from lsst.faro.utils.calibrated_catalog import CalibratedCatalog
33import astropy.units as u
34import numpy as np
35from typing import Dict, List
37__all__ = ("WPerpConfig", "WPerpTask", "TExConfig", "TExTask")
40class WPerpConfig(Config):
41 # These are cuts to apply to the r-band only:
42 bright_rmag_cut = Field(
43 doc="Bright limit of catalog entries to include", dtype=float, default=17.0
44 )
45 faint_rmag_cut = Field(
46 doc="Faint limit of catalog entries to include", dtype=float, default=23.0
47 )
50class WPerpTask(Task):
51 ConfigClass = WPerpConfig
52 _DefaultName = "WPerpTask"
54 def run(
55 self, metricName: str, data: Dict[str, List[CalibratedCatalog]],
56 ):
57 self.log.info("Measuring %s", metricName)
58 bands = set("gri")
60 if bands.issubset(set(data.keys())):
61 data = {b: data[b] for b in bands}
62 rgicatAll = makeMatchedPhotom(data)
63 magcut = (rgicatAll["base_PsfFlux_mag_r"] < self.config.faint_rmag_cut) & (
64 rgicatAll["base_PsfFlux_mag_r"] > self.config.bright_rmag_cut
65 )
66 self.log.info("Merged multiband catalog is %d rows (%d match mag cut)",
67 len(rgicatAll), np.sum(magcut))
68 rgicat = rgicatAll[magcut]
69 extVals = extinction_corr(rgicat, bands)
71 wPerp = self.calcWPerp(metricName, rgicat, extVals)
72 return wPerp
73 else:
74 return Struct(measurement=Measurement(metricName, np.nan * u.mmag))
76 def calcWPerp(self, metricName: str, phot: SourceCatalog, extinctionVals):
77 p1, p2, p1coeffs, p2coeffs = stellarLocusResid(
78 *[phot[f"base_PsfFlux_mag_{b}"] - extinctionVals[f"A_{b}"] for b in 'gri'],
79 )
81 if np.size(p2) > 2:
82 p2_rms = calcQuartileClippedStats(p2).rms * u.mag
83 extras = {
84 "p1_coeffs": Datum(
85 p1coeffs * u.Unit(""),
86 label="p1_coefficients",
87 description="p1 coeffs from wPerp fit",
88 ),
89 "p2_coeffs": Datum(
90 p2coeffs * u.Unit(""),
91 label="p2_coefficients",
92 description="p2_coeffs from wPerp fit",
93 ),
94 }
96 return Struct(
97 measurement=Measurement(metricName, p2_rms.to(u.mmag), extras=extras)
98 )
99 else:
100 return Struct(measurement=Measurement(metricName, np.nan * u.mmag))
103class TExConfig(Config):
104 minSep = Field(
105 doc="Inner radius of the annulus in arcmin", dtype=float, default=0.25
106 )
107 maxSep = Field(
108 doc="Outer radius of the annulus in arcmin", dtype=float, default=1.0
109 )
110 nbins = Field(doc="Number of log-spaced angular bins", dtype=int, default=10)
111 rhoStat = Field(doc="Rho statistic to be computed", dtype=int, default=1)
112 shearConvention = Field(
113 doc="Use shear ellipticity convention rather than distortion",
114 dtype=bool,
115 default=False,
116 )
117 columnPsf = Field(
118 doc="Column to use for PSF model shape moments",
119 dtype=str,
120 default="slot_PsfShape",
121 )
122 column = Field(
123 doc="Column to use for shape moments", dtype=str, default="slot_Shape"
124 )
125 brute = Field(
126 doc="Run treecorr in brute-force mode for improved reproducibility in tests",
127 dtype=bool,
128 default=False
129 )
130 # Eventually want to add option to use only PSF reserve stars
133class TExTask(Task):
134 ConfigClass = TExConfig
135 _DefaultName = "TExTask"
137 def run(
138 self, metricName, data: Dict[str, List[CalibratedCatalog]]
139 ):
140 bands = data.keys()
141 if len(bands) != 1:
142 raise RuntimeError(f'TEx task got bands: {bands} but expecting exactly one')
143 else:
144 data = data[list(bands)[0]]
146 self.log.info("Measuring %s", metricName)
148 result = calculateTEx(data, self.config)
149 if "corr" not in result.keys():
150 return Struct(measurement=Measurement(metricName, np.nan * u.Unit("")))
152 writeExtras = True
153 if writeExtras:
154 extras = {}
155 extras["radius"] = Datum(
156 result["radius"], label="radius", description="Separation (arcmin)."
157 )
158 extras["corr"] = Datum(
159 result["corr"], label="Correlation", description="Correlation."
160 )
161 extras["corrErr"] = Datum(
162 result["corrErr"],
163 label="Correlation Uncertianty",
164 description="Correlation Uncertainty.",
165 )
166 else:
167 extras = None
168 return Struct(
169 measurement=Measurement(
170 metricName, np.mean(np.abs(result["corr"])), extras=extras
171 )
172 )