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