Loading [MathJax]/extensions/tex2jax.js
LSST Applications g0fba68d861+05816baf74,g1ec0fe41b4+f536777771,g1fd858c14a+a9301854fb,g35bb328faa+fcb1d3bbc8,g4af146b050+a5c07d5b1d,g4d2262a081+6e5fcc2a4e,g53246c7159+fcb1d3bbc8,g56a49b3a55+9c12191793,g5a012ec0e7+3632fc3ff3,g60b5630c4e+ded28b650d,g67b6fd64d1+ed4b5058f4,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g8352419a5c+fcb1d3bbc8,g87b7deb4dc+7b42cf88bf,g8852436030+e5453db6e6,g89139ef638+ed4b5058f4,g8e3bb8577d+d38d73bdbd,g9125e01d80+fcb1d3bbc8,g94187f82dc+ded28b650d,g989de1cb63+ed4b5058f4,g9d31334357+ded28b650d,g9f33ca652e+50a8019d8c,gabe3b4be73+1e0a283bba,gabf8522325+fa80ff7197,gb1101e3267+d9fb1f8026,gb58c049af0+f03b321e39,gb665e3612d+2a0c9e9e84,gb89ab40317+ed4b5058f4,gcf25f946ba+e5453db6e6,gd6cbbdb0b4+bb83cc51f8,gdd1046aedd+ded28b650d,gde0f65d7ad+941d412827,ge278dab8ac+d65b3c2b70,ge410e46f29+ed4b5058f4,gf23fb2af72+b7cae620c0,gf5e32f922b+fcb1d3bbc8,gf67bdafdda+ed4b5058f4,w.2025.16
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
BoundedField.cc
Go to the documentation of this file.
1// -*- LSST-C++ -*-
2
3/*
4 * LSST Data Management System
5 * Copyright 2008-2014 LSST Corporation.
6 *
7 * This product includes software developed by the
8 * LSST Project (http://www.lsst.org/).
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 LSST License Statement and
21 * the GNU General Public License along with this program. If not,
22 * see <http://www.lsstcorp.org/LegalNotices/>.
23 */
24
25#include <numeric>
26
27#include "lsst/pex/exceptions.h"
31
32namespace lsst {
33namespace afw {
34
35template std::shared_ptr<math::BoundedField> table::io::PersistableFacade<math::BoundedField>::dynamicCast(
36 std::shared_ptr<table::io::Persistable> const &);
37
38namespace math {
39
40ndarray::Array<double, 1, 1> BoundedField::evaluate(ndarray::Array<double const, 1> const &x,
41 ndarray::Array<double const, 1> const &y) const {
42 ndarray::Array<double, 1, 1> out = ndarray::allocate(x.getSize<0>());
43 for (int i = 0, n = x.getSize<0>(); i < n; ++i) {
44 out[i] = evaluate(x[i], y[i]);
45 }
46 return out;
47}
48
49double BoundedField::integrate() const { throw LSST_EXCEPT(pex::exceptions::LogicError, "Not Implemented"); }
50
51double BoundedField::mean() const { throw LSST_EXCEPT(pex::exceptions::LogicError, "Not Implemented"); }
52
53namespace {
54
55// We use these operator-based functors to implement the various image-modifying routines
56// in BoundedField. I don't think this is necessarily the best way to add interoperability
57// with images, but it seems like a reasonable point on the simplicity vs. featurefulness
58// scale, and I don't have time to explore more complex solutions (expression templates!).
59// Of course, in C++11, we could replace all these with lambdas.
60
61struct Assign {
62 template <typename T, typename U>
63 void operator()(T &out, U a) const {
64 out = a;
65 }
66};
67
68struct ScaledAdd {
69 explicit ScaledAdd(double s) : scaleBy(s) {}
70
71 template <typename T, typename U>
72 void operator()(T &out, U a) const {
73 out += scaleBy * a;
74 }
75
76 double scaleBy;
77};
78
79struct Multiply {
80 template <typename T, typename U>
81 void operator()(T &out, U a) const {
82 out *= a;
83 }
84};
85
86struct Divide {
87 template <typename T, typename U>
88 void operator()(T &out, U a) const {
89 out /= a;
90 }
91};
92
93// Helper class to do bilinear interpolation with no dynamic memory
94// allocation. This means we evaluate the function multiple times at some
95// points (when we move to a new interval in y, to be specific). Could
96// probably be optimized for the cases where either step is 1, but that seems
97// premature.
98class Interpolator {
99public:
100 // Description of a cell to interpolate in one dimension.
101 struct Bounds {
102 int const step; // step between min and max, except in the special last row/column
103 int min; // lower-bound of cell (coordinate of known value and one before first point to fill in)
104 int max; // upper-bound of cell (coordinate of known value)
105 int end; // upper-bound of cell (one after last point to fill in)
106
107 // Construct from step only.
108 //
109 // Other variables are initialized (and re-initialized) by calls to reset().
110 explicit Bounds(int step_) : step(step_), min(0), max(0), end(0) {}
111
112 // Reset all points (aside from the step) to the first cell in this dimension.
113 void reset(int min_) {
114 min = min_;
115 max = min_ + step;
116 end = min_ + step;
117 }
118 };
119
120 // Construct an object to interpolate the given BoundedField on an evenly-spaced grid within a region.
121 Interpolator(BoundedField const *field, lsst::geom::Box2I const *region, int xStep, int yStep)
122 : _field(field),
123 _region(region),
124 _x(xStep),
125 _y(yStep),
126 _z00(std::numeric_limits<double>::quiet_NaN()),
127 _z01(std::numeric_limits<double>::quiet_NaN()),
128 _z10(std::numeric_limits<double>::quiet_NaN()),
129 _z11(std::numeric_limits<double>::quiet_NaN()) {}
130
131 // Actually do the interpolation.
132 //
133 // This method iterates over rows of cells in y, calling _runRow()
134 // to iterate over cells in x.
135 template <typename T, typename F>
136 void run(image::Image<T> &img, F functor) {
137 _y.reset(_region->getBeginY());
138 while (_y.end < _region->getEndY()) {
139 _runRow(img, functor);
140 _y.min = _y.max;
141 _y.max += _y.step;
142 _y.end = _y.max;
143 }
144 { // special-case last iteration in y
145 _y.max = _region->getMaxY();
146 _y.end = _region->getEndY();
147 _runRow(img, functor);
148 }
149 }
150
151private:
152 // Process a row of cells, calling _runCell() on each one.
153 template <typename T, typename F>
154 void _runRow(image::Image<T> &img, F functor) {
155 _x.reset(_region->getBeginX());
156 _z00 = _field->evaluate(_x.min, _y.min);
157 _z01 = _field->evaluate(_x.min, _y.max);
158 while (_x.max < _region->getEndX()) {
159 _z10 = _field->evaluate(_x.max, _y.min);
160 _z11 = _field->evaluate(_x.max, _y.max);
161 _runCell(img, functor);
162 _x.min = _x.max;
163 _x.max += _x.step;
164 _x.end = _x.max;
165 _z00 = _z10;
166 _z01 = _z11;
167 }
168 { // special-case last iteration in x
169 _x.max = _region->getMaxX();
170 _x.end = _region->getEndX();
171 _z10 = _field->evaluate(_x.max, _y.min);
172 _z11 = _field->evaluate(_x.max, _y.max);
173 _runCell(img, functor);
174 }
175 }
176
177 // Interpolate all points in a cell, which is defined as a rectangle for which
178 // the BoundedField has been evaluated at all four corners. The main complication
179 // comes from the need to special-case the unusually-sized final cells and final
180 // rows/columns within cells in each dimension.
181 template <typename T, typename F>
182 void _runCell(image::Image<T> const &img, F functor) const {
183 int dy = _y.max - _y.min;
184 int dx = _x.max - _x.min;
185 // First iteration of each of the for loops below has been
186 // split into an explicit block. This avoids a few instructions
187 // when no interpolation is necessary, but more importantly it
188 // allows us to handle dx==0 and dy==0 with no divide-by-zero
189 // problems.
190 { // y=_y.min
191 auto rowIter = img.x_at(_x.min - img.getX0(), _y.min - img.getY0());
192 { // x=_x.min
193 functor(*rowIter, _z00);
194 ++rowIter;
195 }
196 for (int x = _x.min + 1; x < _x.end; ++x, ++rowIter) {
197 functor(*rowIter, ((_x.max - x) * _z00 + (x - _x.min) * _z10) / dx);
198 }
199 }
200 for (int y = _y.min + 1; y < _y.end; ++y) {
201 auto rowIter = img.x_at(_x.min - img.getX0(), y - img.getY0());
202 double z0 = ((_y.max - y) * _z00 + (y - _y.min) * _z01) / dy;
203 double z1 = ((_y.max - y) * _z10 + (y - _y.min) * _z11) / dy;
204 { // x=_x.min
205 functor(*rowIter, z0);
206 ++rowIter;
207 }
208 for (int x = _x.min + 1; x < _x.end; ++x, ++rowIter) {
209 functor(*rowIter, ((_x.max - x) * z0 + (x - _x.min) * z1) / dx);
210 }
211 }
212 }
213
214 BoundedField const *_field;
215 lsst::geom::Box2I const *_region;
216 Bounds _x;
217 Bounds _y;
218 double _z00, _z01, _z10, _z11;
219};
220
221template <typename T>
222struct ImageRowGetter {
223 int y0;
224 ndarray::Array<T, 2, 1> array;
225
226 ndarray::ArrayRef<T, 1, 1> get_row(int y) const {
227 return array[y - y0];
228 }
229};
230
231template <typename T>
232struct MaskedImageRow {
233 ndarray::ArrayRef<T, 1, 1> image_row;
234 ndarray::ArrayRef<float, 1, 1> variance_row;
235
236 template <typename U>
237 MaskedImageRow & operator*=(U a) {
238 image_row *= a;
239 variance_row *= a;
240 variance_row *= a;
241 return *this;
242 }
243
244 template <typename U>
245 MaskedImageRow & operator/=(U a) {
246 image_row /= a;
247 variance_row /= a;
248 variance_row /= a;
249 return *this;
250 }
251};
252
253template <typename T>
254struct MaskedImageRowGetter {
255 int y0;
256 ndarray::Array<T, 2, 1> image_array;
257 ndarray::Array<float, 2, 1> variance_array;
258
259 MaskedImageRow<T> get_row(int y) const {
260 return MaskedImageRow<T> { image_array[y - y0], variance_array[y - y0] };
261 }
262};
263
264template <typename T>
265ImageRowGetter<T> make_row_getter(image::Image<T> & im) {
266 return ImageRowGetter<T> { im.getY0(), im.getArray() };
267}
268
269template <typename T>
270MaskedImageRowGetter<T> make_row_getter(image::MaskedImage<T> & mi) {
271 return MaskedImageRowGetter<T> { mi.getY0(), mi.getImage()->getArray(), mi.getVariance()->getArray() };
272}
273
274template <typename ImageT, typename F>
275void applyToImage(BoundedField const &field, ImageT &img, F functor, bool overlapOnly, int xStep,
276 int yStep) {
277 lsst::geom::Box2I region(field.getBBox());
278 if (overlapOnly) {
279 region.clip(img.getBBox(image::PARENT));
280 } else if (region != img.getBBox(image::PARENT)) {
281 throw LSST_EXCEPT(pex::exceptions::RuntimeError,
282 "Image bounding box does not match field bounding box");
283 }
284
285 if (yStep > 1 || xStep > 1) {
286 if constexpr (std::is_scalar_v<typename ImageT::Pixel>) {
287 Interpolator interpolator(&field, &region, xStep, yStep);
288 interpolator.run(img, functor);
289 } else {
290 throw LSST_EXCEPT(
291 pex::exceptions::LogicError,
292 "No interpolation with MaskedImage."
293 );
294 }
295 } else {
296 // We iterate over rows as a significant optimization for AST-backed bounded fields
297 // (it's also slightly faster for other bounded fields, too).
298 auto subImage = img.subset(region);
299 auto size = region.getWidth();
300 ndarray::Array<double, 1> xx = ndarray::allocate(ndarray::makeVector(size));
301 ndarray::Array<double, 1> yy = ndarray::allocate(ndarray::makeVector(size));
302 // y gets incremented each outer loop, x is always xMin->xMax
303 std::iota(xx.begin(), xx.end(), region.getBeginX());
304 auto row_getter = make_row_getter(subImage);
305 for (int y = region.getBeginY(); y < region.getEndY(); ++y) {
306 yy.deep() = y; // don't need indexToPosition, as we're already working in the right box (region).
307 // We need to make 'row' a temporary variable so it can be an
308 // lvalue reference in the functor; it needs to be an lvalue
309 // reference functor for the interpolation code path that passes
310 // it a single value.
311 auto row = row_getter.get_row(y);
312 functor(row, field.evaluate(xx, yy));
313 }
314 }
315}
316
317} // namespace
318
320 return *bf * scale;
321}
322
323template <typename T>
324void BoundedField::fillImage(image::Image<T> &img, bool overlapOnly, int xStep, int yStep) const {
325 applyToImage(*this, img, Assign(), overlapOnly, xStep, yStep);
326}
327
328template <typename T>
329void BoundedField::addToImage(image::Image<T> &img, double scaleBy, bool overlapOnly, int xStep,
330 int yStep) const {
331 applyToImage(*this, img, ScaledAdd(scaleBy), overlapOnly, xStep, yStep);
332}
333
334template <typename T>
335void BoundedField::multiplyImage(image::Image<T> &img, bool overlapOnly, int xStep, int yStep) const {
336 applyToImage(*this, img, Multiply(), overlapOnly, xStep, yStep);
337}
338
339template <typename T>
340void BoundedField::multiplyImage(image::MaskedImage<T> &img, bool overlapOnly) const {
341 applyToImage(*this, img, Multiply(), overlapOnly, 1, 1);
342}
343
344template <typename T>
345void BoundedField::divideImage(image::Image<T> &img, bool overlapOnly, int xStep, int yStep) const {
346 applyToImage(*this, img, Divide(), overlapOnly, xStep, yStep);
347}
348
349template <typename T>
350void BoundedField::divideImage(image::MaskedImage<T> &img, bool overlapOnly) const {
351 applyToImage(*this, img, Divide(), overlapOnly, 1, 1);
352}
353
354#define INSTANTIATE(T) \
355 template void BoundedField::fillImage(image::Image<T> &, bool, int, int) const; \
356 template void BoundedField::addToImage(image::Image<T> &, double, bool, int, int) const; \
357 template void BoundedField::multiplyImage(image::Image<T> &, bool, int, int) const; \
358 template void BoundedField::multiplyImage(image::MaskedImage<T> &, bool) const; \
359 template void BoundedField::divideImage(image::Image<T> &, bool, int, int) const; \
360 template void BoundedField::divideImage(image::MaskedImage<T> &, bool) const
361
362INSTANTIATE(float);
363INSTANTIATE(double);
364
365} // namespace math
366} // namespace afw
367} // namespace lsst
#define INSTANTIATE(FROMSYS, TOSYS)
Definition Detector.cc:509
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition Exception.h:48
int getX0() const
Return the image's column-origin.
Definition ImageBase.h:306
int getY0() const
Return the image's row-origin.
Definition ImageBase.h:314
x_iterator x_at(int x, int y) const
Return an x_iterator to the point (x, y) in the image.
Definition ImageBase.h:407
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
A class to manipulate images, masks, and variance as a single object.
Definition MaskedImage.h:74
int getY0() const
Return the image's row-origin.
VariancePtr getVariance() const
Return a (shared_ptr to) the MaskedImage's variance.
ImagePtr getImage() const
Return a (shared_ptr to) the MaskedImage's image.
virtual double integrate() const
Compute the integral of this function over its bounding-box.
void divideImage(image::Image< T > &image, bool overlapOnly=false, int xStep=1, int yStep=1) const
Divide an image by the field in-place.
virtual double mean() const
Compute the mean of this function over its bounding-box.
void addToImage(image::Image< T > &image, double scaleBy=1.0, bool overlapOnly=false, int xStep=1, int yStep=1) const
Add the field or a constant multiple of it to an image in-place.
virtual double evaluate(lsst::geom::Point2D const &position) const =0
Evaluate the field at the given point.
void multiplyImage(image::Image< T > &image, bool overlapOnly=false, int xStep=1, int yStep=1) const
Multiply an image by the field in-place.
void fillImage(image::Image< T > &image, bool overlapOnly=false, int xStep=1, int yStep=1) const
Assign the field to an image, overwriting values already present.
static std::shared_ptr< T > dynamicCast(std::shared_ptr< Persistable > const &ptr)
Dynamically cast a shared_ptr.
Reports errors in the logical structure of the program.
Definition Runtime.h:46
Eigen::Array< int, 4, 1 > Bounds
T iota(T... args)
Image< LhsPixelT > & operator/=(Image< LhsPixelT > &lhs, Image< RhsPixelT > const &rhs)
Divide lhs by Image rhs (i.e. pixel-by-pixel division) where types are different.
Definition Image.cc:676
Image< LhsPixelT > & operator*=(Image< LhsPixelT > &lhs, Image< RhsPixelT > const &rhs)
Multiply lhs by Image rhs (i.e. pixel-by-pixel multiplication) where types are different.
Definition Image.cc:670
std::shared_ptr< BoundedField > operator*(double const scale, std::shared_ptr< BoundedField const > bf)
run(self, *, coaddExposureHandles, bbox, wcs, dataIds, physical_filter)