lsst.afw g7876432fea+de8826df4f
Loading...
Searching...
No Matches
catalogMatches.py
Go to the documentation of this file.
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 numpy as np
27
28from ._schema import Schema
29from ._schemaMapper import SchemaMapper
30from ._base import BaseCatalog
31from ._table import SimpleTable
32from ._simple import SimpleCatalog
33from ._source import SourceCatalog, SourceTable
34from ._match import ReferenceMatch
35
36
37def makeMapper(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None):
38 """Create a SchemaMapper between the input source and target schemas.
39
40 Parameters
41 ----------
42 sourceSchema : :py:class:`lsst.afw.table.Schema`
43 Input source schema that fields will be mapped from.
44 targetSchema : :py:class:`lsst.afw.table.Schema`
45 Target schema that fields will be mapped to.
46 sourcePrefix : `str`, optional
47 If set, only those keys with that prefix will be mapped.
48 targetPrefix : `str`, optional
49 If set, prepend it to the mapped (target) key name.
50
51 Returns
52 -------
53 SchemaMapper : :py:class:`lsst.afw.table.SchemaMapper`
54 Mapping between source and target schemas.
55 """
56 m = SchemaMapper(sourceSchema, targetSchema)
57 for key, field in sourceSchema:
58 keyName = field.getName()
59 if sourcePrefix is not None:
60 if not keyName.startswith(sourcePrefix):
61 continue
62 else:
63 keyName = field.getName().replace(sourcePrefix, "", 1)
64 m.addMapping(key, (targetPrefix or "") + keyName)
65 return m
66
67
68def makeMergedSchema(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None):
69 """Return a schema that is a deep copy of a mapping between source and target schemas.
70
71 Parameters
72 ----------
73 sourceSchema : :py:class:`lsst.afw.table.Schema`
74 Input source schema that fields will be mapped from.
75 targetSchema : :py:class:`lsst.afw.atable.Schema`
76 Target schema that fields will be mapped to.
77 sourcePrefix : `str`, optional
78 If set, only those keys with that prefix will be mapped.
79 targetPrefix : `str`, optional
80 If set, prepend it to the mapped (target) key name.
81
82 Returns
83 -------
84 schema : :py:class:`lsst.afw.table.Schema`
85 Schema that is the result of the mapping between source and target schemas.
86 """
87 return makeMapper(sourceSchema, targetSchema, sourcePrefix, targetPrefix).getOutputSchema()
88
89
90def copyIntoCatalog(catalog, target, sourceSchema=None, sourcePrefix=None, targetPrefix=None):
91 """Copy entries from one Catalog into another.
92
93 Parameters
94 ----------
95 catalog : :py:class:`lsst.afw.table.base.Catalog`
96 Source catalog to be copied from.
97 target : :py:class:`lsst.afw.table.base.Catalog`
98 Target catalog to be copied to (edited in place).
99 sourceSchema : :py:class:`lsst.afw.table.Schema`, optional
100 Schema of source catalog.
101 sourcePrefix : `str`, optional
102 If set, only those keys with that prefix will be copied.
103 targetPrefix : `str`, optional
104 If set, prepend it to the copied (target) key name
105
106 Returns
107 -------
108 target : :py:class:`lsst.afw.table.base.Catalog`
109 Target catalog that is edited in place.
110 """
111 if sourceSchema is None:
112 sourceSchema = catalog.schema
113
114 targetSchema = target.schema
115 target.reserve(len(catalog))
116 for i in range(len(target), len(catalog)):
117 target.addNew()
118
119 if len(catalog) != len(target):
120 raise RuntimeError(f"Length mismatch: {len(catalog)} vs {len(target)}")
121
122 m = makeMapper(sourceSchema, targetSchema, sourcePrefix, targetPrefix)
123 for rFrom, rTo in zip(catalog, target):
124 rTo.assign(rFrom, m)
125
126
127def matchesToCatalog(matches, matchMeta):
128 """Denormalise matches into a Catalog of "unpacked matches".
129
130 Parameters
131 ----------
132 matches : `~lsst.afw.table.match.SimpleMatch`
133 Unpacked matches, i.e. a list of Match objects whose schema
134 has "first" and "second" attributes which, resepectively,
135 contain the reference and source catalog entries, and a
136 "distance" field (the measured distance between the reference
137 and source objects).
138 matchMeta : `~lsst.daf.base.PropertySet`
139 Metadata for matches (must have .add attribute).
140
141 Returns
142 -------
143 mergedCatalog : :py:class:`lsst.afw.table.BaseCatalog`
144 Catalog of matches (with ``ref_`` and ``src_`` prefix identifiers for
145 referece and source entries, respectively, including alias
146 maps from reference and source catalogs)
147 """
148 if len(matches) == 0:
149 raise RuntimeError("No matches provided.")
150
151 refSchema = matches[0].first.getSchema()
152 srcSchema = matches[0].second.getSchema()
153
154 mergedSchema = makeMergedSchema(refSchema, Schema(), targetPrefix="ref_")
155 mergedSchema = makeMergedSchema(
156 srcSchema, mergedSchema, targetPrefix="src_")
157
158 mergedSchema = copyAliasMapWithPrefix(refSchema, mergedSchema, prefix="ref_")
159 mergedSchema = copyAliasMapWithPrefix(srcSchema, mergedSchema, prefix="src_")
160
161 distKey = mergedSchema.addField(
162 "distance", type=np.float64, doc="Distance between ref and src")
163
164 mergedCatalog = BaseCatalog(mergedSchema)
165 copyIntoCatalog([m.first for m in matches], mergedCatalog,
166 sourceSchema=refSchema, targetPrefix="ref_")
167 copyIntoCatalog([m.second for m in matches], mergedCatalog,
168 sourceSchema=srcSchema, targetPrefix="src_")
169 for m, r in zip(matches, mergedCatalog):
170 r.set(distKey, m.distance)
171
172 # The reference catalog is not known.
173 catalogName = "NOT_SET"
174 matchMeta.add("REFCAT", catalogName)
175 mergedCatalog.getTable().setMetadata(matchMeta)
176
177 return mergedCatalog
178
179
180def matchesFromCatalog(catalog, sourceSlotConfig=None):
181 """Generate a list of ReferenceMatches from a Catalog of "unpacked matches".
182
183 Parameters
184 ----------
185 catalog : :py:class:`lsst.afw.table.BaseCatalog`
186 Catalog of matches. Must have schema where reference entries
187 are prefixed with ``ref_`` and source entries are prefixed with
188 ``src_``.
189 sourceSlotConfig : `lsst.meas.base.baseMeasurement.SourceSlotConfig`, optional
190 Configuration for source slots.
191
192 Returns
193 -------
194 matches : :py:class:`lsst.afw.table.ReferenceMatch`
195 List of matches.
196 """
197 refSchema = makeMergedSchema(
198 catalog.schema, SimpleTable.makeMinimalSchema(), sourcePrefix="ref_")
199 refCatalog = SimpleCatalog(refSchema)
200 copyIntoCatalog(catalog, refCatalog, sourcePrefix="ref_")
201
202 srcSchema = makeMergedSchema(
203 catalog.schema, SourceTable.makeMinimalSchema(), sourcePrefix="src_")
204 srcCatalog = SourceCatalog(srcSchema)
205 copyIntoCatalog(catalog, srcCatalog, sourcePrefix="src_")
206
207 if sourceSlotConfig is not None:
208 sourceSlotConfig.setupSchema(srcCatalog.schema)
209
210 matches = []
211 distKey = catalog.schema.find("distance").key
212 for ref, src, cat in zip(refCatalog, srcCatalog, catalog):
213 matches.append(ReferenceMatch(ref, src, cat[distKey]))
214
215 return matches
216
217
218def copyAliasMapWithPrefix(inSchema, outSchema, prefix=""):
219 """Copy an alias map from one schema into another.
220
221 This copies the alias map of one schema into another, optionally
222 prepending a prefix to both the "from" and "to" names of the alias
223 (the example use case here is for the "match" catalog created by
224 `lsst.meas.astrom.denormalizeMatches` where prefixes ``src_`` and
225 ``ref_`` are added to the source and reference field entries,
226 respectively).
227
228 Parameters
229 ----------
230 inSchema : `lsst.afw.table.Schema`
231 The input schema whose `lsst.afw.table.AliasMap` is to be
232 copied to ``outSchema``.
233 outSchema : `lsst.afw.table.Schema`
234 The output schema into which the `lsst.afw.table.AliasMap`
235 from ``inSchema`` is to be copied (modified in place).
236 prefix : `str`, optional
237 An optional prefix to add to both the "from" and "to" names
238 of the alias (default is an empty string).
239
240 Returns
241 -------
242 outSchema : `lsst.afw.table.Schema`
243 The output schema with the alias mappings from `inSchema`
244 added.
245 """
246 for k, v in inSchema.getAliasMap().items():
247 outSchema.getAliasMap().set(prefix + k, prefix + v)
248
249 return outSchema
250
251
252def reindexCatalog(catalog, indices, deep=True):
253 """Apply a numpy index array to an afw Catalog
254
255 Parameters
256 ----------
257 catalog : `lsst.afw.table.SourceCatalog`
258 Catalog to reindex.
259 indices : `numpy.ndarray` of `int`
260 Index array.
261 deep : `bool`
262 Whether or not to make a deep copy of the original catalog.
263
264 Returns
265 -------
266 new : subclass of `lsst.afw.table.BaseCatalog`
267 Reindexed catalog. Records are shallow copies of those in ``catalog``.
268 """
269 new = SourceCatalog(catalog.table.clone() if deep else catalog.table)
270 records = [catalog[int(ii)] for ii in indices]
271 new.extend(records, deep=deep)
272 return new
makeMapper(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None)
reindexCatalog(catalog, indices, deep=True)
copyAliasMapWithPrefix(inSchema, outSchema, prefix="")
copyIntoCatalog(catalog, target, sourceSchema=None, sourcePrefix=None, targetPrefix=None)
matchesToCatalog(matches, matchMeta)
matchesFromCatalog(catalog, sourceSlotConfig=None)
makeMergedSchema(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None)