Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions include/lsst/meas/algorithms/python.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* This file is part of meas_algorithms.
*
* Developed for the LSST Data Management System.
* This product includes software developed by the LSST Project
* (https://www.lsst.org).
* See the COPYRIGHT file at the top-level directory of this distribution
* for details of code ownership.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef LSST_MEAS_ALGORITHMS_PYTHON_H
#define LSST_MEAS_ALGORITHMS_PYTHON_H

#include "pybind11/pybind11.h"
#include "lsst/meas/algorithms/ImagePsf.h"
#include "lsst/afw/detection/Psf.h"
#include "lsst/afw/detection/python.h"

using lsst::afw::detection::PsfTrampoline;
using lsst::afw::typehandling::StorableHelper;

namespace lsst {
namespace meas {
namespace algorithms {

/**
* "Trampoline" for ImagePsf to let it be used as a base class in Python.
*
* Subclasses of ImagePsf that are wrapped in %pybind11 should have a similar
* helper that subclasses `ImagePsfTrampoline<subclass>`. This helper can be
* skipped if the subclass neither adds any virtual methods nor implements
* any abstract methods.
*
* @tparam Base the exact (most specific) class being wrapped
*
* @see [pybind11 documentation](https://pybind11.readthedocs.io/en/stable/advanced/classes.html)
*/
template <typename Base = ImagePsf>
class ImagePsfTrampoline : public PsfTrampoline<Base> {
public:
/**
* Delegating constructor for wrapped class.
*
* While we would like to simply inherit base class constructors, when doing so, we cannot
* change their access specifiers. One consequence is that it's not possible to use inheritance
* to expose a protected constructor to python. The alternative, used here, is to create a new
* public constructor that delegates to the base class public or protected constructor with the
* same signature.
*
* @tparam Args Variadic type specification
* @param ...args Arguments to forward to the Base class constructor.
*/
template<typename... Args>
ImagePsfTrampoline<Base>(Args... args) : PsfTrampoline<Base>(args...) {}

double doComputeApertureFlux(
double radius, geom::Point2D const& position,
afw::image::Color const& color
) const override {
PYBIND11_OVERLOAD_NAME(
double, Base, "_doComputeApertureFlux", doComputeApertureFlux, radius, position, color
);
}

afw::geom::ellipses::Quadrupole doComputeShape(
geom::Point2D const& position,
afw::image::Color const& color
) const override {
PYBIND11_OVERLOAD_NAME(
afw::geom::ellipses::Quadrupole, Base, "_doComputeShape", doComputeShape, position, color
);
}
};

} // algorithms
} // meas
} // lsst




#endif // LSST_MEAS_ALGORITHMS_PYTHON_H
9 changes: 7 additions & 2 deletions python/lsst/meas/algorithms/imagePsf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@
*/
#include "pybind11/pybind11.h"

#include "lsst/utils/python/PySharedPtr.h"
#include "lsst/afw/table/io/python.h"
#include "lsst/meas/algorithms/ImagePsf.h"
#include "lsst/meas/algorithms/python.h"

namespace py = pybind11;
using namespace pybind11::literals;

using lsst::utils::python::PySharedPtr;

namespace lsst {
namespace meas {
namespace algorithms {
Expand All @@ -37,9 +41,10 @@ PYBIND11_MODULE(imagePsf, mod) {

afw::table::io::python::declarePersistableFacade<ImagePsf>(mod, "ImagePsf");

py::class_<ImagePsf, std::shared_ptr<ImagePsf>, afw::table::io::PersistableFacade<ImagePsf>,
afw::detection::Psf>
py::class_<ImagePsf, PySharedPtr<ImagePsf>, afw::table::io::PersistableFacade<ImagePsf>,
afw::detection::Psf, ImagePsfTrampoline<>>
clsImagePsf(mod, "ImagePsf");
clsImagePsf.def(py::init<bool>(), "init", "isFixed"_a=false); // Ctor for pure python subclasses
}

} // namespace
Expand Down
114 changes: 114 additions & 0 deletions tests/test_imagePsf_trampoline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# This file is part of meas_algorithms.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import unittest
from copy import deepcopy

import numpy as np

import lsst.utils.tests
from lsst.afw.image import Image
from lsst.geom import Box2I, Point2I, Extent2I
from lsst.meas.algorithms import ImagePsf


class DummyImagePsf(ImagePsf):
def __init__(self, image):
ImagePsf.__init__(self)
self.image = image

# "public" virtual overrides
def __deepcopy__(self, meta=None):
return DummyImagePsf(self.image)

def resized(self, width, height):
raise NotImplementedError("resized not implemented for DummyImagePsf")

# "private" virtual overrides are underscored
def _doComputeKernelImage(self, position=None, color=None):
return self.image

def _doComputeBBox(self, position=None, color=None):
return self.image.getBBox()


class ImagePsfTrampolineTestSuite(lsst.utils.tests.TestCase):
def setUp(self):
dimensions = Extent2I(7, 7)
self.bbox = Box2I(Point2I(-dimensions/2), dimensions)
self.img = Image(self.bbox, dtype=np.float64)
x, y = np.ogrid[-3:4, -3:4]
rsqr = x**2 + y**2
# Some arbitrary circular double Gaussian
self.img.array[:] = np.exp(-0.5*rsqr**2) + np.exp(-0.5*rsqr**2/4)
self.img.array /= np.sum(self.img.array)
self.psf = DummyImagePsf(self.img)

def testImage(self):
self.assertImagesEqual(
self.img,
self.psf.computeImage()
)
self.assertImagesEqual(
self.img,
self.psf.computeKernelImage()
)

def testBBox(self):
self.assertEqual(
self.bbox,
self.psf.computeBBox()
)

def testResized(self):
with self.assertRaises(NotImplementedError):
self.psf.resized(9, 9)

def testClone(self):
clone1 = deepcopy(self.psf)
clone2 = self.psf.clone()
for clone in [clone1, clone2]:
self.assertIsNot(clone, self.psf)
self.assertImagesEqual(
clone.computeImage(),
self.psf.computeImage()
)
self.assertEqual(
clone.computeApertureFlux(0.5),
self.psf.computeApertureFlux(0.5)
)
self.assertEqual(
clone.computeShape(),
self.psf.computeShape()
)


class MemoryTester(lsst.utils.tests.MemoryTestCase):
pass


def setup_module(module):
lsst.utils.tests.init()


if __name__ == "__main__":
lsst.utils.tests.init()
unittest.main()