LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
catalog.h
Go to the documentation of this file.
1/*
2 * This file is part of afw.
3 *
4 * Developed for the LSST Data Management System.
5 * This product includes software developed by the LSST Project
6 * (https://www.lsst.org).
7 * See the COPYRIGHT file at the top-level directory of this distribution
8 * for details of code ownership.
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23#ifndef AFW_TABLE_PYTHON_CATALOG_H_INCLUDED
24#define AFW_TABLE_PYTHON_CATALOG_H_INCLUDED
25
26#include "pybind11/pybind11.h"
27
28#include "lsst/utils/python.h"
31
32namespace lsst {
33namespace afw {
34namespace table {
35namespace python {
36
37template <typename Record>
38using PyCatalog = pybind11::class_<CatalogT<Record>, std::shared_ptr<CatalogT<Record>>>;
39
41template <typename T, typename Record>
42ndarray::Array<typename Field<T>::Value const, 1, 1> _getArrayFromCatalog(
43 CatalogT<Record> const &catalog,
44 Key<T> const &key
45) {
46 ndarray::Array<typename Field<T>::Value, 1, 1> out = ndarray::allocate(catalog.size());
47 auto outIter = out.begin();
48 auto inIter = catalog.begin();
49 for (; inIter != catalog.end(); ++inIter, ++outIter) {
50 *outIter = inIter->get(key);
51 }
52 return out;
53}
54
55// Specialization of the above for lsst::geom::Angle: have to return a double array (in
56// radians), since NumPy arrays can't hold lsst::geom::Angles.
57template <typename Record>
58ndarray::Array<double const, 1, 1> _getArrayFromCatalog(
59 CatalogT<Record> const &catalog,
60 Key<lsst::geom::Angle> const &key
61) {
62 ndarray::Array<double, 1, 1> out = ndarray::allocate(catalog.size());
63 auto outIter = out.begin();
64 auto inIter = catalog.begin();
65 for (; inIter != catalog.end(); ++inIter, ++outIter) {
66 *outIter = inIter->get(key).asRadians();
67 }
68 return out;
69}
70
71template <typename Record>
73 CatalogT<Record> & catalog,
74 Key<Flag> const & key,
75 ndarray::Array<bool const, 1> const & array
76) {
77 if (array.size() != catalog.size()) {
78 throw LSST_EXCEPT(
80 (boost::format("Catalog has %d rows, while array has %d elements.")
81 % catalog.size() % array.size()).str()
82 );
83 }
84 auto catIter = catalog.begin();
85 auto arrayIter = array.begin();
86 for (; catIter != catalog.end(); ++catIter, ++arrayIter) {
87 catIter->set(key, *arrayIter);
88 }
89}
90
91template <typename Record>
93 CatalogT<Record> & catalog,
94 Key<Flag> const & key,
95 bool value
96) {
97 for (auto catIter = catalog.begin(); catIter != catalog.end(); ++catIter) {
98 catIter->set(key, value);
99 }
100}
101
102// Custom safe Python iterator for Catalog. See Catalog.__iter__ wrapper
103// for details.
104template <typename Record>
106public:
107
112
113 PyCatalogIndexIterator(CatalogT<Record> const * catalog, std::size_t index) : _catalog(catalog), _index(index) {}
114
116 if (_index < _catalog->size()) {
117 return _catalog->get(_index);
118 }
119 throw std::out_of_range(
120 "Catalog shrunk during iteration, invalidating this iterator."
121 );
122 }
123
125 ++_index;
126 return *this;
127 }
128
129 bool operator==(PyCatalogIndexIterator const & other) const {
130 return _catalog == other._catalog && _index == other._index;
131 }
132
133 bool operator!=(PyCatalogIndexIterator const & other) const {
134 return !(*this == other);
135 }
136
137private:
138 CatalogT<Record> const * _catalog;
139 std::size_t _index;
140};
141
150template <typename T, typename Record>
152 namespace py = pybind11;
153 using namespace pybind11::literals;
154
156 using Value = typename Field<T>::Value;
157
158 cls.def("isSorted", (bool (Catalog::*)(Key<T> const &) const) & Catalog::isSorted);
159 cls.def("sort", (void (Catalog::*)(Key<T> const &)) & Catalog::sort);
160 cls.def("find", [](Catalog &self, Value const &value, Key<T> const &key) -> std::shared_ptr<Record> {
161 auto iter = self.find(value, key);
162 if (iter == self.end()) {
163 return nullptr;
164 };
165 return iter;
166 });
167 cls.def("upper_bound", [](Catalog &self, Value const &value, Key<T> const &key) -> std::ptrdiff_t {
168 return self.upper_bound(value, key) - self.begin();
169 });
170 cls.def("lower_bound", [](Catalog &self, Value const &value, Key<T> const &key) -> std::ptrdiff_t {
171 return self.lower_bound(value, key) - self.begin();
172 });
173 cls.def("equal_range", [](Catalog &self, Value const &value, Key<T> const &key) {
174 auto p = self.equal_range(value, key);
175 return py::slice(p.first - self.begin(), p.second - self.begin(), 1);
176 });
177 cls.def("between", [](Catalog &self, Value const &lower, Value const &upper, Key<T> const &key) {
178 std::ptrdiff_t a = self.lower_bound(lower, key) - self.begin();
179 std::ptrdiff_t b = self.upper_bound(upper, key) - self.begin();
180 return py::slice(a, b, 1);
181 });
182
183 cls.def("_getitem_",
184 [](Catalog const &self, Key<T> const &key) { return _getArrayFromCatalog(self, key); });
185 cls.def(
186 "_set_flag",
187 [](Catalog &self, Key<Flag> const & key, ndarray::Array<bool const, 1> const & array) {
188 _setFlagColumnToArray(self, key, array);
189 }
190 );
191 cls.def(
192 "_set_flag",
193 [](Catalog &self, Key<Flag> const & key, bool value) {
194 _setFlagColumnToScalar(self, key, value);
195 }
196 );
197}
198
212template <typename Record>
213PyCatalog<Record> declareCatalog(utils::python::WrapperCollection &wrappers, std::string const &name,
214 bool isBase = false) {
215 namespace py = pybind11;
216 using namespace pybind11::literals;
217
219 using Table = typename Record::Table;
220
221 std::string fullName;
222 if (isBase) {
223 fullName = "_" + name + "CatalogBase";
224 } else {
225 fullName = name + "Catalog";
226 }
227
228 // We need py::dynamic_attr() in the class definition to support our Python-side caching
229 // of the associated ColumnView.
230 return wrappers.wrapType(
231 PyCatalog<Record>(wrappers.module, fullName.c_str(), py::dynamic_attr()),
232 [](auto &mod, auto &cls) {
233 /* Constructors */
234 cls.def(py::init<Schema const &>(), "schema"_a);
235 cls.def(py::init<std::shared_ptr<Table> const &>(), "table"_a);
236 cls.def(py::init<Catalog const &>(), "other"_a);
237
238 /* Static Methods */
239 cls.def_static("readFits", (Catalog(*)(std::string const &, int, int)) & Catalog::readFits,
240 "filename"_a, "hdu"_a = fits::DEFAULT_HDU, "flags"_a = 0);
241 cls.def_static("readFits", (Catalog(*)(fits::MemFileManager &, int, int)) & Catalog::readFits,
242 "manager"_a, "hdu"_a = fits::DEFAULT_HDU, "flags"_a = 0);
243 // readFits taking Fits objects not wrapped, because Fits objects are not wrapped.
244
245 /* Methods */
246 cls.def("getTable", &Catalog::getTable);
247 cls.def_property_readonly("table", &Catalog::getTable);
248 cls.def("getSchema", &Catalog::getSchema);
249 cls.def_property_readonly("schema", &Catalog::getSchema);
250 cls.def("capacity", &Catalog::capacity);
251 cls.def("__len__", &Catalog::size);
252 cls.def("resize", &Catalog::resize);
253
254 // Use private names for the following so the public Python method
255 // can manage the _column cache
256 cls.def("_getColumnView", &Catalog::getColumnView);
257 cls.def("_addNew", &Catalog::addNew);
258 cls.def("_extend", [](Catalog &self, Catalog const &other, bool deep) {
259 self.insert(self.end(), other.begin(), other.end(), deep);
260 });
261 cls.def("_extend", [](Catalog &self, Catalog const &other, SchemaMapper const &mapper) {
262 self.insert(mapper, self.end(), other.begin(), other.end());
263 });
264 cls.def("_append",
265 [](Catalog &self, std::shared_ptr<Record> const &rec) { self.push_back(rec); });
266 cls.def("_delitem_", [](Catalog &self, std::ptrdiff_t i) {
267 self.erase(self.begin() + utils::python::cppIndex(self.size(), i));
268 });
269 cls.def("_delslice_", [](Catalog &self, py::slice const &s) {
270 Py_ssize_t start = 0, stop = 0, step = 0, length = 0;
271 if (PySlice_GetIndicesEx(s.ptr(), self.size(), &start, &stop, &step, &length) != 0) {
272 throw py::error_already_set();
273 }
274 if (step != 1) {
275 throw py::index_error("Slice step must not exactly 1");
276 }
277 self.erase(self.begin() + start, self.begin() + stop);
278 });
279 cls.def("_clear", &Catalog::clear);
280
281 cls.def("set", &Catalog::set);
282 cls.def("_getitem_", [](Catalog &self, int i) {
283 return self.get(utils::python::cppIndex(self.size(), i));
284 });
285 cls.def("__iter__", [](Catalog & self) {
286 // We wrap a custom iterator class here for two reasons:
287 //
288 // - letting Python define an automatic iterator that
289 // delegates to __getitem__(int) is super slow, because
290 // __getitem__ is overloaded;
291 //
292 // - using pybind11::make_iterator on either Catalog's own
293 // iterator type or Catalog.getInternal()'s iterator (a
294 // std::vector iterator) opens us up to undefined
295 // behavior if a modification to the container those
296 // iterators during iteration.
297 //
298 // Our custom iterator holds a Catalog and an integer
299 // index, allowing it to do a bounds check at ever access,
300 // but unlike its Python equivalent there's no overloading
301 // in play (and even if it was, C++ overloading is resolved
302 // at compile-time).
303 //
304 // This custom iterator also yields `shared_ptr<Record>`.
305 // That should make the return value policy passed to
306 // `py::make_iterator` irrelevant; we don't need to keep
307 // the catalog alive in order to keep a record alive,
308 // because the `shared_ptr` manages the record's lifetime.
309 // But we still need keep_alive on the `__iter__` method
310 // itself to keep the raw catalog pointer alive as long as
311 // the iterator is alive.
312 return py::make_iterator(
314 PyCatalogIndexIterator<Record>(&self, self.size())
315 );
316 }, py::keep_alive<0, 1>());
317 cls.def("isContiguous", &Catalog::isContiguous);
318 cls.def("writeFits",
319 (void (Catalog::*)(std::string const &, std::string const &, int) const) &
320 Catalog::writeFits,
321 "filename"_a, "mode"_a = "w", "flags"_a = 0);
322 cls.def("writeFits",
323 (void (Catalog::*)(fits::MemFileManager &, std::string const &, int) const) &
324 Catalog::writeFits,
325 "manager"_a, "mode"_a = "w", "flags"_a = 0);
326 cls.def("reserve", &Catalog::reserve);
327 cls.def("subset",
328 (Catalog(Catalog::*)(ndarray::Array<bool const, 1> const &) const) & Catalog::subset);
329 cls.def("subset",
331 Catalog::subset);
332
333 declareCatalogOverloads<std::int32_t>(cls);
334 declareCatalogOverloads<std::int64_t>(cls);
335 declareCatalogOverloads<float>(cls);
336 declareCatalogOverloads<double>(cls);
337 declareCatalogOverloads<lsst::geom::Angle>(cls);
338
339 cls.def("_getitem_",
340 [](Catalog const &self, Key<Flag> const &key) -> ndarray::Array<bool const, 1, 0> {
341 return _getArrayFromCatalog(self, key);
342 });
343
344 });
345}
346
347} // namespace python
348} // namespace table
349} // namespace afw
350} // namespace lsst
351
352#endif // !LSST_AFW_TABLE_PYTHON_CATALOG_H_INCLUDED
table::Key< std::string > name
Definition: Amplifier.cc:116
int const step
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
SchemaMapper * mapper
Definition: SchemaMapper.cc:71
table::Key< int > b
table::Key< int > a
T c_str(T... args)
Lifetime-management for memory that goes into FITS memory files.
Definition: fits.h:125
def insert(self, key, value)
Definition: _base.py:147
A custom container class for records, based on std::vector.
Definition: Catalog.h:98
std::shared_ptr< RecordT > const get(size_type i) const
Return a pointer to the record at index i.
Definition: Catalog.h:464
A class used as a handle to a particular field in a table.
Definition: Key.h:53
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:21
PyCatalogIndexIterator(CatalogT< Record > const *catalog, std::size_t index)
Definition: catalog.h:113
std::shared_ptr< Record > operator*() const
Definition: catalog.h:115
bool operator!=(PyCatalogIndexIterator const &other) const
Definition: catalog.h:133
PyCatalogIndexIterator & operator++()
Definition: catalog.h:124
bool operator==(PyCatalogIndexIterator const &other) const
Definition: catalog.h:129
Reports attempts to exceed implementation-defined length limits for some classes.
Definition: Runtime.h:76
void _setFlagColumnToArray(CatalogT< Record > &catalog, Key< Flag > const &key, ndarray::Array< bool const, 1 > const &array)
Definition: catalog.h:72
ndarray::Array< typename Field< T >::Value const, 1, 1 > _getArrayFromCatalog(CatalogT< Record > const &catalog, Key< T > const &key)
Extract a column from a potentially non-contiguous Catalog.
Definition: catalog.h:42
void _setFlagColumnToScalar(CatalogT< Record > &catalog, Key< Flag > const &key, bool value)
Definition: catalog.h:92
pybind11::class_< CatalogT< Record >, std::shared_ptr< CatalogT< Record > > > PyCatalog
Definition: catalog.h:38
PyCatalog< Record > declareCatalog(utils::python::WrapperCollection &wrappers, std::string const &name, bool isBase=false)
Wrap an instantiation of lsst::afw::table::CatalogT<Record>.
Definition: catalog.h:213
void declareCatalogOverloads(PyCatalog< Record > &cls)
Declare field-type-specific overloaded catalog member functions for one field type.
Definition: catalog.h:151
T Value
the type returned by BaseRecord::get
Definition: FieldBase.h:42