lsst.afw  22.0.1-24-g2e899d296+b05a4897c9
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  typedef std::shared_ptr<SeparableKernel> KernelPtr;
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 colBand = 0, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
551  int const endCol = edgeColList[colBand];
552  endColPosList.emplace_back(lsst::geom::Point2D(endCol, -1));
553  }
554  auto rightSrcPosList = localDestToParentSrc->applyForward(endColPosList);
555  srcPosView[-1] = rightSrcPosList[0];
556  for (int colBand = 1, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
557  int const prevEndCol = edgeColList[colBand - 1];
558  int const endCol = edgeColList[colBand];
559  lsst::geom::Point2D leftSrcPos = srcPosView[prevEndCol];
560 
561  lsst::geom::Extent2D xDeltaSrcPos =
562  (rightSrcPosList[colBand] - leftSrcPos) * invWidthList[colBand];
563 
564  for (int col = prevEndCol + 1; col <= endCol; ++col) {
565  srcPosView[col] = srcPosView[col - 1] + xDeltaSrcPos;
566  }
567  }
568 
569  int endRow = -1;
570  while (endRow < maxRow) {
571  // Next horizontal interpolation band
572 
573  int prevEndRow = endRow;
574  endRow = prevEndRow + interpLength;
575  if (endRow > maxRow) {
576  endRow = maxRow;
577  }
578  assert(endRow - prevEndRow > 0);
579  double interpInvHeight = 1.0 / static_cast<double>(endRow - prevEndRow);
580 
581  // Set yDeltaSrcPosList for this horizontal interpolation band
582  std::vector<lsst::geom::Point2D> destRowPosList;
583  destRowPosList.reserve(edgeColList.size());
584  for (int colBand = 0, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
585  int endCol = edgeColList[colBand];
586  destRowPosList.emplace_back(lsst::geom::Point2D(endCol, endRow));
587  }
588  auto bottomSrcPosList = localDestToParentSrc->applyForward(destRowPosList);
589  for (int colBand = 0, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
590  int endCol = edgeColList[colBand];
591  yDeltaSrcPosList[colBand] =
592  (bottomSrcPosList[colBand] - srcPosView[endCol]) * interpInvHeight;
593  }
594 
595  for (int row = prevEndRow + 1; row <= endRow; ++row) {
596  typename DestImageT::x_iterator destXIter = destImage.row_begin(row);
597  srcPosView[-1] += yDeltaSrcPosList[0];
598  for (int colBand = 1, endBand = edgeColList.size(); colBand < endBand; ++colBand) {
599  // Next vertical interpolation band
600 
601  int const prevEndCol = edgeColList[colBand - 1];
602  int const endCol = edgeColList[colBand];
603 
604  // Compute xDeltaSrcPos; remember that srcPosView contains
605  // positions for this row in prevEndCol and smaller indices,
606  // and positions for the previous row for larger indices (including endCol)
607  lsst::geom::Point2D leftSrcPos = srcPosView[prevEndCol];
608  lsst::geom::Point2D rightSrcPos = srcPosView[endCol] + yDeltaSrcPosList[colBand];
609  lsst::geom::Extent2D xDeltaSrcPos = (rightSrcPos - leftSrcPos) * invWidthList[colBand];
610 
611  for (int col = prevEndCol + 1; col <= endCol; ++col, ++destXIter) {
612  lsst::geom::Point2D leftSrcPos = srcPosView[col - 1];
613  lsst::geom::Point2D srcPos = leftSrcPos + xDeltaSrcPos;
614  double relativeArea = computeRelativeArea(srcPos, leftSrcPos, srcPosView[col]);
615 
616  srcPosView[col] = srcPos;
617 
618  if (warpAtOnePoint(
619  destXIter, srcPos, relativeArea,
621  ++numGoodPixels;
622  }
623  } // for col
624  } // for col band
625  } // for row
626  } // while next row band
627 
628  } else {
629  // No interpolation
630 
631  // prevSrcPosList = source positions from the previous row; these are used to compute pixel area;
632  // to begin, compute sources positions corresponding to destination row = -1
634  destPosList.reserve(1 + destWidth);
635  for (int col = -1; col < destWidth; ++col) {
636  destPosList.emplace_back(lsst::geom::Point2D(col, -1));
637  }
638  auto prevSrcPosList = localDestToParentSrc->applyForward(destPosList);
639 
640  for (int row = 0; row < destHeight; ++row) {
641  destPosList.clear();
642  for (int col = -1; col < destWidth; ++col) {
643  destPosList.emplace_back(lsst::geom::Point2D(col, row));
644  }
645  auto srcPosList = localDestToParentSrc->applyForward(destPosList);
646 
647  typename DestImageT::x_iterator destXIter = destImage.row_begin(row);
648  for (int col = 0; col < destWidth; ++col, ++destXIter) {
649  // column index = column + 1 because the first entry in srcPosList is for column -1
650  auto srcPos = srcPosList[col + 1];
651  double relativeArea =
652  computeRelativeArea(srcPos, prevSrcPosList[col], prevSrcPosList[col + 1]);
653 
654  if (warpAtOnePoint(destXIter, srcPos, relativeArea,
656  ++numGoodPixels;
657  }
658  } // for col
659  // move points from srcPosList to prevSrcPosList (we don't care about what ends up in srcPosList
660  // because it will be reallocated anyway)
661  swap(srcPosList, prevSrcPosList);
662  } // for row
663  } // if interp
664 
665  return numGoodPixels;
666 }
667 
668 template <typename DestImageT, typename SrcImageT>
669 int warpCenteredImage(DestImageT &destImage, SrcImageT const &srcImage,
670  lsst::geom::LinearTransform const &linearTransform,
671  lsst::geom::Point2D const &centerPosition, WarpingControl const &control,
672  typename DestImageT::SinglePixel padValue) {
673  // force src and dest to be the same size and xy0
674  if ((destImage.getWidth() != srcImage.getWidth()) || (destImage.getHeight() != srcImage.getHeight()) ||
675  (destImage.getXY0() != srcImage.getXY0())) {
676  std::ostringstream errStream;
677  errStream << "src and dest images must have same size and xy0.";
679  }
680 
681  // set the xy0 coords to 0,0 to make life easier
682  SrcImageT srcImageCopy(srcImage, true);
683  srcImageCopy.setXY0(0, 0);
684  destImage.setXY0(0, 0);
685  lsst::geom::Extent2D cLocal =
686  lsst::geom::Extent2D(centerPosition) - lsst::geom::Extent2D(srcImage.getXY0());
687 
688  // for the affine transform, the centerPosition will not only get sheared, but also
689  // moved slightly. So we'll include a translation to move it back by an amount
690  // centerPosition - translatedCenterPosition
691  lsst::geom::AffineTransform affTran(linearTransform, cLocal - linearTransform(cLocal));
693 
694 // now warp
695 #if 0
696  static float t = 0.0;
697  float t_before = 1.0*clock()/CLOCKS_PER_SEC;
698  int n = warpImage(destImage, srcImageCopy, affTran, control, padValue);
699  float t_after = 1.0*clock()/CLOCKS_PER_SEC;
700  float dt = t_after - t_before;
701  t += dt;
702  std::cout <<srcImage.getWidth()<<"x"<<srcImage.getHeight()<<": "<< dt <<" "<< t <<std::endl;
703 #else
704  int n = warpImage(destImage, srcImageCopy, *affineTransform22, control, padValue);
705 #endif
706 
707  // fix the origin and we're done.
708  destImage.setXY0(srcImage.getXY0());
709 
710  return n;
711 }
712 
713 //
714 // Explicit instantiations
715 //
717 // may need to omit default params for EXPOSURE -- original code did that and it worked
718 #define EXPOSURE(PIXTYPE) image::Exposure<PIXTYPE, image::MaskPixel, image::VariancePixel>
719 #define MASKEDIMAGE(PIXTYPE) image::MaskedImage<PIXTYPE, image::MaskPixel, image::VariancePixel>
720 #define IMAGE(PIXTYPE) image::Image<PIXTYPE>
721 #define NL /* */
722 
723 #define INSTANTIATE(DESTIMAGEPIXELT, SRCIMAGEPIXELT) \
724  template int warpCenteredImage( \
725  IMAGE(DESTIMAGEPIXELT) & destImage, IMAGE(SRCIMAGEPIXELT) const &srcImage, \
726  lsst::geom::LinearTransform const &linearTransform, lsst::geom::Point2D const &centerPosition, \
727  WarpingControl const &control, IMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
728  NL template int warpCenteredImage( \
729  MASKEDIMAGE(DESTIMAGEPIXELT) & destImage, MASKEDIMAGE(SRCIMAGEPIXELT) const &srcImage, \
730  lsst::geom::LinearTransform const &linearTransform, lsst::geom::Point2D const &centerPosition, \
731  WarpingControl const &control, MASKEDIMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
732  NL template int warpImage(IMAGE(DESTIMAGEPIXELT) & destImage, IMAGE(SRCIMAGEPIXELT) const &srcImage, \
733  geom::TransformPoint2ToPoint2 const &srcToDest, WarpingControl const &control, \
734  IMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
735  NL template int warpImage(MASKEDIMAGE(DESTIMAGEPIXELT) & destImage, \
736  MASKEDIMAGE(SRCIMAGEPIXELT) const &srcImage, \
737  geom::TransformPoint2ToPoint2 const &srcToDest, WarpingControl const &control, \
738  MASKEDIMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
739  NL template int warpImage(IMAGE(DESTIMAGEPIXELT) & destImage, geom::SkyWcs const &destWcs, \
740  IMAGE(SRCIMAGEPIXELT) const &srcImage, geom::SkyWcs const &srcWcs, \
741  WarpingControl const &control, IMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
742  NL template int warpImage(MASKEDIMAGE(DESTIMAGEPIXELT) & destImage, geom::SkyWcs const &destWcs, \
743  MASKEDIMAGE(SRCIMAGEPIXELT) const &srcImage, geom::SkyWcs const &srcWcs, \
744  WarpingControl const &control, \
745  MASKEDIMAGE(DESTIMAGEPIXELT)::SinglePixel padValue); \
746  NL template int warpExposure(EXPOSURE(DESTIMAGEPIXELT) & destExposure, \
747  EXPOSURE(SRCIMAGEPIXELT) const &srcExposure, WarpingControl const &control, \
748  EXPOSURE(DESTIMAGEPIXELT)::MaskedImageT::SinglePixel padValue);
749 
750 INSTANTIATE(double, double)
751 INSTANTIATE(double, float)
752 INSTANTIATE(double, int)
753 INSTANTIATE(double, std::uint16_t)
754 INSTANTIATE(float, float)
755 INSTANTIATE(float, int)
757 INSTANTIATE(int, int)
760 } // namespace math
761 
762 template std::shared_ptr<math::LanczosWarpingKernel> table::io::PersistableFacade<
763  math::LanczosWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
764 template std::shared_ptr<math::BilinearWarpingKernel> table::io::PersistableFacade<
765  math::BilinearWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
766 template std::shared_ptr<math::NearestWarpingKernel> table::io::PersistableFacade<
767  math::NearestWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
768 template std::shared_ptr<math::WarpingControl> table::io::PersistableFacade<
769  math::WarpingControl>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
770 
771 } // namespace afw
772 } // 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)
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