Skip to content

Commit

Permalink
Merge branch 'tickets/DM-30829'
Browse files Browse the repository at this point in the history
  • Loading branch information
kfindeisen committed Jun 24, 2021
2 parents 119a660 + 55a094a commit 2736904
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 5 deletions.
37 changes: 35 additions & 2 deletions include/lsst/afw/math/warpExposure.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "lsst/afw/math/Function.h"
#include "lsst/afw/math/FunctionLibrary.h"
#include "lsst/afw/math/Kernel.h"
#include "lsst/afw/table/io/Persistable.h"

namespace lsst {
namespace afw {
Expand Down Expand Up @@ -83,8 +84,16 @@ class LanczosWarpingKernel : public SeparableKernel {
*/
int getOrder() const;

bool isPersistable() const noexcept override { return true; }

protected:
void setKernelParameter(unsigned int ind, double value) const override;

std::string getPersistenceName() const override { return "LanczosWarpingKernel"; }

std::string getPythonModule() const override { return "lsst.afw.math"; }

void write(OutputArchiveHandle &handle) const override;
};

/**
Expand All @@ -110,6 +119,8 @@ class BilinearWarpingKernel : public SeparableKernel {

std::shared_ptr<Kernel> clone() const override;

bool isPersistable() const noexcept override { return true; }

/**
* 1-dimensional bilinear interpolation function.
*
Expand Down Expand Up @@ -149,6 +160,12 @@ class BilinearWarpingKernel : public SeparableKernel {

protected:
void setKernelParameter(unsigned int ind, double value) const override;

std::string getPersistenceName() const override { return "BillinearWarpingKernel"; }

std::string getPythonModule() const override { return "lsst.afw.math"; }

void write(OutputArchiveHandle &handle) const override;
};

/**
Expand All @@ -173,6 +190,8 @@ class NearestWarpingKernel : public SeparableKernel {

std::shared_ptr<Kernel> clone() const override;

bool isPersistable() const noexcept override { return true; }

/**
* 1-dimensional nearest neighbor interpolation function.
*
Expand Down Expand Up @@ -212,6 +231,12 @@ class NearestWarpingKernel : public SeparableKernel {

protected:
void setKernelParameter(unsigned int ind, double value) const override;

std::string getPersistenceName() const override { return "NearestWarpingKernel"; }

std::string getPythonModule() const override { return "lsst.afw.math"; }

void write(OutputArchiveHandle &handle) const override;
};

/**
Expand Down Expand Up @@ -247,7 +272,8 @@ std::shared_ptr<SeparableKernel> makeWarpingKernel(std::string name);
*
* @ingroup afw
*/
class WarpingControl {
class WarpingControl final : public table::io::PersistableFacade<WarpingControl>,
public table::io::Persistable {
public:
/**
* Construct a WarpingControl object
Expand Down Expand Up @@ -373,6 +399,13 @@ class WarpingControl {
_growFullMask = growFullMask;
}

bool isPersistable() const noexcept override;

protected:
std::string getPersistenceName() const override;
std::string getPythonModule() const override;
void write(OutputArchiveHandle &handle) const override;

private:
/**
* Throw an exception if the two kernels are not compatible in shape
Expand All @@ -382,7 +415,7 @@ class WarpingControl {
*/
void _testWarpingKernels(SeparableKernel const &warpingKernel, ///< warping kernel
SeparableKernel const &maskWarpingKernel ///< mask warping kernel
) const;
) const;

std::shared_ptr<SeparableKernel> _warpingKernelPtr;
std::shared_ptr<SeparableKernel> _maskWarpingKernelPtr;
Expand Down
2 changes: 1 addition & 1 deletion include/lsst/afw/table/io/Persistable.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ LSST_EXCEPTION_TYPE(MalformedArchiveError, lsst::afw::table::io::PersistenceErro
* A concrete class that inherits (possibly indirectly) from Persistable should inherit from
* PersistableFacade<Derived> (though this just provides a slightly nicer interface to users),
* implement isPersistable(), getPersistenceName(), getPythonModule(), and write(),
* and define a subclass of PersistenceFactory. Inheritance from PersistableFacade should
* and define a subclass of PersistableFactory. Inheritance from PersistableFacade should
* always precede inheritance from Persistable.
*
* Persistable has no pure virtual member functions, and instead contains a default implementation
Expand Down
6 changes: 5 additions & 1 deletion python/lsst/afw/math/_warpExposure.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "lsst/afw/image/MaskedImage.h"
#include "lsst/afw/math/Kernel.h"
#include "lsst/afw/math/warpExposure.h"
#include "lsst/afw/table/io/python.h"

namespace py = pybind11;
using namespace py::literals;
Expand All @@ -57,6 +58,7 @@ void declareWarpingKernel(lsst::utils::python::WrapperCollection &wrappers, std:
cls.def(py::init<int>(), "order"_a);
cls.def("getOrder", &KernelT::getOrder);
cls.def("clone", &KernelT::clone);
table::io::python::addPersistableMethods(cls);
});
}

Expand All @@ -74,6 +76,7 @@ void declareSimpleWarpingKernel(lsst::utils::python::WrapperCollection &wrappers
wrappers.wrapType(PyClass(wrappers.module, name.c_str()), [](auto &mod, auto &cls) {
cls.def(py::init<>());
cls.def("clone", &KernelT::clone);
table::io::python::addPersistableMethods(cls);
});
}

Expand Down Expand Up @@ -158,6 +161,7 @@ void declareWarpExposure(lsst::utils::python::WrapperCollection &wrappers) {
cls.def("setMaskWarpingKernel", &WarpingControl::setMaskWarpingKernel, "maskWarpingKernel"_a);
cls.def("getGrowFullMask", &WarpingControl::getGrowFullMask);
cls.def("setGrowFullMask", &WarpingControl::setGrowFullMask, "growFullMask"_a);
table::io::python::addPersistableMethods(cls);
});
}
} // namespace
Expand All @@ -181,4 +185,4 @@ void wrapWarpExposure(lsst::utils::python::WrapperCollection &wrappers) {
}
} // namespace math
} // namespace afw
} // namespace lsst
} // namespace lsst
172 changes: 171 additions & 1 deletion src/math/warpExposure.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
#include "lsst/afw/math/Kernel.h"
#include "lsst/afw/image/PhotoCalib.h"
#include "lsst/afw/math/detail/WarpAtOnePoint.h"
#include "lsst/afw/table/io/InputArchive.h"
#include "lsst/afw/table/io/OutputArchive.h"
#include "lsst/afw/table/io/CatalogVector.h"
#include "lsst/afw/table/io/Persistable.cc" // Needed for PersistableFacade::dynamicCast

namespace pexExcept = lsst::pex::exceptions;

Expand Down Expand Up @@ -158,6 +162,69 @@ std::string NearestWarpingKernel::NearestFunction1::toString(std::string const &
return os.str();
}

namespace {

struct LanczosKernelPersistenceHelper {
table::Schema schema;
table::Key<int> order;

static LanczosKernelPersistenceHelper const &get() {
static LanczosKernelPersistenceHelper const instance;
return instance;
}

LanczosKernelPersistenceHelper(LanczosKernelPersistenceHelper const &) = delete;
LanczosKernelPersistenceHelper(LanczosKernelPersistenceHelper &&) = delete;
LanczosKernelPersistenceHelper &operator=(LanczosKernelPersistenceHelper const &) = delete;
LanczosKernelPersistenceHelper &operator=(LanczosKernelPersistenceHelper &&) = delete;

private:
LanczosKernelPersistenceHelper()
: schema(), order(schema.addField<int>("order", "order of Lanczos function")) {}
};

class : public table::io::PersistableFactory {
std::shared_ptr<table::io::Persistable> read(table::io::InputArchive const &archive,
table::io::CatalogVector const &catalogs) const override {
auto const &keys = LanczosKernelPersistenceHelper::get();
LSST_ARCHIVE_ASSERT(catalogs.size() == 1u);
LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
afw::table::BaseRecord const &record = catalogs.front().front();
LSST_ARCHIVE_ASSERT(record.getSchema() == keys.schema);
return std::make_shared<LanczosWarpingKernel>(record.get(keys.order));
}

using table::io::PersistableFactory::PersistableFactory;
} lanczosFactory("LanczosWarpingKernel");

template <class T>
class DefaultPersistableFactory : public table::io::PersistableFactory {
std::shared_ptr<table::io::Persistable> read(table::io::InputArchive const &archive,
table::io::CatalogVector const &catalogs) const override {
LSST_ARCHIVE_ASSERT(catalogs.empty());
return std::make_shared<T>();
}

using table::io::PersistableFactory::PersistableFactory;
};

DefaultPersistableFactory<BilinearWarpingKernel> bilinearFactory("BilinearWarpingKernel");
DefaultPersistableFactory<NearestWarpingKernel> nearestFactory("NearestWarpingKernel");

} // namespace

void LanczosWarpingKernel::write(OutputArchiveHandle &handle) const {
auto const &keys = LanczosKernelPersistenceHelper::get();
table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
std::shared_ptr<table::BaseRecord> record = catalog.addNew();
record->set(keys.order, getOrder());
handle.saveCatalog(catalog);
}

void BilinearWarpingKernel::write(OutputArchiveHandle &handle) const { handle.saveEmpty(); }

void NearestWarpingKernel::write(OutputArchiveHandle &handle) const { handle.saveEmpty(); }

std::shared_ptr<SeparableKernel> makeWarpingKernel(std::string name) {
typedef std::shared_ptr<SeparableKernel> KernelPtr;
std::smatch matches;
Expand Down Expand Up @@ -233,6 +300,99 @@ void WarpingControl::_testWarpingKernels(SeparableKernel const &warpingKernel,
}
}

namespace {

struct WarpingControlPersistenceHelper {
table::Schema schema;
table::Key<int> warpingKernelIndex;
table::Key<table::Flag> hasMaskKernel;
table::Key<int> maskKernelIndex; // undefined if !hasMaskKernel
table::Key<int> cacheSize;
table::Key<int> interpLength;
table::Key<image::MaskPixel> growFullMask;

static WarpingControlPersistenceHelper const &get() {
static WarpingControlPersistenceHelper const instance;
return instance;
}

WarpingControlPersistenceHelper(WarpingControlPersistenceHelper const &) = delete;
WarpingControlPersistenceHelper(WarpingControlPersistenceHelper &&) = delete;
WarpingControlPersistenceHelper &operator=(WarpingControlPersistenceHelper const &) = delete;
WarpingControlPersistenceHelper &operator=(WarpingControlPersistenceHelper &&) = delete;

private:
WarpingControlPersistenceHelper()
: schema(),
warpingKernelIndex(
schema.addField<int>("warpingKernelIndex", "archive ID of nested warping kernel")),
hasMaskKernel(schema.addField<table::Flag>("hasMaskKernel", "whether a mask kernel is stored")),
maskKernelIndex(schema.addField<int>("maskKernelIndex",
"archive ID of nested mask kernel. "
"Valid only if hasMaskKernel")),
cacheSize(schema.addField<int>("cacheSize", "Cache size for warping kernel(s)")),
interpLength(schema.addField<int>("interpLength",
"Distance over which WCS can be linearly interpolated")),
growFullMask(schema.addField<image::MaskPixel>(
"growFullMask", "bits to grow to full width of image/variance kernel")) {}
};

std::string _getWarpingControlPersistenceName() { return "WarpingControl"; }

class : public table::io::PersistableFactory {
std::shared_ptr<table::io::Persistable> read(table::io::InputArchive const &archive,
table::io::CatalogVector const &catalogs) const override {
auto const &keys = WarpingControlPersistenceHelper::get();
LSST_ARCHIVE_ASSERT(catalogs.size() == 1u);
LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
afw::table::BaseRecord const &record = catalogs.front().front();
LSST_ARCHIVE_ASSERT(record.getSchema() == keys.schema);

// Warping kernels are dummy values, set true kernels later
auto control = std::make_shared<WarpingControl>("bilinear", "", record.get(keys.cacheSize),
record.get(keys.interpLength),
record.get(keys.growFullMask));
// archive.get returns a shared_ptr, so this code depends on the
// undocumented fact that setWarpingKernel and setMaskWarpingKernel
// make defensive copies.
control->setWarpingKernel(*archive.get<SeparableKernel>(record.get(keys.warpingKernelIndex)));
if (record.get(keys.hasMaskKernel)) {
control->setMaskWarpingKernel(*archive.get<SeparableKernel>(record.get(keys.maskKernelIndex)));
}
return control;
}

using table::io::PersistableFactory::PersistableFactory;
} controlFactory(_getWarpingControlPersistenceName());

} // namespace

std::string WarpingControl::getPersistenceName() const { return _getWarpingControlPersistenceName(); }

std::string WarpingControl::getPythonModule() const { return "lsst.afw.math"; }

bool WarpingControl::isPersistable() const noexcept {
return _warpingKernelPtr->isPersistable() &&
(!hasMaskWarpingKernel() || _maskWarpingKernelPtr->isPersistable());
}

void WarpingControl::write(OutputArchiveHandle &handle) const {
auto const &keys = WarpingControlPersistenceHelper::get();
table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
std::shared_ptr<table::BaseRecord> record = catalog.addNew();

record->set(keys.warpingKernelIndex, handle.put(_warpingKernelPtr));
record->set(keys.hasMaskKernel, hasMaskWarpingKernel());
if (hasMaskWarpingKernel()) {
record->set(keys.maskKernelIndex, handle.put(_maskWarpingKernelPtr));
}
record->set(keys.cacheSize, _cacheSize);
record->set(keys.interpLength, _interpLength);
record->set(keys.growFullMask, _growFullMask);

handle.saveCatalog(catalog);
}

template <typename DestExposureT, typename SrcExposureT>
int warpExposure(DestExposureT &destExposure, SrcExposureT const &srcExposure, WarpingControl const &control,
typename DestExposureT::MaskedImageT::SinglePixel padValue) {
Expand Down Expand Up @@ -300,7 +460,7 @@ int warpImage(DestImageT &destImage, SrcImageT const &srcImage,
std::shared_ptr<SeparableKernel> warpingKernelPtr = control.getWarpingKernel();
try {
warpingKernelPtr->shrinkBBox(srcImage.getBBox(image::LOCAL));
} catch (lsst::pex::exceptions::InvalidParameterError const&) {
} catch (lsst::pex::exceptions::InvalidParameterError const &) {
for (int y = 0, height = destImage.getHeight(); y < height; ++y) {
for (typename DestImageT::x_iterator destPtr = destImage.row_begin(y), end = destImage.row_end(y);
destPtr != end; ++destPtr) {
Expand Down Expand Up @@ -598,5 +758,15 @@ INSTANTIATE(int, int)
INSTANTIATE(std::uint16_t, std::uint16_t)
/// @endcond
} // namespace math

template std::shared_ptr<math::LanczosWarpingKernel> table::io::PersistableFacade<
math::LanczosWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
template std::shared_ptr<math::BilinearWarpingKernel> table::io::PersistableFacade<
math::BilinearWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
template std::shared_ptr<math::NearestWarpingKernel> table::io::PersistableFacade<
math::NearestWarpingKernel>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);
template std::shared_ptr<math::WarpingControl> table::io::PersistableFacade<
math::WarpingControl>::dynamicCast(std::shared_ptr<table::io::Persistable> const &);

} // namespace afw
} // namespace lsst
16 changes: 16 additions & 0 deletions tests/test_warpExposure.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,22 @@ def testWarpingControl(self):
self.assertEqual(
wc.getMaskWarpingKernel().getCacheSize(), newCacheSize)

wc = afwMath.WarpingControl("lanczos4", "nearest", 64, 7, 42)
self.assertTrue(wc.isPersistable())
with lsst.utils.tests.getTempFilePath(".fits", expectOutput=True) as tempFile:
wc.writeFits(tempFile)
wc2 = afwMath.WarpingControl.readFits(tempFile)
self.assertEqual(wc.getCacheSize(), wc2.getCacheSize())
self.assertEqual(wc.getInterpLength(), wc2.getInterpLength())
self.assertEqual(wc.getWarpingKernel().getBBox(), wc2.getWarpingKernel().getBBox())
self.assertEqual(wc.getWarpingKernel().getKernelParameters(),
wc2.getWarpingKernel().getKernelParameters())
self.assertEqual(wc.hasMaskWarpingKernel(), wc2.hasMaskWarpingKernel())
self.assertEqual(wc.getMaskWarpingKernel().getBBox(), wc2.getMaskWarpingKernel().getBBox())
self.assertEqual(wc.getMaskWarpingKernel().getKernelParameters(),
wc2.getMaskWarpingKernel().getKernelParameters())
self.assertEqual(wc.getGrowFullMask(), wc2.getGrowFullMask())

def testWarpingControlError(self):
"""Test error handling of WarpingControl
"""
Expand Down

0 comments on commit 2736904

Please sign in to comment.