lsst.afw 23.0.0+60ce4897b0
warpExposure.cc
Go to the documentation of this file.
1// -*- LSST-C++ -*- // fixed format comment for emacs
2
3/*
4 * LSST Data Management System
5 * Copyright 2008, 2009, 2010 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 * Support for warping an %image to a new Wcs.
26 */
27
28#include <cassert>
29#include <cmath>
30#include <limits>
31#include <memory>
32#include <sstream>
33#include <string>
34#include <vector>
35#include <utility>
36#include <regex>
37
38#include "astshim.h"
39
40#include "lsst/log/Log.h"
41#include "lsst/pex/exceptions.h"
42#include "lsst/geom.h"
44#include "lsst/afw/geom.h"
51#include "lsst/afw/table/io/Persistable.cc" // Needed for PersistableFacade::dynamicCast
52
54
55using std::swap;
56
57namespace lsst {
58namespace afw {
59namespace math {
60
61//
62// A helper function for the warping kernels which provides error-checking:
63// the warping kernels are designed to work in two cases
64// 0 < x < 1 and ctrX=(size-1)/2
65// -1 < x < 0 and ctrX=(size+1)/2
66// (and analogously for y). Note that to get the second case, Kernel::setCtr(1, y) must be
67// called before calling Kernel::setKernelParameter(). [see afw::math::offsetImage() for
68// an example]
69//
70// FIXME eventually the 3 warping kernels will inherit from a common base class WarpingKernel
71// and this routine can be eliminated by putting the code in WarpingKernel::setKernelParameter()
72//
73static inline void checkWarpingKernelParameter(const SeparableKernel *p, unsigned int ind, double value) {
74 if (ind > 1) {
76 "bad ind argument in WarpingKernel::setKernelParameter()");
77 }
78 int ctr = p->getCtr()[ind];
79 int size = p->getDimensions()[ind];
80
81 if (ctr == (size - 1) / 2) {
82 if (value < -1e-6 || value > 1 + 1e-6) {
84 "bad coordinate in WarpingKernel::setKernelParameter()");
85 }
86 } else if (ctr == (size + 1) / 2) {
87 if (value < -1 - 1e-6 || value > 1e-6) {
89 "bad coordinate in WarpingKernel::setKernelParameter()");
90 }
91 } else {
93 "bad ctr value in WarpingKernel::setKernelParameter()");
94 }
95}
96
99}
100
101int LanczosWarpingKernel::getOrder() const { return this->getWidth() / 2; }
102
103void LanczosWarpingKernel::setKernelParameter(unsigned int ind, double value) const {
104 checkWarpingKernelParameter(this, ind, value);
106}
107
110}
111
113 //
114 // this->_params[0] = value of x where we want to interpolate the function
115 // x = integer value of x where we evaluate the function in the interpolation
116 //
117 // The following weird-looking expression has no if/else statements, is roundoff-tolerant,
118 // and works in the following two cases:
119 // 0 < this->_params[0] < 1, x \in {0,1}
120 // -1 < this->_params[0] < 0, x \in {-1,0}
121 //
122 // The checks in BilinearWarpingKernel::setKernelParameter() ensure that one of these
123 // conditions is satisfied
124 //
125 return 0.5 + (1.0 - (2.0 * fabs(this->_params[0]))) * (0.5 - fabs(x));
126}
127
128void BilinearWarpingKernel::setKernelParameter(unsigned int ind, double value) const {
129 checkWarpingKernelParameter(this, ind, value);
131}
132
135 os << "_BilinearFunction1: ";
136 os << Function1<Kernel::Pixel>::toString(prefix);
137 return os.str();
138}
139
141 return std::make_shared<NearestWarpingKernel>();
142}
143
145 // this expression is faster than using conditionals, but offers no sanity checking
146 return static_cast<double>((fabs(this->_params[0]) < 0.5) == (fabs(x) < 0.5));
147}
148
149void NearestWarpingKernel::setKernelParameter(unsigned int ind, double value) const {
150 checkWarpingKernelParameter(this, ind, value);
152}
153
156 os << "_NearestFunction1: ";
157 os << Function1<Kernel::Pixel>::toString(prefix);
158 return os.str();
159}
160
161namespace {
162
163struct LanczosKernelPersistenceHelper {
164 table::Schema schema;
165 table::Key<int> order;
166
167 static LanczosKernelPersistenceHelper const &get() {
168 static LanczosKernelPersistenceHelper const instance;
169 return instance;
170 }
171
172 LanczosKernelPersistenceHelper(LanczosKernelPersistenceHelper const &) = delete;
173 LanczosKernelPersistenceHelper(LanczosKernelPersistenceHelper &&) = delete;
174 LanczosKernelPersistenceHelper &operator=(LanczosKernelPersistenceHelper const &) = delete;
175 LanczosKernelPersistenceHelper &operator=(LanczosKernelPersistenceHelper &&) = delete;
176
177private:
178 LanczosKernelPersistenceHelper()
179 : schema(), order(schema.addField<int>("order", "order of Lanczos function")) {}
180};
181
182class : public table::io::PersistableFactory {
183 std::shared_ptr<table::io::Persistable> read(table::io::InputArchive const &archive,
184 table::io::CatalogVector const &catalogs) const override {
185 auto const &keys = LanczosKernelPersistenceHelper::get();
186 LSST_ARCHIVE_ASSERT(catalogs.size() == 1u);
187 LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
188 afw::table::BaseRecord const &record = catalogs.front().front();
189 LSST_ARCHIVE_ASSERT(record.getSchema() == keys.schema);
190 return std::make_shared<LanczosWarpingKernel>(record.get(keys.order));
191 }
192
194} lanczosFactory("LanczosWarpingKernel");
195
196template <class T>
197class DefaultPersistableFactory : public table::io::PersistableFactory {
198 std::shared_ptr<table::io::Persistable> read(table::io::InputArchive const &archive,
199 table::io::CatalogVector const &catalogs) const override {
200 LSST_ARCHIVE_ASSERT(catalogs.empty());
201 return std::make_shared<T>();
202 }
203
205};
206
207DefaultPersistableFactory<BilinearWarpingKernel> bilinearFactory("BilinearWarpingKernel");
208DefaultPersistableFactory<NearestWarpingKernel> nearestFactory("NearestWarpingKernel");
209
210} // namespace
211
213 auto const &keys = LanczosKernelPersistenceHelper::get();
214 table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
216 record->set(keys.order, getOrder());
217 handle.saveCatalog(catalog);
218}
219
221
223
225 using KernelPtr = std::shared_ptr<SeparableKernel>;
226 std::smatch matches;
227 static const std::regex LanczosRE("lanczos(\\d+)");
228 if (name == "bilinear") {
229 return KernelPtr(new BilinearWarpingKernel());
230 } else if (std::regex_match(name, matches, LanczosRE)) {
231 std::string orderStr(matches[1].first, matches[1].second);
232 int order = std::stoi(orderStr);
233 return KernelPtr(new LanczosWarpingKernel(order));
234 } else if (name == "nearest") {
235 return KernelPtr(new NearestWarpingKernel());
236 } else {
237 throw LSST_EXCEPT(pexExcept::InvalidParameterError, "unknown warping kernel name: \"" + name + "\"");
238 }
239}
240
242 if (_warpingKernelPtr->getCacheSize() != _cacheSize) {
243 _warpingKernelPtr->computeCache(_cacheSize);
244 }
245 return _warpingKernelPtr;
246};
247
248void WarpingControl::setWarpingKernelName(std::string const &warpingKernelName) {
249 std::shared_ptr<SeparableKernel> warpingKernelPtr(makeWarpingKernel(warpingKernelName));
250 setWarpingKernel(*warpingKernelPtr);
251}
252
254 if (_maskWarpingKernelPtr) {
255 _testWarpingKernels(warpingKernel, *_maskWarpingKernelPtr);
256 }
257 std::shared_ptr<SeparableKernel> warpingKernelPtr(
258 std::static_pointer_cast<SeparableKernel>(warpingKernel.clone()));
259 _warpingKernelPtr = warpingKernelPtr;
260}
261
263 if (_maskWarpingKernelPtr) { // lazily update kernel cache
264 if (_maskWarpingKernelPtr->getCacheSize() != _cacheSize) {
265 _maskWarpingKernelPtr->computeCache(_cacheSize);
266 }
267 }
268 return _maskWarpingKernelPtr;
269}
270
271void WarpingControl::setMaskWarpingKernelName(std::string const &maskWarpingKernelName) {
272 if (!maskWarpingKernelName.empty()) {
273 std::shared_ptr<SeparableKernel> maskWarpingKernelPtr(makeWarpingKernel(maskWarpingKernelName));
274 setMaskWarpingKernel(*maskWarpingKernelPtr);
275 } else {
276 _maskWarpingKernelPtr.reset();
277 }
278}
279
281 _testWarpingKernels(*_warpingKernelPtr, maskWarpingKernel);
282 _maskWarpingKernelPtr = std::static_pointer_cast<SeparableKernel>(maskWarpingKernel.clone());
283}
284
285void WarpingControl::_testWarpingKernels(SeparableKernel const &warpingKernel,
286 SeparableKernel const &maskWarpingKernel) const {
287 lsst::geom::Box2I kernelBBox =
289 warpingKernel.getDimensions());
290 lsst::geom::Box2I maskKernelBBox =
292 maskWarpingKernel.getDimensions());
293 if (!kernelBBox.contains(maskKernelBBox)) {
295 "warping kernel is smaller than mask warping kernel");
296 }
297}
298
299namespace {
300
301struct WarpingControlPersistenceHelper {
302 table::Schema schema;
303 table::Key<int> warpingKernelIndex;
304 table::Key<table::Flag> hasMaskKernel;
305 table::Key<int> maskKernelIndex; // undefined if !hasMaskKernel
306 table::Key<int> cacheSize;
307 table::Key<int> interpLength;
308 table::Key<image::MaskPixel> growFullMask;
309
310 static WarpingControlPersistenceHelper const &get() {
311 static WarpingControlPersistenceHelper const instance;
312 return instance;
313 }
314
315 WarpingControlPersistenceHelper(WarpingControlPersistenceHelper const &) = delete;
316 WarpingControlPersistenceHelper(WarpingControlPersistenceHelper &&) = delete;
317 WarpingControlPersistenceHelper &operator=(WarpingControlPersistenceHelper const &) = delete;
318 WarpingControlPersistenceHelper &operator=(WarpingControlPersistenceHelper &&) = delete;
319
320private:
321 WarpingControlPersistenceHelper()
322 : schema(),
324 schema.addField<int>("warpingKernelIndex", "archive ID of nested warping kernel")),
325 hasMaskKernel(schema.addField<table::Flag>("hasMaskKernel", "whether a mask kernel is stored")),
326 maskKernelIndex(schema.addField<int>("maskKernelIndex",
327 "archive ID of nested mask kernel. "
328 "Valid only if hasMaskKernel")),
329 cacheSize(schema.addField<int>("cacheSize", "Cache size for warping kernel(s)")),
330 interpLength(schema.addField<int>("interpLength",
331 "Distance over which WCS can be linearly interpolated")),
333 "growFullMask", "bits to grow to full width of image/variance kernel")) {}
334};
335
336std::string _getWarpingControlPersistenceName() { return "WarpingControl"; }
337
338class : public table::io::PersistableFactory {
339 std::shared_ptr<table::io::Persistable> read(table::io::InputArchive const &archive,
340 table::io::CatalogVector const &catalogs) const override {
341 auto const &keys = WarpingControlPersistenceHelper::get();
342 LSST_ARCHIVE_ASSERT(catalogs.size() == 1u);
343 LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
344 afw::table::BaseRecord const &record = catalogs.front().front();
345 LSST_ARCHIVE_ASSERT(record.getSchema() == keys.schema);
346
347 // Warping kernels are dummy values, set true kernels later
348 auto control = std::make_shared<WarpingControl>("bilinear", "", record.get(keys.cacheSize),
349 record.get(keys.interpLength),
350 record.get(keys.growFullMask));
351 // archive.get returns a shared_ptr, so this code depends on the
352 // undocumented fact that setWarpingKernel and setMaskWarpingKernel
353 // make defensive copies.
354 control->setWarpingKernel(*archive.get<SeparableKernel>(record.get(keys.warpingKernelIndex)));
355 if (record.get(keys.hasMaskKernel)) {
356 control->setMaskWarpingKernel(*archive.get<SeparableKernel>(record.get(keys.maskKernelIndex)));
357 }
358 return control;
359 }
360
362} controlFactory(_getWarpingControlPersistenceName());
363
364} // namespace
365
366std::string WarpingControl::getPersistenceName() const { return _getWarpingControlPersistenceName(); }
367
368std::string WarpingControl::getPythonModule() const { return "lsst.afw.math"; }
369
370bool WarpingControl::isPersistable() const noexcept {
371 return _warpingKernelPtr->isPersistable() &&
372 (!hasMaskWarpingKernel() || _maskWarpingKernelPtr->isPersistable());
373}
374
376 auto const &keys = WarpingControlPersistenceHelper::get();
377 table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
379
380 record->set(keys.warpingKernelIndex, handle.put(_warpingKernelPtr));
381 record->set(keys.hasMaskKernel, hasMaskWarpingKernel());
382 if (hasMaskWarpingKernel()) {
383 record->set(keys.maskKernelIndex, handle.put(_maskWarpingKernelPtr));
384 }
385 record->set(keys.cacheSize, _cacheSize);
386 record->set(keys.interpLength, _interpLength);
387 record->set(keys.growFullMask, _growFullMask);
388
389 handle.saveCatalog(catalog);
390}
391
392template <typename DestExposureT, typename SrcExposureT>
393int warpExposure(DestExposureT &destExposure, SrcExposureT const &srcExposure, WarpingControl const &control,
394 typename DestExposureT::MaskedImageT::SinglePixel padValue) {
395 if (!destExposure.hasWcs()) {
396 throw LSST_EXCEPT(pexExcept::InvalidParameterError, "destExposure has no Wcs");
397 }
398 if (!srcExposure.hasWcs()) {
399 throw LSST_EXCEPT(pexExcept::InvalidParameterError, "srcExposure has no Wcs");
400 }
401 typename DestExposureT::MaskedImageT mi = destExposure.getMaskedImage();
402 destExposure.setPhotoCalib(srcExposure.getPhotoCalib());
403 destExposure.setFilterLabel(srcExposure.getFilterLabel());
404 destExposure.getInfo()->setVisitInfo(srcExposure.getInfo()->getVisitInfo());
405 return warpImage(mi, *destExposure.getWcs(), srcExposure.getMaskedImage(), *srcExposure.getWcs(), control,
406 padValue);
407}
408
409namespace {
410
411inline lsst::geom::Point2D computeSrcPos(
412 int destCol,
413 int destRow,
414 lsst::geom::Point2D const &destXY0,
415 geom::SkyWcs const &destWcs,
416 geom::SkyWcs const &srcWcs)
417{
418 lsst::geom::Point2D const destPix(image::indexToPosition(destCol + destXY0[0]),
419 image::indexToPosition(destRow + destXY0[1]));
420 return srcWcs.skyToPixel(destWcs.pixelToSky(destPix));
421}
422
423inline double computeRelativeArea(
424 lsst::geom::Point2D const &srcPos,
426 &leftSrcPos,
427 lsst::geom::Point2D const &upSrcPos)
428{
429 lsst::geom::Extent2D dSrcA = srcPos - leftSrcPos;
430 lsst::geom::Extent2D dSrcB = srcPos - upSrcPos;
431
432 return std::abs(dSrcA.getX() * dSrcB.getY() - dSrcA.getY() * dSrcB.getX());
433}
434
435} // namespace
436
437template <typename DestImageT, typename SrcImageT>
438int warpImage(DestImageT &destImage, geom::SkyWcs const &destWcs, SrcImageT const &srcImage,
439 geom::SkyWcs const &srcWcs, WarpingControl const &control,
440 typename DestImageT::SinglePixel padValue) {
441 auto srcToDest = geom::makeWcsPairTransform(srcWcs, destWcs);
442 return warpImage(destImage, srcImage, *srcToDest, control, padValue);
443}
444
445template <typename DestImageT, typename SrcImageT>
446int warpImage(DestImageT &destImage, SrcImageT const &srcImage,
447 geom::TransformPoint2ToPoint2 const &srcToDest, WarpingControl const &control,
448 typename DestImageT::SinglePixel padValue) {
449 if (imagesOverlap(destImage, srcImage)) {
450 throw LSST_EXCEPT(pexExcept::InvalidParameterError, "destImage overlaps srcImage; cannot warp");
451 }
452 if (destImage.getBBox(image::LOCAL).isEmpty()) {
453 return 0;
454 }
455 // if src image is too small then don't try to warp
456 std::shared_ptr<SeparableKernel> warpingKernelPtr = control.getWarpingKernel();
457 try {
458 warpingKernelPtr->shrinkBBox(srcImage.getBBox(image::LOCAL));
460 for (int y = 0, height = destImage.getHeight(); y < height; ++y) {
461 for (typename DestImageT::x_iterator destPtr = destImage.row_begin(y), end = destImage.row_end(y);
462 destPtr != end; ++destPtr) {
463 *destPtr = padValue;
464 }
465 }
466 return 0;
467 }
468 int interpLength = control.getInterpLength();
469
470 std::shared_ptr<LanczosWarpingKernel const> const lanczosKernelPtr =
471 std::dynamic_pointer_cast<LanczosWarpingKernel>(warpingKernelPtr);
472
473 int numGoodPixels = 0;
474
475 // compute a transform from local destination pixels to parent source pixels
476 auto const parentDestToParentSrc = srcToDest.inverted();
477 std::vector<double> const localDestToParentDestVec = {static_cast<double>(destImage.getX0()),
478 static_cast<double>(destImage.getY0())};
479 auto const localDestToParentDest = geom::TransformPoint2ToPoint2(ast::ShiftMap(localDestToParentDestVec));
480 auto const localDestToParentSrc = localDestToParentDest.then(*parentDestToParentSrc);
481
482 // Get the source MaskedImage and a pixel accessor to it.
483 int const srcWidth = srcImage.getWidth();
484 int const srcHeight = srcImage.getHeight();
485 LOGL_DEBUG("TRACE2.afw.math.warp", "source image width=%d; height=%d", srcWidth, srcHeight);
486
487 int const destWidth = destImage.getWidth();
488 int const destHeight = destImage.getHeight();
489 LOGL_DEBUG("TRACE2.afw.math.warp", "remap image width=%d; height=%d", destWidth, destHeight);
490
491 // Set each pixel of destExposure's MaskedImage
492 LOGL_DEBUG("TRACE3.afw.math.warp", "Remapping masked image");
493
494 int const maxCol = destWidth - 1;
495 int const maxRow = destHeight - 1;
496
497 detail::WarpAtOnePoint<DestImageT, SrcImageT> warpAtOnePoint(srcImage, control, padValue);
498
499 if (interpLength > 0) {
500 // Use interpolation. Note that 1 produces the same result as no interpolation
501 // but uses this code branch, thus providing an easy way to compare the two branches.
502
503 // Estimate for number of horizontal interpolation band edges, to reserve memory in vectors
504 int const numColEdges = 2 + ((destWidth - 1) / interpLength);
505
506 // A list of edge column indices for interpolation bands;
507 // starts at -1, increments by interpLen (except the final interval), and ends at destWidth-1
508 std::vector<int> edgeColList;
509 edgeColList.reserve(numColEdges);
510
511 // A list of 1/column width for horizontal interpolation bands; the first value is garbage.
512 // The inverse is used for speed because the values are always multiplied.
513 std::vector<double> invWidthList;
514 invWidthList.reserve(numColEdges);
515
516 // Compute edgeColList and invWidthList
517 edgeColList.push_back(-1);
518 invWidthList.push_back(0.0);
519 for (int prevEndCol = -1; prevEndCol < maxCol; prevEndCol += interpLength) {
520 int endCol = prevEndCol + interpLength;
521 if (endCol > maxCol) {
522 endCol = maxCol;
523 }
524 edgeColList.push_back(endCol);
525 assert(endCol - prevEndCol > 0);
526 invWidthList.push_back(1.0 / static_cast<double>(endCol - prevEndCol));
527 }
528 assert(edgeColList.back() == maxCol);
529
530 // A list of delta source positions along the edge columns of the horizontal interpolation bands
531 std::vector<lsst::geom::Extent2D> yDeltaSrcPosList(edgeColList.size());
532
533 // A cache of pixel positions on the source corresponding to the previous or current row
534 // of the destination image.
535 // The first value is for column -1 because the previous source position is used to compute relative
536 // area To simplify the indexing, use an iterator that starts at begin+1, thus: srcPosView =
537 // srcPosList.begin() + 1 srcPosView[col-1] and lower indices are for this row srcPosView[col] and
538 // higher indices are for the previous row
539 std::vector<lsst::geom::Point2D> srcPosList(1 + destWidth);
540 std::vector<lsst::geom::Point2D>::iterator const srcPosView = srcPosList.begin() + 1;
541
543 endColPosList.reserve(numColEdges);
544
545 // Initialize srcPosList for row -1
546 for (int endCol : edgeColList) {
547 endColPosList.emplace_back(lsst::geom::Point2D(endCol, -1));
548 }
549 auto rightSrcPosList = localDestToParentSrc->applyForward(endColPosList);
550 srcPosView[-1] = rightSrcPosList[0];
551 for (int colBand = 1, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
552 int const prevEndCol = edgeColList[colBand - 1];
553 int const endCol = edgeColList[colBand];
554 lsst::geom::Point2D leftSrcPos = srcPosView[prevEndCol];
555
556 lsst::geom::Extent2D xDeltaSrcPos =
557 (rightSrcPosList[colBand] - leftSrcPos) * invWidthList[colBand];
558
559 for (int col = prevEndCol + 1; col <= endCol; ++col) {
560 srcPosView[col] = srcPosView[col - 1] + xDeltaSrcPos;
561 }
562 }
563
564 int endRow = -1;
565 while (endRow < maxRow) {
566 // Next horizontal interpolation band
567
568 int prevEndRow = endRow;
569 endRow = prevEndRow + interpLength;
570 if (endRow > maxRow) {
571 endRow = maxRow;
572 }
573 assert(endRow - prevEndRow > 0);
574 double interpInvHeight = 1.0 / static_cast<double>(endRow - prevEndRow);
575
576 // Set yDeltaSrcPosList for this horizontal interpolation band
578 destRowPosList.reserve(edgeColList.size());
579 for (int endCol : edgeColList) {
580 destRowPosList.emplace_back(lsst::geom::Point2D(endCol, endRow));
581 }
582 auto bottomSrcPosList = localDestToParentSrc->applyForward(destRowPosList);
583 for (int colBand = 0, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
584 int endCol = edgeColList[colBand];
585 yDeltaSrcPosList[colBand] =
586 (bottomSrcPosList[colBand] - srcPosView[endCol]) * interpInvHeight;
587 }
588
589 for (int row = prevEndRow + 1; row <= endRow; ++row) {
590 typename DestImageT::x_iterator destXIter = destImage.row_begin(row);
591 srcPosView[-1] += yDeltaSrcPosList[0];
592 for (int colBand = 1, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
593 // Next vertical interpolation band
594
595 int const prevEndCol = edgeColList[colBand - 1];
596 int const endCol = edgeColList[colBand];
597
598 // Compute xDeltaSrcPos; remember that srcPosView contains
599 // positions for this row in prevEndCol and smaller indices,
600 // and positions for the previous row for larger indices (including endCol)
601 lsst::geom::Point2D leftSrcPos = srcPosView[prevEndCol];
602 lsst::geom::Point2D rightSrcPos = srcPosView[endCol] + yDeltaSrcPosList[colBand];
603 lsst::geom::Extent2D xDeltaSrcPos = (rightSrcPos - leftSrcPos) * invWidthList[colBand];
604
605 for (int col = prevEndCol + 1; col <= endCol; ++col, ++destXIter) {
606 lsst::geom::Point2D leftSrcPos = srcPosView[col - 1];
607 lsst::geom::Point2D srcPos = leftSrcPos + xDeltaSrcPos;
608 double relativeArea = computeRelativeArea(srcPos, leftSrcPos, srcPosView[col]);
609
610 srcPosView[col] = srcPos;
611
612 if (warpAtOnePoint(
613 destXIter, srcPos, relativeArea,
615 ++numGoodPixels;
616 }
617 } // for col
618 } // for col band
619 } // for row
620 } // while next row band
621
622 } else {
623 // No interpolation
624
625 // prevSrcPosList = source positions from the previous row; these are used to compute pixel area;
626 // to begin, compute sources positions corresponding to destination row = -1
628 destPosList.reserve(1 + destWidth);
629 for (int col = -1; col < destWidth; ++col) {
630 destPosList.emplace_back(lsst::geom::Point2D(col, -1));
631 }
632 auto prevSrcPosList = localDestToParentSrc->applyForward(destPosList);
633
634 for (int row = 0; row < destHeight; ++row) {
635 destPosList.clear();
636 for (int col = -1; col < destWidth; ++col) {
637 destPosList.emplace_back(lsst::geom::Point2D(col, row));
638 }
639 auto srcPosList = localDestToParentSrc->applyForward(destPosList);
640
641 typename DestImageT::x_iterator destXIter = destImage.row_begin(row);
642 for (int col = 0; col < destWidth; ++col, ++destXIter) {
643 // column index = column + 1 because the first entry in srcPosList is for column -1
644 auto srcPos = srcPosList[col + 1];
645 double relativeArea =
646 computeRelativeArea(srcPos, prevSrcPosList[col], prevSrcPosList[col + 1]);
647
648 if (warpAtOnePoint(destXIter, srcPos, relativeArea,
650 ++numGoodPixels;
651 }
652 } // for col
653 // move points from srcPosList to prevSrcPosList (we don't care about what ends up in srcPosList
654 // because it will be reallocated anyway)
655 swap(srcPosList, prevSrcPosList);
656 } // for row
657 } // if interp
658
659 return numGoodPixels;
660}
661
662template <typename DestImageT, typename SrcImageT>
663int warpCenteredImage(DestImageT &destImage, SrcImageT const &srcImage,
664 lsst::geom::LinearTransform const &linearTransform,
665 lsst::geom::Point2D const &centerPosition, WarpingControl const &control,
666 typename DestImageT::SinglePixel padValue) {
667 // force src and dest to be the same size and xy0
668 if ((destImage.getWidth() != srcImage.getWidth()) || (destImage.getHeight() != srcImage.getHeight()) ||
669 (destImage.getXY0() != srcImage.getXY0())) {
670 std::ostringstream errStream;
671 errStream << "src and dest images must have same size and xy0.";
673 }
674
675 // set the xy0 coords to 0,0 to make life easier
676 SrcImageT srcImageCopy(srcImage, true);
677 srcImageCopy.setXY0(0, 0);
678 destImage.setXY0(0, 0);
679 lsst::geom::Extent2D cLocal =
680 lsst::geom::Extent2D(centerPosition) - lsst::geom::Extent2D(srcImage.getXY0());
681
682 // for the affine transform, the centerPosition will not only get sheared, but also
683 // moved slightly. So we'll include a translation to move it back by an amount
684 // centerPosition - translatedCenterPosition
685 lsst::geom::AffineTransform affTran(linearTransform, cLocal - linearTransform(cLocal));
687
688// now warp
689#if 0
690 static float t = 0.0;
691 float t_before = 1.0*clock()/CLOCKS_PER_SEC;
692 int n = warpImage(destImage, srcImageCopy, affTran, control, padValue);
693 float t_after = 1.0*clock()/CLOCKS_PER_SEC;
694 float dt = t_after - t_before;
695 t += dt;
696 std::cout <<srcImage.getWidth()<<"x"<<srcImage.getHeight()<<": "<< dt <<" "<< t <<std::endl;
697#else
698 int n = warpImage(destImage, srcImageCopy, *affineTransform22, control, padValue);
699#endif
700
701 // fix the origin and we're done.
702 destImage.setXY0(srcImage.getXY0());
703
704 return n;
705}
706
707//
708// Explicit instantiations
709//
711// may need to omit default params for EXPOSURE -- original code did that and it worked
712#define EXPOSURE(PIXTYPE) image::Exposure<PIXTYPE, image::MaskPixel, image::VariancePixel>
713#define MASKEDIMAGE(PIXTYPE) image::MaskedImage<PIXTYPE, image::MaskPixel, image::VariancePixel>
714#define IMAGE(PIXTYPE) image::Image<PIXTYPE>
715#define NL /* */
716
717#define INSTANTIATE(DESTIMAGEPIXELT, SRCIMAGEPIXELT) \
718 template int warpCenteredImage( \
719 IMAGE(DESTIMAGEPIXELT) & destImage, IMAGE(SRCIMAGEPIXELT) const &srcImage, \
720 lsst::geom::LinearTransform const &linearTransform, lsst::geom::Point2D const &centerPosition, \
721 WarpingControl const &control, IMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
722 NL template int warpCenteredImage( \
723 MASKEDIMAGE(DESTIMAGEPIXELT) & destImage, MASKEDIMAGE(SRCIMAGEPIXELT) const &srcImage, \
724 lsst::geom::LinearTransform const &linearTransform, lsst::geom::Point2D const &centerPosition, \
725 WarpingControl const &control, MASKEDIMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
726 NL template int warpImage(IMAGE(DESTIMAGEPIXELT) & destImage, IMAGE(SRCIMAGEPIXELT) const &srcImage, \
727 geom::TransformPoint2ToPoint2 const &srcToDest, WarpingControl const &control, \
728 IMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
729 NL template int warpImage(MASKEDIMAGE(DESTIMAGEPIXELT) & destImage, \
730 MASKEDIMAGE(SRCIMAGEPIXELT) const &srcImage, \
731 geom::TransformPoint2ToPoint2 const &srcToDest, WarpingControl const &control, \
732 MASKEDIMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
733 NL template int warpImage(IMAGE(DESTIMAGEPIXELT) & destImage, geom::SkyWcs const &destWcs, \
734 IMAGE(SRCIMAGEPIXELT) const &srcImage, geom::SkyWcs const &srcWcs, \
735 WarpingControl const &control, IMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
736 NL template int warpImage(MASKEDIMAGE(DESTIMAGEPIXELT) & destImage, geom::SkyWcs const &destWcs, \
737 MASKEDIMAGE(SRCIMAGEPIXELT) const &srcImage, geom::SkyWcs const &srcWcs, \
738 WarpingControl const &control, \
739 MASKEDIMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
740 NL template int warpExposure(EXPOSURE(DESTIMAGEPIXELT) & destExposure, \
741 EXPOSURE(SRCIMAGEPIXELT) const &srcExposure, WarpingControl const &control, \
742 EXPOSURE(DESTIMAGEPIXELT)::MaskedImageT::SinglePixel padValue);
743
744INSTANTIATE(double, double)
745INSTANTIATE(double, float)
746INSTANTIATE(double, int)
748INSTANTIATE(float, float)
749INSTANTIATE(float, int)
751INSTANTIATE(int, int)
754} // namespace math
755
756template std::shared_ptr<math::LanczosWarpingKernel> table::io::PersistableFacade<
757 math::LanczosWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
758template std::shared_ptr<math::BilinearWarpingKernel> table::io::PersistableFacade<
759 math::BilinearWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
760template std::shared_ptr<math::NearestWarpingKernel> table::io::PersistableFacade<
761 math::NearestWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
762template std::shared_ptr<math::WarpingControl> table::io::PersistableFacade<
763 math::WarpingControl>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
764
765} // namespace afw
766} // namespace lsst
table::Key< std::string > name
Definition: Amplifier.cc:116
int end
double x
#define INSTANTIATE(FROMSYS, TOSYS)
Definition: Detector.cc:484
#define LSST_EXCEPT(type,...)
#define LOGL_DEBUG(logger, message...)
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:48
Implementation of the Photometric Calibration class.
std::ostream * os
Definition: Schema.cc:557
std::string prefix
Definition: SchemaMapper.cc:72
int y
Definition: SpanSet.cc:48
T back(T... args)
T begin(T... args)
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition: SkyWcs.h:117
lsst::geom::SpherePoint pixelToSky(lsst::geom::Point2D const &pixel) const
Compute sky position(s) from pixel position(s)
Definition: SkyWcs.h:334
lsst::geom::Point2D skyToPixel(lsst::geom::SpherePoint const &sky) const
Compute pixel position(s) from sky position(s)
Definition: SkyWcs.h:349
Transform LSST spatial data, such as lsst::geom::Point2D and lsst::geom::SpherePoint,...
Definition: Transform.h:68
std::shared_ptr< Transform< ToEndpoint, FromEndpoint > > inverted() const
The inverse of this Transform.
Definition: Transform.cc:110
std::string toString(std::string const &="") const override
Return string representation.
Kernel::Pixel operator()(double x) const override
Solve bilinear equation.
BilinearWarpingKernel & operator=(const BilinearWarpingKernel &)=delete
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
std::shared_ptr< Kernel > clone() const override
Return a pointer to a deep copy of this kernel.
void setKernelParameter(unsigned int ind, double value) const override
Set one kernel parameter.
std::vector< double > _params
Definition: Function.h:185
lsst::geom::Extent2I const getDimensions() const
Return the Kernel's dimensions (width, height)
Definition: Kernel.h:212
lsst::geom::Point2I getCtr() const
Return index of kernel's center.
Definition: Kernel.h:234
int getWidth() const
Return the Kernel's width.
Definition: Kernel.h:224
Lanczos warping: accurate but slow and can introduce ringing artifacts.
Definition: warpExposure.h:66
int getOrder() const
get the order of the kernel
void setKernelParameter(unsigned int ind, double value) const override
Set one kernel parameter.
std::shared_ptr< Kernel > clone() const override
Return a pointer to a deep copy of this kernel.
Definition: warpExposure.cc:97
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
Kernel::Pixel operator()(double x) const override
Solve nearest neighbor equation.
std::string toString(std::string const &="") const override
Return string representation.
Nearest neighbor warping: fast; good for undersampled data.
Definition: warpExposure.h:180
std::shared_ptr< Kernel > clone() const override
Return a pointer to a deep copy of this kernel.
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
void setKernelParameter(unsigned int ind, double value) const override
Set one kernel parameter.
A kernel described by a pair of functions: func(x, y) = colFunc(x) * rowFunc(y)
Definition: Kernel.h:860
std::shared_ptr< Kernel > clone() const override
Return a pointer to a deep copy of this kernel.
void setKernelParameter(unsigned int ind, double value) const override
Set one kernel parameter.
SeparableKernel()
Construct an empty spatially invariant SeparableKernel of size 0x0.
Parameters to control convolution.
Definition: warpExposure.h:276
void setWarpingKernel(SeparableKernel const &warpingKernel)
set the warping kernel
int getInterpLength() const
get the interpolation length (pixels)
Definition: warpExposure.h:329
std::string getPythonModule() const override
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
void setWarpingKernelName(std::string const &warpingKernelName)
set the warping kernel by name
void setMaskWarpingKernelName(std::string const &maskWarpingKernelName)
set or clear the mask warping kernel by name
std::string getPersistenceName() const override
Return the unique name used to persist this object and look up its factory.
void setMaskWarpingKernel(SeparableKernel const &maskWarpingKernel)
set the mask warping kernel
std::shared_ptr< SeparableKernel > getWarpingKernel() const
get the warping kernel
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
std::shared_ptr< SeparableKernel > getMaskWarpingKernel() const
get the mask warping kernel
bool isPersistable() const noexcept override
Return true if this particular object can be persisted using afw::table::io.
A functor that computes one warped pixel.
std::shared_ptr< RecordT > addNew()
Create a new record, add it to the end of the catalog, and return a pointer to it.
Definition: Catalog.h:490
An object passed to Persistable::write to allow it to persist itself.
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
void saveEmpty()
Indicate that the object being persisted has no state, and hence will never call makeCatalog() or sav...
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
int put(Persistable const *obj, bool permissive=false)
Save an object to the archive and return a unique ID that can be used to retrieve it from an InputArc...
PersistableFactory(std::string const &name)
Constructor for the factory.
Definition: Persistable.cc:74
bool contains(Point2I const &point) const noexcept
T clear(T... args)
T emplace_back(T... args)
T empty(T... args)
T endl(T... args)
def keys(self)
void swap(CameraSys &a, CameraSys &b)
Definition: CameraSys.h:157
std::shared_ptr< TransformPoint2ToPoint2 > makeTransform(lsst::geom::AffineTransform const &affine)
Wrap an lsst::geom::AffineTransform as a Transform.
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
A Transform obtained by putting two SkyWcs objects "back to back".
Definition: SkyWcs.cc:146
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
Definition: Transform.h:300
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
std::int32_t MaskPixel
default type for Masks and MaskedImage Masks
double indexToPosition(double ind)
Convert image index to image position.
Definition: ImageUtils.h:55
bool imagesOverlap(ImageBase< T1 > const &image1, ImageBase< T2 > const &image2)
Return true if the pixels for two images or masks overlap in memory.
Definition: Image.cc:694
std::shared_ptr< SeparableKernel > makeWarpingKernel(std::string name)
Return a warping kernel given its name.
int warpCenteredImage(DestImageT &destImage, SrcImageT const &srcImage, lsst::geom::LinearTransform const &linearTransform, lsst::geom::Point2D const &centerPosition, WarpingControl const &control, typename DestImageT::SinglePixel padValue=lsst::afw::math::edgePixel< DestImageT >(typename lsst::afw::image::detail::image_traits< DestImageT >::image_category()))
Warp an image with a LinearTranform about a specified point.
int warpImage(DestImageT &destImage, geom::SkyWcs const &destWcs, SrcImageT const &srcImage, geom::SkyWcs const &srcWcs, WarpingControl const &control, typename DestImageT::SinglePixel padValue=lsst::afw::math::edgePixel< DestImageT >(typename lsst::afw::image::detail::image_traits< DestImageT >::image_category()))
Warp an Image or MaskedImage to a new Wcs.
int warpExposure(DestExposureT &destExposure, SrcExposureT const &srcExposure, WarpingControl const &control, typename DestExposureT::MaskedImageT::SinglePixel padValue=lsst::afw::math::edgePixel< typename DestExposureT::MaskedImageT >(typename lsst::afw::image::detail::image_traits< typename DestExposureT::MaskedImageT >::image_category()))
Warp (remap) one exposure to another.
Extent< double, 2 > Extent2D
A base class for image defects.
T push_back(T... args)
T regex_match(T... args)
T reserve(T... args)
T size(T... args)
T stoi(T... args)
T str(T... args)
typename ImageT::image_category image_category
Definition: ImageBase.h:67
T swap(T... args)
table::Key< int > warpingKernelIndex
table::Schema schema
std::shared_ptr< table::io::Persistable > read(table::io::InputArchive const &archive, table::io::CatalogVector const &catalogs) const override
Definition: warpExposure.cc:0
table::Key< int > interpLength
table::Key< image::MaskPixel > growFullMask
table::Key< int > order
table::Key< int > cacheSize
table::Key< int > maskKernelIndex
table::Key< table::Flag > hasMaskKernel