lsst.afw  22.0.1-28-g197addcb9+cd0c1e0a06
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 
26 import os.path
27 
28 import numpy as np
29 
30 import lsst.pex.exceptions as pexExcept
31 from ._schema import Schema
32 from ._schemaMapper import SchemaMapper
33 from ._base import BaseCatalog
34 from ._table import SimpleTable
35 from ._simple import SimpleCatalog
36 from ._source import SourceCatalog, SourceTable
37 from ._match import ReferenceMatch
38 
39 from lsst.utils import getPackageDir
40 
41 
42 def 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 
73 def 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 
95 def 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 
132 def 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"))
181  catalogName = "NOT_SET"
182  matchMeta.add("REFCAT", catalogName)
183  mergedCatalog.getTable().setMetadata(matchMeta)
184 
185  return mergedCatalog
186 
187 
188 def 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 
226 def 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 
260 def 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
std::vector< SchemaItem< Flag > > * items
daf::base::PropertySet * set
Definition: fits.cc:912
def makeMergedSchema(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None)
def makeMapper(sourceSchema, targetSchema, sourcePrefix=None, targetPrefix=None)
def matchesToCatalog(matches, matchMeta)
def reindexCatalog(catalog, indices, deep=True)
def copyAliasMapWithPrefix(inSchema, outSchema, prefix="")
def matchesFromCatalog(catalog, sourceSlotConfig=None)
def copyIntoCatalog(catalog, target, sourceSchema=None, sourcePrefix=None, targetPrefix=None)
Match< SimpleRecord, SourceRecord > ReferenceMatch
Definition: fwd.h:104
SortedCatalogT< SimpleRecord > SimpleCatalog
Definition: fwd.h:79
CatalogT< BaseRecord > BaseCatalog
Definition: fwd.h:71