Skip to content

Commit

Permalink
Psf: use new lsst::utils::Cache
Browse files Browse the repository at this point in the history
This allows caching many more realisations (they're relatively cheap:
50x50 pixels per realisation @ 8 bytes per pixel); the number of
realisations is configurable.

This gains about 22% in the runtime of measureCoaddSources.py using the
default 100 cache slots for all Psf instances. Allowing CoaddPsf to use
100000 cache slots increases the gain to about 25%.
  • Loading branch information
PaulPrice committed Mar 27, 2018
1 parent 2776184 commit 9ecf299
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 40 deletions.
37 changes: 27 additions & 10 deletions include/lsst/afw/detection/Psf.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <memory>

#include "lsst/daf/base.h"
#include "lsst/utils/CacheFwd.h"
#include "lsst/afw/geom/ellipses/Quadrupole.h"
#include "lsst/afw/math/Kernel.h"
#include "lsst/afw/image/Color.h"
Expand All @@ -37,6 +38,12 @@
namespace lsst {
namespace afw {
namespace detection {
namespace detail {

/// Key for caching PSFs with lsst::utils::Cache
struct PsfCacheKey;

} // namespace detail

class PsfFormatter;

Expand Down Expand Up @@ -86,12 +93,12 @@ class Psf : public daf::base::Citizen,
*/
};

Psf(Psf const&) = default;
Psf(Psf&&) = default;
Psf(Psf const&);
Psf& operator=(Psf const&) = delete;
Psf& operator=(Psf&&) = delete;

virtual ~Psf() = default;
Psf(Psf&&);
virtual ~Psf();

/**
* Polymorphic deep-copy.
Expand Down Expand Up @@ -254,14 +261,27 @@ class Psf : public daf::base::Citizen,
std::string const& warpAlgorithm = "lanczos5",
unsigned int warpBuffer = 5);

/** Return the capacity of the caches
*
* Both the image and kernel image caches have the same capacity.
*/
std::size_t getCacheCapacity() const;

/** Set the capacity of the caches
*
* Both the image and kernel image caches will be set to this capacity.
*/
void setCacheCapacity(std::size_t capacity);

protected:
/**
* Main constructor for subclasses.
*
* @param[in] isFixed Should be true for Psf for which doComputeKernelImage always returns
* the same image, regardless of color or position arguments.
* @param[in] capacity Capacity of the caches.
*/
explicit Psf(bool isFixed = false);
explicit Psf(bool isFixed = false, std::size_t capacity=100);

private:
//@{
Expand All @@ -284,12 +304,9 @@ class Psf : public daf::base::Citizen,
//@}

bool const _isFixed;
mutable std::shared_ptr<Image> _cachedImage;
mutable std::shared_ptr<Image> _cachedKernelImage;
mutable image::Color _cachedImageColor;
mutable image::Color _cachedKernelImageColor;
mutable geom::Point2D _cachedImagePosition;
mutable geom::Point2D _cachedKernelImagePosition;
using PsfCache = utils::Cache<detail::PsfCacheKey, std::shared_ptr<Image>>;
std::unique_ptr<PsfCache> _imageCache;
std::unique_ptr<PsfCache> _kernelImageCache;

LSST_PERSIST_FORMATTER(PsfFormatter)
};
Expand Down
2 changes: 2 additions & 0 deletions python/lsst/afw/detection/psf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ PYBIND11_PLUGIN(_psf) {
cls.def("getAveragePosition", &Psf::getAveragePosition);
cls.def_static("recenterKernelImage", &Psf::recenterKernelImage, "im"_a, "position"_a,
"warpAlgorithm"_a = "lanczos5", "warpBuffer"_a = 5);
cls.def("getCacheCapacity", &Psf::getCacheCapacity);
cls.def("setCacheCapacity", &Psf::setCacheCapacity);

return mod.ptr();
}
Expand Down
110 changes: 80 additions & 30 deletions src/detection/Psf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,82 @@
#include <limits>
#include <typeinfo>
#include <cmath>
#include <memory>

#include "lsst/utils/Cache.h"
#include "lsst/utils/hashCombine.h"
#include "lsst/afw/detection/Psf.h"
#include "lsst/afw/math/offsetImage.h"

namespace lsst::afw::detection::detail {

// Key for caching PSFs with lsst::utils::Cache
//
// We cache PSFs by their x,y position. Although there are placeholders
// in the `Psf` class and here for `image::Color`, these are not used
// in the cache because `image::Color` is not currently well-defined
// or used.
struct PsfCacheKey {
geom::Point2D const position;
image::Color const color;

PsfCacheKey(geom::Point2D const& position_, image::Color color_=image::Color())
: position(position_), color(color_) {}

bool operator==(PsfCacheKey const& other) const {
return position == other.position; // Currently don't care about color
}

friend std::ostream & operator<<(std::ostream &os, PsfCacheKey const &key) {
return os << key.position;
}
};

} // namespace lsst::afw::detection::detail

namespace std {

// Template specialisation for hashing PsfCacheKey
//
// We currently ignore the color.
template <>
struct hash<lsst::afw::detection::detail::PsfCacheKey> {
std::size_t operator()(lsst::afw::detection::detail::PsfCacheKey const& key) const {
std::size_t seed = 0;
return lsst::utils::hashCombine(seed, key.position.getX(), key.position.getY());
}
};

} // namespace std

namespace lsst {
namespace afw {
namespace detection {

namespace {

// Comparison function that determines when we used the cached image instead of recomputing it.
// We'll probably want a tolerance for colors someday too, but they're just a placeholder right now
// so it's not worth the effort.
bool comparePsfEvalPoints(geom::Point2D const &a, geom::Point2D const &b) {
// n.b. desired tolerance is actually sqrt(eps), so tolerance squared is eps.
return (a - b).computeSquaredNorm() < std::numeric_limits<double>::epsilon();
}

bool isPointNull(geom::Point2D const &p) { return std::isnan(p.getX()) && std::isnan(p.getY()); }

} // anonymous

Psf::Psf(bool isFixed) : daf::base::Citizen(typeid(this)), _isFixed(isFixed) {}
Psf::Psf(bool isFixed, std::size_t capacity)
: daf::base::Citizen(typeid(this)),
_isFixed(isFixed)
{
_imageCache = std::make_unique<PsfCache>(capacity);
_kernelImageCache = std::make_unique<PsfCache>(capacity);
}

Psf::~Psf() = default;

Psf::Psf(Psf const& other) : Psf(other._isFixed, other.getCacheCapacity()) {}

Psf::Psf(Psf && other)
: daf::base::Citizen(std::move(other)),
_isFixed(other._isFixed),
_imageCache(std::move(other._imageCache)),
_kernelImageCache(std::move(other._kernelImageCache))
{}

std::shared_ptr<image::Image<double>> Psf::recenterKernelImage(std::shared_ptr<Image> im,
geom::Point2D const &position,
Expand All @@ -46,15 +99,10 @@ std::shared_ptr<Psf::Image> Psf::computeImage(geom::Point2D position, image::Col
ImageOwnerEnum owner) const {
if (isPointNull(position)) position = getAveragePosition();
if (color.isIndeterminate()) color = getAverageColor();
std::shared_ptr<Psf::Image> result;
if (_cachedImage && color == _cachedImageColor && comparePsfEvalPoints(position, _cachedImagePosition)) {
result = _cachedImage;
} else {
result = doComputeImage(position, color);
_cachedImage = result;
_cachedImageColor = color;
_cachedImagePosition = position;
}
std::shared_ptr<Psf::Image> result = (*_imageCache)(
detail::PsfCacheKey(position, color),
[this](detail::PsfCacheKey const& key) { return doComputeImage(key.position, key.color); }
);
if (owner == COPY) {
result = std::make_shared<Image>(*result, true);
}
Expand All @@ -63,18 +111,12 @@ std::shared_ptr<Psf::Image> Psf::computeImage(geom::Point2D position, image::Col

std::shared_ptr<Psf::Image> Psf::computeKernelImage(geom::Point2D position, image::Color color,
ImageOwnerEnum owner) const {
if (isPointNull(position)) position = getAveragePosition();
if (color.isIndeterminate()) color = getAverageColor();
std::shared_ptr<Psf::Image> result;
if (_cachedKernelImage && (_isFixed || (color == _cachedKernelImageColor &&
comparePsfEvalPoints(position, _cachedKernelImagePosition)))) {
result = _cachedKernelImage;
} else {
result = doComputeKernelImage(position, color);
_cachedKernelImage = result;
_cachedKernelImageColor = color;
_cachedKernelImagePosition = position;
}
if (_isFixed || isPointNull(position)) position = getAveragePosition();
if (_isFixed || color.isIndeterminate()) color = getAverageColor();
std::shared_ptr<Psf::Image> result = (*_kernelImageCache)(
detail::PsfCacheKey(position, color),
[this](detail::PsfCacheKey const& key) { return doComputeKernelImage(key.position, key.color); }
);
if (owner == COPY) {
result = std::make_shared<Image>(*result, true);
}
Expand Down Expand Up @@ -121,6 +163,14 @@ std::shared_ptr<Psf::Image> Psf::doComputeImage(geom::Point2D const &position,
}

geom::Point2D Psf::getAveragePosition() const { return geom::Point2D(); }

std::size_t Psf::getCacheCapacity() const { return _kernelImageCache->capacity(); }

void Psf::setCacheCapacity(std::size_t capacity) {
_imageCache->reserve(capacity);
_kernelImageCache->reserve(capacity);
}

}
}
} // namespace lsst::afw::detection

0 comments on commit 9ecf299

Please sign in to comment.