Hide keyboard shortcuts

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

1# This file is part of afw. 

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 

22__all__ = ["makeMergedSchema", "copyIntoCatalog", 

23 "matchesToCatalog", "matchesFromCatalog", "copyAliasMapWithPrefix", 

24 "reindexCatalog"] 

25 

26import os.path 

27 

28import numpy as np 

29 

30import lsst.pex.exceptions as pexExcept 

31from ._schema import Schema 

32from ._schemaMapper import SchemaMapper 

33from ._base import BaseCatalog 

34from ._table import SimpleTable 

35from ._simple import SimpleCatalog 

36from ._source import SourceCatalog, SourceTable 

37from ._match import ReferenceMatch 

38 

39from lsst.utils import getPackageDir 

40 

41 

42def makeMapper(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None): 

43 """Create a SchemaMapper between the input source and target schemas. 

44 

45 Parameters 

46 ---------- 

47 sourceSchema : :py:class:`lsst.afw.table.Schema` 

48 Input source schema that fields will be mapped from. 

49 targetSchema : :py:class:`lsst.afw.table.Schema` 

50 Target schema that fields will be mapped to. 

51 sourcePrefix : `str`, optional 

52 If set, only those keys with that prefix will be mapped. 

53 targetPrefix : `str`, optional 

54 If set, prepend it to the mapped (target) key name. 

55 

56 Returns 

57 ------- 

58 SchemaMapper : :py:class:`lsst.afw.table.SchemaMapper` 

59 Mapping between source and target schemas. 

60 """ 

61 m = SchemaMapper(sourceSchema, targetSchema) 

62 for key, field in sourceSchema: 

63 keyName = field.getName() 

64 if sourcePrefix is not None: 

65 if not keyName.startswith(sourcePrefix): 

66 continue 

67 else: 

68 keyName = field.getName().replace(sourcePrefix, "", 1) 

69 m.addMapping(key, (targetPrefix or "") + keyName) 

70 return m 

71 

72 

73def makeMergedSchema(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None): 

74 """Return a schema that is a deep copy of a mapping between source and target schemas. 

75 

76 Parameters 

77 ---------- 

78 sourceSchema : :py:class:`lsst.afw.table.Schema` 

79 Input source schema that fields will be mapped from. 

80 targetSchema : :py:class:`lsst.afw.atable.Schema` 

81 Target schema that fields will be mapped to. 

82 sourcePrefix : `str`, optional 

83 If set, only those keys with that prefix will be mapped. 

84 targetPrefix : `str`, optional 

85 If set, prepend it to the mapped (target) key name. 

86 

87 Returns 

88 ------- 

89 schema : :py:class:`lsst.afw.table.Schema` 

90 Schema that is the result of the mapping between source and target schemas. 

91 """ 

92 return makeMapper(sourceSchema, targetSchema, sourcePrefix, targetPrefix).getOutputSchema() 

93 

94 

95def copyIntoCatalog(catalog, target, sourceSchema=None, sourcePrefix=None, targetPrefix=None): 

96 """Copy entries from one Catalog into another. 

97 

98 Parameters 

99 ---------- 

100 catalog : :py:class:`lsst.afw.table.base.Catalog` 

101 Source catalog to be copied from. 

102 target : :py:class:`lsst.afw.table.base.Catalog` 

103 Target catalog to be copied to (edited in place). 

104 sourceSchema : :py:class:`lsst.afw.table.Schema`, optional 

105 Schema of source catalog. 

106 sourcePrefix : `str`, optional 

107 If set, only those keys with that prefix will be copied. 

108 targetPrefix : `str`, optional 

109 If set, prepend it to the copied (target) key name 

110 

111 Returns 

112 ------- 

113 target : :py:class:`lsst.afw.table.base.Catalog` 

114 Target catalog that is edited in place. 

115 """ 

116 if sourceSchema is None: 

117 sourceSchema = catalog.schema 

118 

119 targetSchema = target.schema 

120 target.reserve(len(catalog)) 

121 for i in range(len(target), len(catalog)): 

122 target.addNew() 

123 

124 if len(catalog) != len(target): 

125 raise RuntimeError(f"Length mismatch: {len(catalog)} vs {len(target)}") 

126 

127 m = makeMapper(sourceSchema, targetSchema, sourcePrefix, targetPrefix) 

128 for rFrom, rTo in zip(catalog, target): 

129 rTo.assign(rFrom, m) 

130 

131 

132def matchesToCatalog(matches, matchMeta): 

133 """Denormalise matches into a Catalog of "unpacked matches". 

134 

135 Parameters 

136 ---------- 

137 matches : `~lsst.afw.table.match.SimpleMatch` 

138 Unpacked matches, i.e. a list of Match objects whose schema 

139 has "first" and "second" attributes which, resepectively, 

140 contain the reference and source catalog entries, and a 

141 "distance" field (the measured distance between the reference 

142 and source objects). 

143 matchMeta : `~lsst.daf.base.PropertySet` 

144 Metadata for matches (must have .add attribute). 

145 

146 Returns 

147 ------- 

148 mergedCatalog : :py:class:`lsst.afw.table.BaseCatalog` 

149 Catalog of matches (with ``ref_`` and ``src_`` prefix identifiers for 

150 referece and source entries, respectively, including alias 

151 maps from reference and source catalogs) 

152 """ 

153 if len(matches) == 0: 

154 raise RuntimeError("No matches provided.") 

155 

156 refSchema = matches[0].first.getSchema() 

157 srcSchema = matches[0].second.getSchema() 

158 

159 mergedSchema = makeMergedSchema(refSchema, Schema(), targetPrefix="ref_") 

160 mergedSchema = makeMergedSchema( 

161 srcSchema, mergedSchema, targetPrefix="src_") 

162 

163 mergedSchema = copyAliasMapWithPrefix(refSchema, mergedSchema, prefix="ref_") 

164 mergedSchema = copyAliasMapWithPrefix(srcSchema, mergedSchema, prefix="src_") 

165 

166 distKey = mergedSchema.addField( 

167 "distance", type=np.float64, doc="Distance between ref and src") 

168 

169 mergedCatalog = BaseCatalog(mergedSchema) 

170 copyIntoCatalog([m.first for m in matches], mergedCatalog, 

171 sourceSchema=refSchema, targetPrefix="ref_") 

172 copyIntoCatalog([m.second for m in matches], mergedCatalog, 

173 sourceSchema=srcSchema, targetPrefix="src_") 

174 for m, r in zip(matches, mergedCatalog): 

175 r.set(distKey, m.distance) 

176 

177 # obtain reference catalog name if one is setup 

178 try: 

179 catalogName = os.path.basename(getPackageDir("astrometry_net_data")) 

180 except pexExcept.NotFoundError: 

181 catalogName = "NOT_SET" 

182 matchMeta.add("REFCAT", catalogName) 

183 mergedCatalog.getTable().setMetadata(matchMeta) 

184 

185 return mergedCatalog 

186 

187 

188def matchesFromCatalog(catalog, sourceSlotConfig=None): 

189 """Generate a list of ReferenceMatches from a Catalog of "unpacked matches". 

190 

191 Parameters 

192 ---------- 

193 catalog : :py:class:`lsst.afw.table.BaseCatalog` 

194 Catalog of matches. Must have schema where reference entries 

195 are prefixed with ``ref_`` and source entries are prefixed with 

196 ``src_``. 

197 sourceSlotConfig : `lsst.meas.base.baseMeasurement.SourceSlotConfig`, optional 

198 Configuration for source slots. 

199 

200 Returns 

201 ------- 

202 matches : :py:class:`lsst.afw.table.ReferenceMatch` 

203 List of matches. 

204 """ 

205 refSchema = makeMergedSchema( 

206 catalog.schema, SimpleTable.makeMinimalSchema(), sourcePrefix="ref_") 

207 refCatalog = SimpleCatalog(refSchema) 

208 copyIntoCatalog(catalog, refCatalog, sourcePrefix="ref_") 

209 

210 srcSchema = makeMergedSchema( 

211 catalog.schema, SourceTable.makeMinimalSchema(), sourcePrefix="src_") 

212 srcCatalog = SourceCatalog(srcSchema) 

213 copyIntoCatalog(catalog, srcCatalog, sourcePrefix="src_") 

214 

215 if sourceSlotConfig is not None: 

216 sourceSlotConfig.setupSchema(srcCatalog.schema) 

217 

218 matches = [] 

219 distKey = catalog.schema.find("distance").key 

220 for ref, src, cat in zip(refCatalog, srcCatalog, catalog): 

221 matches.append(ReferenceMatch(ref, src, cat[distKey])) 

222 

223 return matches 

224 

225 

226def copyAliasMapWithPrefix(inSchema, outSchema, prefix=""): 

227 """Copy an alias map from one schema into another. 

228 

229 This copies the alias map of one schema into another, optionally 

230 prepending a prefix to both the "from" and "to" names of the alias 

231 (the example use case here is for the "match" catalog created by 

232 `lsst.meas.astrom.denormalizeMatches` where prefixes ``src_`` and 

233 ``ref_`` are added to the source and reference field entries, 

234 respectively). 

235 

236 Parameters 

237 ---------- 

238 inSchema : `lsst.afw.table.Schema` 

239 The input schema whose `lsst.afw.table.AliasMap` is to be 

240 copied to ``outSchema``. 

241 outSchema : `lsst.afw.table.Schema` 

242 The output schema into which the `lsst.afw.table.AliasMap` 

243 from ``inSchema`` is to be copied (modified in place). 

244 prefix : `str`, optional 

245 An optional prefix to add to both the "from" and "to" names 

246 of the alias (default is an empty string). 

247 

248 Returns 

249 ------- 

250 outSchema : `lsst.afw.table.Schema` 

251 The output schema with the alias mappings from `inSchema` 

252 added. 

253 """ 

254 for k, v in inSchema.getAliasMap().items(): 

255 outSchema.getAliasMap().set(prefix + k, prefix + v) 

256 

257 return outSchema 

258 

259 

260def reindexCatalog(catalog, indices, deep=True): 

261 """Apply a numpy index array to an afw Catalog 

262 

263 Parameters 

264 ---------- 

265 catalog : `lsst.afw.table.SourceCatalog` 

266 Catalog to reindex. 

267 indices : `numpy.ndarray` of `int` 

268 Index array. 

269 deep : `bool` 

270 Whether or not to make a deep copy of the original catalog. 

271 

272 Returns 

273 ------- 

274 new : subclass of `lsst.afw.table.BaseCatalog` 

275 Reindexed catalog. Records are shallow copies of those in ``catalog``. 

276 """ 

277 new = SourceCatalog(catalog.table.clone() if deep else catalog.table) 

278 records = [catalog[int(ii)] for ii in indices] 

279 new.extend(records, deep=deep) 

280 return new