Coverage for python/lsst/meas/astrom/match_probabilistic_task.py: 36%

62 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-03 10:36 +0000

1# This file is part of meas_astrom. 

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/>. 

21 

22import lsst.afw.geom as afwGeom 

23import lsst.geom as geom 

24import lsst.pipe.base as pipeBase 

25import lsst.utils as utils 

26from .matcher_probabilistic import MatchProbabilisticConfig, MatcherProbabilistic 

27 

28import logging 

29import numpy as np 

30import pandas as pd 

31from typing import Dict, List, Optional, Set, Tuple 

32 

33__all__ = ["MatchProbabilisticTask", "radec_to_xy"] 

34 

35 

36def radec_to_xy(ra_vec, dec_vec, factor, wcs: afwGeom.SkyWcs): 

37 radec_true = [ 

38 geom.SpherePoint(ra*factor, dec*factor, geom.degrees) 

39 for ra, dec in zip(ra_vec, dec_vec) 

40 ] 

41 return wcs.skyToPixel(radec_true) 

42 

43 

44class MatchProbabilisticTask(pipeBase.Task): 

45 """Run MatchProbabilistic on a reference and target catalog covering the same tract.""" 

46 

47 ConfigClass = MatchProbabilisticConfig 

48 _DefaultName = "matchProbabilistic" 

49 

50 @staticmethod 

51 def _apply_select_bool( 

52 catalog: pd.DataFrame, 

53 columns_true: List[str], 

54 columns_false: List[str], 

55 selection: Optional[np.array], 

56 ) -> np.array: 

57 """Apply additional boolean selection columns. 

58 

59 catalog : `pandas.DataFrame` 

60 The catalog to select from. 

61 columns_true : `list` [`str`] 

62 Columns that must be True for selection. 

63 columns_false : `list` [`str`] 

64 Columns that must be False for selection. 

65 selection : `numpy.array` 

66 A prior selection array. Default all true. 

67 

68 Returns 

69 ------- 

70 selection : `numpy.array` 

71 The final selection array. 

72 

73 """ 

74 select_additional = (len(columns_true) + len(columns_false)) > 0 

75 if select_additional: 

76 if selection is None: 

77 selection = np.ones(len(catalog), dtype=bool) 

78 for column in columns_true: 

79 # This is intended for boolean columns, so the behaviour for non-boolean is not obvious 

80 # More config options and/or using a ConfigurableActionField might be best 

81 values = catalog[column].values 

82 selection &= (np.isfinite(values) & (values != 0)) 

83 for column in columns_false: 

84 selection &= (catalog[column].values == 0) 

85 return selection 

86 

87 @property 

88 def columns_in_ref(self) -> Set[str]: 

89 return self.config.columns_in_ref 

90 

91 @property 

92 def columns_in_target(self) -> Set[str]: 

93 return self.config.columns_in_target 

94 

95 def match( 

96 self, 

97 catalog_ref: pd.DataFrame, 

98 catalog_target: pd.DataFrame, 

99 select_ref: np.array = None, 

100 select_target: np.array = None, 

101 wcs: afwGeom.SkyWcs = None, 

102 logger: logging.Logger = None, 

103 logging_n_rows: int = None, 

104 ) -> Tuple[pd.DataFrame, pd.DataFrame, Dict[int, str]]: 

105 """Match sources in a reference tract catalog with a target catalog. 

106 

107 Parameters 

108 ---------- 

109 catalog_ref : `pandas.DataFrame` 

110 A reference catalog to match objects/sources from. 

111 catalog_target : `pandas.DataFrame` 

112 A target catalog to match reference objects/sources to. 

113 select_ref : `numpy.array` 

114 A boolean array of the same length as `catalog_ref` selecting the sources that can be matched. 

115 select_target : `numpy.array` 

116 A boolean array of the same length as `catalog_target` selecting the sources that can be matched. 

117 wcs : `lsst.afw.image.SkyWcs` 

118 A coordinate system to convert catalog positions to sky coordinates. Only used if 

119 `self.config.coords_ref_to_convert` is set. 

120 logger : `logging.Logger` 

121 A Logger for logging. 

122 logging_n_rows : `int` 

123 Number of matches to make before outputting incremental log message. 

124 

125 Returns 

126 ------- 

127 catalog_out_ref : `pandas.DataFrame` 

128 Reference matched catalog with indices of target matches. 

129 catalog_out_target : `pandas.DataFrame` 

130 Reference matched catalog with indices of target matches. 

131 """ 

132 if logger is None: 

133 logger = self.log 

134 

135 config = self.config 

136 

137 if config.column_ref_order is None: 

138 flux_tot = np.nansum( 

139 catalog_ref.loc[:, config.columns_ref_flux].values, axis=1 

140 ) 

141 catalog_ref["flux_total"] = flux_tot 

142 if config.mag_brightest_ref != -np.inf or config.mag_faintest_ref != np.inf: 

143 mag_tot = ( 

144 -2.5 * np.log10(flux_tot) + config.coord_format.mag_zeropoint_ref 

145 ) 

146 select_mag = (mag_tot >= config.mag_brightest_ref) & ( 

147 mag_tot <= config.mag_faintest_ref 

148 ) 

149 else: 

150 select_mag = np.isfinite(flux_tot) 

151 if select_ref is None: 

152 select_ref = select_mag 

153 else: 

154 select_ref &= select_mag 

155 

156 select_ref = self._apply_select_bool( 

157 catalog=catalog_ref, 

158 columns_true=config.columns_ref_select_true, 

159 columns_false=config.columns_ref_select_false, 

160 selection=select_ref, 

161 ) 

162 select_target = self._apply_select_bool( 

163 catalog=catalog_target, 

164 columns_true=config.columns_target_select_true, 

165 columns_false=config.columns_target_select_false, 

166 selection=select_target, 

167 ) 

168 

169 logger.info( 

170 "Beginning MatcherProbabilistic.match with %d/%d ref sources selected vs %d/%d target", 

171 np.sum(select_ref), 

172 len(select_ref), 

173 np.sum(select_target), 

174 len(select_target), 

175 ) 

176 

177 catalog_out_ref, catalog_out_target, exceptions = self.matcher.match( 

178 catalog_ref, 

179 catalog_target, 

180 select_ref=select_ref, 

181 select_target=select_target, 

182 logger=logger, 

183 logging_n_rows=logging_n_rows, 

184 wcs=wcs, 

185 radec_to_xy_func=radec_to_xy, 

186 ) 

187 

188 return catalog_out_ref, catalog_out_target, exceptions 

189 

190 @utils.timer.timeMethod 

191 def run( 

192 self, 

193 catalog_ref: pd.DataFrame, 

194 catalog_target: pd.DataFrame, 

195 wcs: afwGeom.SkyWcs = None, 

196 **kwargs, 

197 ) -> pipeBase.Struct: 

198 """Match sources in a reference tract catalog with a target catalog. 

199 

200 Parameters 

201 ---------- 

202 catalog_ref : `pandas.DataFrame` 

203 A reference catalog to match objects/sources from. 

204 catalog_target : `pandas.DataFrame` 

205 A target catalog to match reference objects/sources to. 

206 wcs : `lsst.afw.image.SkyWcs` 

207 A coordinate system to convert catalog positions to sky coordinates. 

208 Only needed if `config.coords_ref_to_convert` is used to convert 

209 reference catalog sky coordinates to pixel positions. 

210 kwargs : Additional keyword arguments to pass to `match`. 

211 

212 Returns 

213 ------- 

214 retStruct : `lsst.pipe.base.Struct` 

215 A struct with output_ref and output_target attribute containing the 

216 output matched catalogs, as well as a dict 

217 """ 

218 catalog_ref.reset_index(inplace=True) 

219 catalog_target.reset_index(inplace=True) 

220 catalog_ref, catalog_target, exceptions = self.match( 

221 catalog_ref, catalog_target, wcs=wcs, **kwargs 

222 ) 

223 return pipeBase.Struct( 

224 cat_output_ref=catalog_ref, 

225 cat_output_target=catalog_target, 

226 exceptions=exceptions, 

227 ) 

228 

229 def __init__(self, **kwargs): 

230 super().__init__(**kwargs) 

231 self.matcher = MatcherProbabilistic(self.config)