1 from builtins
import zip
2 from builtins
import range
3 from builtins
import object
7 from .tableLib
import SchemaMapper, CoordKey, SourceRecord
11 def __init__(self, schema, dataIdFormat, coordField="coord", idField="id", radius=None,
12 RecordClass=SourceRecord):
13 """Initialize a multi-catalog match.
16 schema -------- schema shared by all catalogs to be included in the match.
17 dataIdFormat -- dict of name: type for all data ID keys (e.g. {"visit":int, "ccd":int}).
18 coordField ---- prefix for _ra and _dec fields that contain the coordinates to use for the match.
19 idField ------- name of the field in schema that contains unique object IDs.
20 radius -------- lsst.afw.geom.Angle; maximum separation for a match. Defaults to 0.5 arcseconds.
21 RecordClass --- type of record (a subclass of lsst.afw.table.BaseRecord) to expect in catalogs
25 radius = 0.5*lsst.afw.geom.arcseconds
27 raise ValueError(
"'radius' argument must be an Angle")
30 self.mapper.addMinimalSchema(schema,
True)
32 self.
idKey = schema.find(idField).key
34 outSchema = self.mapper.editOutputSchema()
35 self.
objectKey = outSchema.addField(
"object", type=numpy.int64, doc=
"Unique ID for joined sources")
36 for name, dataType
in dataIdFormat.items():
37 self.
dataIdKeys[name] = outSchema.addField(name, type=dataType, doc=
"'%s' data ID component")
49 self.
table = RecordClass.Table.make(self.mapper.getOutputSchema())
54 """Create a new result record from the given input record, using the given data ID and object ID
55 to fill in additional columns."""
56 outputRecord = self.table.copyRecord(inputRecord, self.
mapper)
57 for name, key
in self.dataIdKeys.items():
58 outputRecord.set(key, dataId[name])
62 def add(self, catalog, dataId):
63 """Add a new catalog to the match, corresponding to the given data ID.
67 for record
in catalog:
70 self.
reference = self.result.copy(deep=
False)
77 newById = {record.get(self.
idKey): record
for record
in catalog}
81 for refRecord, newRecord, distance
in matches:
83 if objById.pop(objId,
None)
is None:
86 self.ambiguous.add(objId)
87 if newById.pop(newRecord.get(self.
idKey),
None)
is None:
90 self.ambiguous.add(objId)
93 newToObj.setdefault(newRecord.get(self.
idKey), set()).
add(objId)
95 self.result.append(self.
makeRecord(newRecord, dataId, objId))
98 for newRecord
in newById.values():
101 self.result.append(resultRecord)
102 self.reference.append(resultRecord)
105 """Return the final match catalog, after sorting it by object, copying it to ensure contiguousness,
106 and optionally removing ambiguous matches.
108 After calling finish(), the in-progress state of the matcher is returned to the state it was
109 just after construction, with the exception of the object ID counter (which is not reset).
112 result = self.table.Catalog(self.
table)
113 for record
in self.
result:
115 result.append(record)
119 result = result.copy(deep=
True)
127 """A mapping (i.e. dict-like object) that provides convenient operations on the concatenated
128 catalogs returned by a MultiMatch object.
130 A GroupView provides access to a catalog of grouped objects, in which the grouping is indicated by
131 a field for which all records in a group have the same value. Once constructed, it allows operations
132 similar to those supported by SQL "GROUP BY", such as filtering and aggregate calculation.
136 def build(cls, catalog, groupField="object"):
137 """!Construct a GroupView from a concatenated catalog.
139 @param[in] catalog Input catalog, containing records grouped by a field in which all records
140 in the same group have the same value. Must be sorted by the group field.
141 @param[in] groupField Name or Key for the field that indicates groups.
143 groupKey = catalog.schema.find(groupField).key
144 ids, indices = numpy.unique(catalog.get(groupKey), return_index=
True)
145 groups = numpy.zeros(len(ids), dtype=object)
146 ends = list(indices[1:]) + [len(catalog)]
147 for n, (i1, i2)
in enumerate(zip(indices, ends)):
148 groups[n] = catalog[int(i1):int(i2)]
149 assert (groups[n].get(groupKey) == ids[n]).
all()
150 return cls(catalog.schema, ids, groups)
153 """Direct constructor; most users should call build() instead.
155 This constructor takes the constituent arrays of the object directly, to allow multiple
156 methods for construction.
164 """Return the number of groups"""
168 """Iterate over group field values"""
172 """Return the catalog subset that corresponds to an group field value"""
173 index = numpy.searchsorted(self.
ids, key)
174 if self.
ids[index] != key:
175 raise KeyError(
"Group with ID {0} not found".
format(key))
179 """Return a new GroupView that contains only groups for which the given predicate function
182 The predicate function is called once for each group, and passed a single argument: the subset
183 catalog for that group.
185 mask = numpy.zeros(len(self), dtype=bool)
186 for i
in range(len(self)):
187 mask[i] = predicate(self.
groups[i])
191 """!Run an aggregate function on each group, returning an array with one element for each group.
193 @param[in] function Callable object that computes the aggregate value. If field is None,
194 called with the entire subset catalog as an argument. If field is not
195 None, called with an array view into that field.
196 @param[in] field A string name or Key object that indicates a single field the aggregate
198 @param[in] dtype Data type for the output array.
200 result = numpy.zeros(len(self), dtype=dtype)
201 if field
is not None:
202 key = self.schema.find(field).key
203 f =
lambda cat: function(cat.get(key))
206 for i
in range(len(self)):
207 result[i] = f(self.
groups[i])
210 def apply(self, function, field=None, dtype=float):
211 """!Run a non-aggregate function on each group, returning an array with one element for each record.
213 @param[in] function Callable object that computes the aggregate value. If field is None,
214 called with the entire subset catalog as an argument. If field is not
215 None, called with an array view into that field.
216 @param[in] field A string name or Key object that indicates a single field the aggregate
218 @param[in] dtype Data type for the output array.
220 result = numpy.zeros(self.
count, dtype=dtype)
221 if field
is not None:
222 key = self.schema.find(field).key
223 f =
lambda cat: function(cat.get(key))
227 for i
in range(len(self)):
228 next = last + len(self.
groups[i])
229 result[last:next] = f(self.
groups[i])
bool all(CoordinateExpr< N > const &expr)
Return true if all elements are true.
def aggregate
Run an aggregate function on each group, returning an array with one element for each group...
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric)
def build
Construct a GroupView from a concatenated catalog.
A class representing an Angle.
def apply
Run a non-aggregate function on each group, returning an array with one element for each record...
A FunctorKey used to get or set celestial coordinates from a pair of Angle keys.