LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
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 "ndarray/pybind11.h"
32
33namespace lsst {
34namespace afw {
35namespace table {
36namespace python {
37
38template <typename Record>
39using PyCatalog = pybind11::class_<CatalogT<Record>, std::shared_ptr<CatalogT<Record>>>;
40
42template <typename T, typename Record>
43ndarray::Array<typename Field<T>::Value const, 1, 1> _getArrayFromCatalog(
44 CatalogT<Record> const &catalog,
45 Key<T> const &key
46) {
47 ndarray::Array<typename Field<T>::Value, 1, 1> out = ndarray::allocate(catalog.size());
48 auto outIter = out.begin();
49 auto inIter = catalog.begin();
50 for (; inIter != catalog.end(); ++inIter, ++outIter) {
51 *outIter = inIter->get(key);
52 }
53 return out;
54}
55
58template <typename Record>
59ndarray::Array<double const, 1, 1> _getArrayFromCatalog(
60 CatalogT<Record> const &catalog,
61 Key<Angle> const &key
62) {
63 ndarray::Array<double, 1, 1> out = ndarray::allocate(catalog.size());
64 auto outIter = out.begin();
65 auto inIter = catalog.begin();
66 for (; inIter != catalog.end(); ++inIter, ++outIter) {
67 *outIter = inIter->get(key).asRadians();
68 }
69 return out;
70}
71
74template <typename T, typename Record>
75ndarray::Array<typename Field<T>::Value const, 2, 2> _getArrayFromCatalog(
76 CatalogT<Record> const &catalog,
77 Key<Array<T>> const &key
78) {
79 ndarray::Array<typename Field<T>::Value, 2, 2> out = ndarray::allocate(catalog.size(), key.getSize());
80 auto outIter = out.begin();
81 auto inIter = catalog.begin();
82 for (; inIter != catalog.end(); ++inIter, ++outIter) {
83 *outIter = inIter->get(key);
84 }
85 return out;
86}
87
88template <typename Record>
90 CatalogT<Record> & catalog,
91 Key<Flag> const & key,
92 ndarray::Array<bool const, 1> const & array
93) {
94 if (array.size() != catalog.size()) {
95 throw LSST_EXCEPT(
97 (boost::format("Catalog has %d rows, while array has %d elements.")
98 % catalog.size() % array.size()).str()
99 );
100 }
101 auto catIter = catalog.begin();
102 auto arrayIter = array.begin();
103 for (; catIter != catalog.end(); ++catIter, ++arrayIter) {
104 catIter->set(key, *arrayIter);
105 }
106}
107
108template <typename Record>
110 CatalogT<Record> & catalog,
111 Key<Flag> const & key,
112 bool value
113) {
114 for (auto catIter = catalog.begin(); catIter != catalog.end(); ++catIter) {
115 catIter->set(key, value);
116 }
117}
118
119// Custom safe Python iterator for Catalog. See Catalog.__iter__ wrapper
120// for details.
121template <typename Record>
123public:
124
129
130 PyCatalogIndexIterator(CatalogT<Record> const * catalog, std::size_t index) : _catalog(catalog), _index(index) {}
131
133 if (_index < _catalog->size()) {
134 return _catalog->get(_index);
135 }
136 throw std::out_of_range(
137 "Catalog shrunk during iteration, invalidating this iterator."
138 );
139 }
140
142 ++_index;
143 return *this;
144 }
145
146 bool operator==(PyCatalogIndexIterator const & other) const {
147 return _catalog == other._catalog && _index == other._index;
148 }
149
150 bool operator!=(PyCatalogIndexIterator const & other) const {
151 return !(*this == other);
152 }
153
154private:
155 CatalogT<Record> const * _catalog;
156 std::size_t _index;
157};
158
167template <typename T, typename Record>
169 namespace py = pybind11;
170 using namespace pybind11::literals;
171
173 using Value = typename Field<T>::Value;
174 using ColumnView = typename Record::ColumnView;
175
176 cls.def("isSorted", (bool (Catalog::*)(Key<T> const &) const) & Catalog::isSorted);
177 cls.def("sort", (void (Catalog::*)(Key<T> const &)) & Catalog::sort);
178 cls.def("find", [](Catalog &self, Value const &value, Key<T> const &key) -> std::shared_ptr<Record> {
179 auto iter = self.find(value, key);
180 if (iter == self.end()) {
181 return nullptr;
182 };
183 return iter;
184 });
185 cls.def("upper_bound", [](Catalog &self, Value const &value, Key<T> const &key) -> std::ptrdiff_t {
186 return self.upper_bound(value, key) - self.begin();
187 });
188 cls.def("lower_bound", [](Catalog &self, Value const &value, Key<T> const &key) -> std::ptrdiff_t {
189 return self.lower_bound(value, key) - self.begin();
190 });
191 cls.def("equal_range", [](Catalog &self, Value const &value, Key<T> const &key) {
192 auto p = self.equal_range(value, key);
193 return py::slice(p.first - self.begin(), p.second - self.begin(), 1);
194 });
195 cls.def("between", [](Catalog &self, Value const &lower, Value const &upper, Key<T> const &key) {
196 std::ptrdiff_t a = self.lower_bound(lower, key) - self.begin();
197 std::ptrdiff_t b = self.upper_bound(upper, key) - self.begin();
198 return py::slice(a, b, 1);
199 });
200
201 cls.def("_get_column_from_key",
202 [](Catalog const &self, Key<T> const &key, pybind11::object py_column_view) {
204 if (!column_view && self.isContiguous()) {
205 // If there's no column view cached, but there could be,
206 // make one (and we'll return it so it can be cached by
207 // the calling Python code).
208 column_view = std::make_shared<ColumnView>(self.getColumnView());
209 py_column_view = pybind11::cast(column_view);
210 }
211 if (column_view) {
212 // If there is a column view, use it to return a view.
213 if constexpr (std::is_same_v<T, Angle>) {
214 // numpy doesn't recognize our Angle type, so we return
215 // double radians.
216 return pybind11::make_tuple(column_view->get_radians_array(key), column_view);
217 } else {
218 return pybind11::make_tuple((*column_view)[key].shallow(), column_view);
219 }
220 }
221 // If we can't make a column view, extract a copy.
222 return pybind11::make_tuple(_getArrayFromCatalog(self, key), column_view);
223 });
224}
225
234template <typename T, typename Record>
236 namespace py = pybind11;
237 using namespace pybind11::literals;
238
240 using Value = typename Field<T>::Value;
241 using ColumnView = typename Record::ColumnView;
242
243 cls.def("_get_column_from_key",
244 [](Catalog const &self, Key<Array<T>> const &key, pybind11::object py_column_view) {
246 if (!column_view && self.isContiguous()) {
247 // If there's no column view cached, but there could be,
248 // make one (and we'll return it so it can be cached by
249 // the calling Python code).
250 column_view = std::make_shared<ColumnView>(self.getColumnView());
251 py_column_view = pybind11::cast(column_view);
252 }
253 if (column_view) {
254 // If there is a column view, use it to return view.
255 return pybind11::make_tuple((*column_view)[key].shallow(), column_view);
256 }
257 // If we can't make a column view, extract a copy.
258 return pybind11::make_tuple(_getArrayFromCatalog(self, key), column_view);
259 });
260}
261
275template <typename Record>
277 bool isBase = false) {
278 namespace py = pybind11;
279 using namespace pybind11::literals;
280
282 using Table = typename Record::Table;
283 using ColumnView = typename Record::ColumnView;
284
286 if (isBase) {
287 fullName = "_" + name + "CatalogBase";
288 } else {
289 fullName = name + "Catalog";
290 }
291
292 // We need py::dynamic_attr() in the class definition to support our Python-side caching
293 // of the associated ColumnView.
294 return wrappers.wrapType(
295 PyCatalog<Record>(wrappers.module, fullName.c_str(), py::dynamic_attr()),
296 [](auto &mod, auto &cls) {
297 /* Constructors */
298 cls.def(py::init<Schema const &>(), "schema"_a);
299 cls.def(py::init<std::shared_ptr<Table> const &>(), "table"_a);
300 cls.def(py::init<Catalog const &>(), "other"_a);
301
302 /* Static Methods */
303 cls.def_static("readFits", (Catalog(*)(std::string const &, int, int)) & Catalog::readFits,
304 "filename"_a, "hdu"_a = fits::DEFAULT_HDU, "flags"_a = 0);
305 cls.def_static("readFits", (Catalog(*)(fits::MemFileManager &, int, int)) & Catalog::readFits,
306 "manager"_a, "hdu"_a = fits::DEFAULT_HDU, "flags"_a = 0);
307 // readFits taking Fits objects not wrapped, because Fits objects are not wrapped.
308
309 /* Methods */
310 cls.def("getTable", &Catalog::getTable);
311 cls.def_property_readonly("table", &Catalog::getTable);
312 cls.def("getSchema", &Catalog::getSchema);
313 cls.def_property_readonly("schema", &Catalog::getSchema);
314 cls.def("capacity", &Catalog::capacity);
315 cls.def("__len__", &Catalog::size);
316 cls.def("resize", &Catalog::resize);
317
318 // Use private names for the following so the public Python method
319 // can manage the _column cache
320 cls.def("_getColumnView", &Catalog::getColumnView);
321 cls.def("_addNew", &Catalog::addNew);
322 cls.def("_extend", [](Catalog &self, Catalog const &other, bool deep) {
323 self.insert(self.end(), other.begin(), other.end(), deep);
324 });
325 cls.def("_extend", [](Catalog &self, Catalog const &other, SchemaMapper const &mapper) {
326 self.insert(mapper, self.end(), other.begin(), other.end());
327 });
328 cls.def("_append",
329 [](Catalog &self, std::shared_ptr<Record> const &rec) { self.push_back(rec); });
330 cls.def("_delitem_", [](Catalog &self, std::ptrdiff_t i) {
331 self.erase(self.begin() + cpputils::python::cppIndex(self.size(), i));
332 });
333 cls.def("_delslice_", [](Catalog &self, py::slice const &s) {
334 Py_ssize_t start = 0, stop = 0, step = 0, length = 0;
335 if (PySlice_GetIndicesEx(s.ptr(), self.size(), &start, &stop, &step, &length) != 0) {
336 throw py::error_already_set();
337 }
338 if (step != 1) {
339 throw py::index_error("Slice step must not exactly 1");
340 }
341 self.erase(self.begin() + start, self.begin() + stop);
342 });
343 cls.def("_clear", &Catalog::clear);
344
345 cls.def("set", &Catalog::set);
346 cls.def("_getitem_", [](Catalog &self, int i) {
347 return self.get(cpputils::python::cppIndex(self.size(), i));
348 });
349 cls.def("__iter__", [](Catalog & self) {
350 // We wrap a custom iterator class here for two reasons:
351 //
352 // - letting Python define an automatic iterator that
353 // delegates to __getitem__(int) is super slow, because
354 // __getitem__ is overloaded;
355 //
356 // - using pybind11::make_iterator on either Catalog's own
357 // iterator type or Catalog.getInternal()'s iterator (a
358 // std::vector iterator) opens us up to undefined
359 // behavior if a modification to the container those
360 // iterators during iteration.
361 //
362 // Our custom iterator holds a Catalog and an integer
363 // index, allowing it to do a bounds check at ever access,
364 // but unlike its Python equivalent there's no overloading
365 // in play (and even if it was, C++ overloading is resolved
366 // at compile-time).
367 //
368 // This custom iterator also yields `shared_ptr<Record>`.
369 // That should make the return value policy passed to
370 // `py::make_iterator` irrelevant; we don't need to keep
371 // the catalog alive in order to keep a record alive,
372 // because the `shared_ptr` manages the record's lifetime.
373 // But we still need keep_alive on the `__iter__` method
374 // itself to keep the raw catalog pointer alive as long as
375 // the iterator is alive.
376 return py::make_iterator(
378 PyCatalogIndexIterator<Record>(&self, self.size())
379 );
380 }, py::keep_alive<0, 1>());
381 cls.def("isContiguous", &Catalog::isContiguous);
382 cls.def("writeFits",
383 (void (Catalog::*)(std::string const &, std::string const &, int) const) &
384 Catalog::writeFits,
385 "filename"_a, "mode"_a = "w", "flags"_a = 0);
386 cls.def("writeFits",
387 (void (Catalog::*)(fits::MemFileManager &, std::string const &, int) const) &
388 Catalog::writeFits,
389 "manager"_a, "mode"_a = "w", "flags"_a = 0);
390 cls.def("reserve", &Catalog::reserve);
391 cls.def("subset",
392 (Catalog(Catalog::*)(ndarray::Array<bool const, 1> const &) const) & Catalog::subset);
393 cls.def("subset",
395 Catalog::subset);
396
407
408 cls.def("_get_column_from_key",
409 [](Catalog const &self, Key<Flag> const &key, pybind11::object py_column_view) {
410 // Extra ColumnView arg and return value here are
411 // for consistency with the non-flag overload (up
412 // in declareCatalogOverloads). Casting the array
413 // (from ndarray::Array to numpy.ndarray) before
414 // return is also for consistency with that, though
415 // it's not strictly necessary.
416 return pybind11::make_tuple(
417 _getArrayFromCatalog(self, key),
419 );
420 });
421 cls.def(
422 "_set_flag",
423 [](Catalog &self, Key<Flag> const & key, ndarray::Array<bool const, 1> const & array) {
424 _setFlagColumnToArray(self, key, array);
425 }
426 );
427 cls.def(
428 "_set_flag",
429 [](Catalog &self, Key<Flag> const & key, bool value) {
430 _setFlagColumnToScalar(self, key, value);
431 }
432 );
433
434 });
435}
436
437} // namespace python
438} // namespace table
439} // namespace afw
440} // namespace lsst
441
442#endif // !LSST_AFW_TABLE_PYTHON_CATALOG_H_INCLUDED
int const step
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition Exception.h:48
SchemaMapper * mapper
table::Key< int > b
Lifetime-management for memory that goes into FITS memory files.
Definition fits.h:125
insert(self, key, value)
Definition _base.py:141
Tag types used to declare specialized field types.
Definition misc.h:31
std::shared_ptr< RecordT > const get(size_type i) const
Return a pointer to the record at index i.
Definition Catalog.h:463
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.
PyCatalogIndexIterator(CatalogT< Record > const *catalog, std::size_t index)
Definition catalog.h:130
std::shared_ptr< Record > operator*() const
Definition catalog.h:132
bool operator!=(PyCatalogIndexIterator const &other) const
Definition catalog.h:150
bool operator==(PyCatalogIndexIterator const &other) const
Definition catalog.h:146
A helper class for subdividing pybind11 module across multiple translation units (i....
Definition python.h:242
PyType wrapType(PyType cls, ClassWrapperCallback function, bool setModuleName=true)
Add a type (class or enum) wrapper, deferring method and other attribute definitions until finish() i...
Definition python.h:391
Reports attempts to exceed implementation-defined length limits for some classes.
Definition Runtime.h:76
PyCatalog< Record > declareCatalog(cpputils::python::WrapperCollection &wrappers, std::string const &name, bool isBase=false)
Wrap an instantiation of lsst::afw::table::CatalogT<Record>.
Definition catalog.h:276
void _setFlagColumnToArray(CatalogT< Record > &catalog, Key< Flag > const &key, ndarray::Array< bool const, 1 > const &array)
Definition catalog.h:89
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:43
void declareCatalogArrayOverloads(PyCatalog< Record > &cls)
Declare field-type-specific overloaded catalog member functions for one array-valued field type.
Definition catalog.h:235
void _setFlagColumnToScalar(CatalogT< Record > &catalog, Key< Flag > const &key, bool value)
Definition catalog.h:109
pybind11::class_< CatalogT< Record >, std::shared_ptr< CatalogT< Record > > > PyCatalog
Definition catalog.h:39
void declareCatalogOverloads(PyCatalog< Record > &cls)
Declare field-type-specific overloaded catalog member functions for one field type.
Definition catalog.h:168
std::size_t cppIndex(std::ptrdiff_t size, std::ptrdiff_t i)
Compute a C++ index from a Python index (negative values count from the end) and range-check.
Definition python.h:124
T Value
the type returned by BaseRecord::get
Definition FieldBase.h:42