Coverage for python/lsst/afw/table/catalogMatches.py : 17%

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/>.
22__all__ = ["makeMergedSchema", "copyIntoCatalog",
23 "matchesToCatalog", "matchesFromCatalog", "copyAliasMapWithPrefix",
24 "reindexCatalog"]
26import os.path
28import numpy as np
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
39from lsst.utils import getPackageDir
42def makeMapper(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None):
43 """Create a SchemaMapper between the input source and target schemas.
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.
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
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.
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.
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()
95def copyIntoCatalog(catalog, target, sourceSchema=None, sourcePrefix=None, targetPrefix=None):
96 """Copy entries from one Catalog into another.
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
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
119 targetSchema = target.schema
120 target.reserve(len(catalog))
121 for i in range(len(target), len(catalog)):
122 target.addNew()
124 if len(catalog) != len(target):
125 raise RuntimeError(f"Length mismatch: {len(catalog)} vs {len(target)}")
127 m = makeMapper(sourceSchema, targetSchema, sourcePrefix, targetPrefix)
128 for rFrom, rTo in zip(catalog, target):
129 rTo.assign(rFrom, m)
132def matchesToCatalog(matches, matchMeta):
133 """Denormalise matches into a Catalog of "unpacked matches".
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).
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.")
156 refSchema = matches[0].first.getSchema()
157 srcSchema = matches[0].second.getSchema()
159 mergedSchema = makeMergedSchema(refSchema, Schema(), targetPrefix="ref_")
160 mergedSchema = makeMergedSchema(
161 srcSchema, mergedSchema, targetPrefix="src_")
163 mergedSchema = copyAliasMapWithPrefix(refSchema, mergedSchema, prefix="ref_")
164 mergedSchema = copyAliasMapWithPrefix(srcSchema, mergedSchema, prefix="src_")
166 distKey = mergedSchema.addField(
167 "distance", type=np.float64, doc="Distance between ref and src")
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)
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)
185 return mergedCatalog
188def matchesFromCatalog(catalog, sourceSlotConfig=None):
189 """Generate a list of ReferenceMatches from a Catalog of "unpacked matches".
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.
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_")
210 srcSchema = makeMergedSchema(
211 catalog.schema, SourceTable.makeMinimalSchema(), sourcePrefix="src_")
212 srcCatalog = SourceCatalog(srcSchema)
213 copyIntoCatalog(catalog, srcCatalog, sourcePrefix="src_")
215 if sourceSlotConfig is not None:
216 sourceSlotConfig.setupSchema(srcCatalog.schema)
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]))
223 return matches
226def copyAliasMapWithPrefix(inSchema, outSchema, prefix=""):
227 """Copy an alias map from one schema into another.
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).
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).
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)
257 return outSchema
260def reindexCatalog(catalog, indices, deep=True):
261 """Apply a numpy index array to an afw Catalog
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.
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