27 #include "boost/math/constants/constants.hpp"
28 #include "lsst/pex/exceptions.h"
29 #include "lsst/afw/geom/Point.h"
30 #include "lsst/afw/geom/Box.h"
31 #include "lsst/afw/geom/SpanSet.h"
32 #include "lsst/afw/image/Exposure.h"
33 #include "lsst/afw/table/Source.h"
34 #include "lsst/afw/math/Integrate.h"
35 #include "lsst/afw/math/FunctionLibrary.h"
36 #include "lsst/afw/math/KernelFunctions.h"
37 #include "lsst/afw/detection/Psf.h"
38 #include "lsst/afw/coord/Coord.h"
39 #include "lsst/afw/geom/AffineTransform.h"
40 #include "lsst/afw/geom/ellipses.h"
41 #include "lsst/meas/base.h"
42 #include "lsst/meas/base/ApertureFlux.h"
48 namespace extensions {
49 namespace photometryKron {
52 base::FlagDefinitionList flagDefinitions;
55 base::FlagDefinition
const KronFluxAlgorithm::FAILURE = flagDefinitions.addFailureFlag(
"general failure flag, set if anything went wrong");
56 base::FlagDefinition
const KronFluxAlgorithm::EDGE = flagDefinitions.add(
"flag_edge",
"bad measurement due to image edge");
63 base::FlagDefinition
const KronFluxAlgorithm::SMALL_RADIUS = flagDefinitions.add(
"flag_small_radius",
"measured Kron radius was smaller than that of the PSF");
64 base::FlagDefinition
const KronFluxAlgorithm::BAD_SHAPE = flagDefinitions.add(
"flag_bad_shape",
"shape for measuring Kron radius is bad; used PSF shape");
67 return flagDefinitions;
75 template <
typename MaskedImageT>
78 explicit FootprintFlux() :
_sum(0.0),
_sumVar(0.0) {}
84 void reset(afw::detection::Footprint
const&) {}
87 void operator()(afw::geom::Point2I
const & pos,
88 typename MaskedImageT::Image::Pixel
const & ival,
89 typename MaskedImageT::Variance::Pixel
const & vval) {
95 double getSum()
const {
return _sum; }
98 double getSumVar()
const {
return _sumVar; }
116 template <
typename MaskedImageT,
typename WeightImageT>
117 class FootprintFindMoment {
119 FootprintFindMoment(MaskedImageT
const& mimage,
120 afw::geom::Point2D
const& center,
123 ) :
_xcen(center.getX()),
_ycen(center.getY()),
136 void reset(afw::detection::Footprint
const& foot) {
142 MaskedImageT
const& mimage = this->getImage();
143 afw::geom::Box2I
const& bbox(foot.getBBox());
144 int const x0 = bbox.getMinX(), y0 = bbox.getMinY(), x1 = bbox.getMaxX(), y1 = bbox.getMaxY();
147 x1 >=
_imageX0 + mimage.getWidth() || y1 >=
_imageY0 + mimage.getHeight()) {
148 throw LSST_EXCEPT(lsst::pex::exceptions::OutOfRangeError,
149 (boost::format(
"Footprint %d,%d--%d,%d doesn't fit in image %d,%d--%d,%d")
158 void operator()(afw::geom::Point2I
const & pos,
typename MaskedImageT::Image::Pixel
const & ival) {
159 double x =
static_cast<double>(pos.getX());
160 double y =
static_cast<double>(pos.getY());
161 double const dx = x -
_xcen;
162 double const dy = y -
_ycen;
164 double const dv = -dx*_sinTheta + dy*
_cosTheta;
166 double r = ::hypot(du, dv*
_ab);
168 if (::hypot(dx, dy) < 0.5) {
180 double const eR = 0.38259771140356325;
181 r = ::hypot(r, eR*(1 + ::hypot(dx, dy)/afw::geom::ROOT2));
188 typename MaskedImageT::Variance::Pixel vval = iloc.variance(0, 0);
190 _sumRVar += r*r*vval;
195 double getIr()
const {
return _sumR/
_sum; }
204 bool getGood()
const {
return _sum > 0 &&
_sumR > 0; }
223 afw::geom::ellipses::Axes
const& shape,
224 afw::geom::LinearTransform
const& transformation,
228 afw::geom::ellipses::Axes axes(shape);
229 axes.scale(radius/axes.getDeterminantRadius());
230 return axes.transform(transformation);
233 template<
typename ImageT>
236 afw::geom::ellipses::Axes axes,
237 afw::geom::Point2D const& center,
244 double const sigma = ctrl.smoothingSigma;
245 bool const smoothImage = sigma > 0;
246 int kSize = smoothImage ? 2*int(2*sigma) + 1 : 1;
247 afw::math::GaussianFunction1<afw::math::Kernel::Pixel> gaussFunc(smoothImage ? sigma : 100);
248 afw::math::SeparableKernel kernel(kSize, kSize, gaussFunc, gaussFunc);
249 bool const doNormalize =
true, doCopyEdge =
false;
250 afw::math::ConvolutionControl convCtrl(doNormalize, doCopyEdge);
251 double radius0 = axes.getDeterminantRadius();
252 double radius = std::numeric_limits<double>::quiet_NaN();
253 float radiusForRadius = std::nanf(
"");
254 for (
int i = 0; i < ctrl.nIterForRadius; ++i) {
255 axes.scale(ctrl.nSigmaForRadius);
256 radiusForRadius = axes.getDeterminantRadius();
260 afw::detection::Footprint foot(afw::geom::SpanSet::fromShape(
261 afw::geom::ellipses::Ellipse(axes, center)));
262 afw::geom::Box2I bbox = !smoothImage ?
264 kernel.growBBox(foot.getBBox());
265 bbox.clip(image.getBBox());
266 ImageT subImage(image, bbox, afw::image::PARENT, smoothImage);
268 afw::math::convolve(subImage, ImageT(image, bbox, afw::image::PARENT,
false), kernel, convCtrl);
273 FootprintFindMoment<ImageT, afw::detection::Psf::Image> iRFunctor(
274 subImage, center, axes.getA()/axes.getB(), axes.getTheta()
278 foot.getSpans()->applyFunctor(
279 iRFunctor, *(subImage.getImage()));
280 }
catch(lsst::pex::exceptions::OutOfRangeError &e) {
282 LSST_EXCEPT_ADD(e,
"Determining Kron aperture");
287 if (!iRFunctor.getGood()) {
291 radius = iRFunctor.getIr()*sqrt(axes.getB()/axes.getA());
292 if (radius <= radius0) {
297 axes.scale(radius/axes.getDeterminantRadius());
301 return std::make_shared<KronAperture>(center, axes, radiusForRadius);
305 template<
typename ImageT>
308 afw::geom::ellipses::Ellipse
const& aperture,
309 double const maxSincRadius
312 afw::geom::ellipses::Axes
const& axes = aperture.getCore();
313 if (axes.getB() > maxSincRadius) {
314 FootprintFlux<ImageT> fluxFunctor;
315 auto spans = afw::geom::SpanSet::fromShape(aperture);
317 fluxFunctor, *(image.getImage()), *(image.getVariance()));
318 return std::make_pair(fluxFunctor.getSum(), ::sqrt(fluxFunctor.getSumVar()));
321 base::ApertureFluxResult fluxResult = base::ApertureFluxAlgorithm::computeSincFlux<float>(image, aperture);
322 return std::make_pair(fluxResult.flux, fluxResult.fluxSigma);
323 }
catch(pex::exceptions::LengthError &e) {
324 LSST_EXCEPT_ADD(e, (boost::format(
"Measuring Kron flux for object at (%.3f, %.3f);"
325 " aperture radius %g,%g theta %g")
326 % aperture.getCenter().getX() % aperture.getCenter().getY()
327 % axes.getA() % axes.getB() % afw::geom::radToDeg(axes.getTheta())).str());
334 CONST_PTR(afw::detection::Psf)
const& psf,
335 afw::geom::Point2D
const& center,
336 double smoothingSigma=0.0
340 double const radius = psf->computeShape(center).getDeterminantRadius();
342 return ::sqrt(afw::geom::PI/2)*::hypot(radius, std::max(0.0, smoothingSigma));
345 template<
typename ImageT>
348 double const nRadiusForFlux,
349 double const maxSincRadius
352 afw::geom::ellipses::Axes axes(
getAxes());
353 axes.scale(nRadiusForFlux);
354 afw::geom::ellipses::Ellipse
const ellip(axes,
getCenter());
356 return photometer(image, ellip, maxSincRadius);
368 std::string
const &
name,
369 afw::table::Schema & schema,
370 daf::base::PropertySet & metadata
374 meas::base::FluxResultKey::addFields(schema, name,
"flux from Kron Flux algorithm")
376 _radiusKey(schema.addField<float>(name +
"_radius",
"Kron radius (sqrt(a*b))")),
377 _radiusForRadiusKey(schema.addField<float>(name +
"_radius_for_radius",
378 "radius used to estimate <radius> (sqrt(a*b))")),
379 _psfRadiusKey(schema.addField<float>(name +
"_psf_radius",
"Radius of PSF")),
380 _centroidExtractor(schema, name, true)
382 _flagHandler = meas::base::FlagHandler::addFields(schema, name,
getFlagDefinitions());
387 afw::table::SourceRecord & measRecord,
388 meas::base::MeasurementError * error
390 _flagHandler.handleFailure(measRecord, error);
393 void KronFluxAlgorithm::_applyAperture(
394 afw::table::SourceRecord & source,
395 afw::image::Exposure<float>
const& exposure,
399 double const rad = aperture.
getAxes().getDeterminantRadius();
400 if (rad < std::numeric_limits<double>::epsilon()) {
402 meas::base::MeasurementError,
408 std::pair<double, double> result;
411 }
catch (pex::exceptions::LengthError
const& e) {
414 meas::base::MeasurementError,
418 }
catch(lsst::pex::exceptions::OutOfRangeError &e) {
420 meas::base::MeasurementError,
427 meas::base::FluxResult fluxResult;
428 fluxResult.flux = result.first;
429 fluxResult.fluxSigma = result.second;
430 source.set(_fluxResultKey, fluxResult);
431 source.set(_radiusKey, aperture.
getAxes().getDeterminantRadius());
437 void KronFluxAlgorithm::_applyForced(
438 afw::table::SourceRecord & source,
439 afw::image::Exposure<float>
const & exposure,
440 afw::geom::Point2D
const & center,
441 afw::table::SourceRecord
const & reference,
442 afw::geom::AffineTransform
const & refToMeas
445 float const radius = reference.get(reference.getSchema().find<
float>(_ctrl.
refRadiusName).key);
446 KronAperture
const aperture(reference, refToMeas, radius);
447 _applyAperture(source, exposure, aperture);
448 if (exposure.getPsf()) {
454 afw::table::SourceRecord & source,
455 afw::image::Exposure<float>
const& exposure
457 afw::geom::Point2D center = _centroidExtractor(source, _flagHandler);
463 afw::image::MaskedImage<float>
const& mimage = exposure.getMaskedImage();
466 if (exposure.getPsf()) {
473 afw::geom::ellipses::Axes axes;
474 if (!source.getShapeFlag()) {
475 axes = source.getShape();
478 if (!exposure.getPsf()) {
480 meas::base::MeasurementError,
485 axes = exposure.getPsf()->computeShape();
486 _flagHandler.setValue(source,
BAD_SHAPE.number,
true);
489 afw::geom::ellipses::Axes footprintAxes(source.getFootprint()->getShape());
492 footprintAxes.scale(::sqrt(2));
494 double radius0 = axes.getDeterminantRadius();
495 double const footRadius = footprintAxes.getDeterminantRadius();
499 axes.scale(radius0/axes.getDeterminantRadius());
509 }
catch (pex::exceptions::OutOfRangeError& e) {
512 meas::base::MeasurementError,
518 aperture = _fallbackRadius(source, R_K_psf, e);
519 }
catch(pex::exceptions::Exception& e) {
521 aperture = _fallbackRadius(source, R_K_psf, e);
531 double rad = aperture->
getAxes().getDeterminantRadius();
533 double newRadius = rad;
539 }
else if (!exposure.getPsf()) {
541 meas::base::MeasurementError,
545 }
else if (rad < R_K_psf) {
549 if (newRadius != rad) {
550 aperture->
getAxes().scale(newRadius/rad);
551 _flagHandler.setValue(source,
SMALL_RADIUS.number,
true);
555 _applyAperture(source, exposure, *aperture);
557 source.set(_psfRadiusKey, R_K_psf);
558 if (bad) _flagHandler.setValue(source,
FAILURE.number,
true);
562 afw::table::SourceRecord & measRecord,
563 afw::image::Exposure<float>
const & exposure,
564 afw::table::SourceRecord
const & refRecord,
565 afw::image::Wcs
const & refWcs
567 afw::geom::Point2D center = _centroidExtractor(measRecord, _flagHandler);
568 CONST_PTR(afw::image::Wcs) refWcsPtr(refWcs.clone());
569 afw::image::XYTransformFromWcsPair xytransform(exposure.getWcs(), refWcsPtr);
570 _applyForced(measRecord, exposure, center, refRecord,
571 xytransform.linearizeForwardTransform(refRecord.getCentroid())
578 pex::exceptions::Exception& exc)
const
580 _flagHandler.setValue(source, BAD_RADIUS.number,
true);
582 if (_ctrl.minimumRadius > 0) {
583 newRadius = _ctrl.minimumRadius;
584 _flagHandler.setValue(source, USED_MINIMUM_RADIUS.number,
true);
585 }
else if (R_K_psf > 0) {
587 _flagHandler.setValue(source, USED_PSF_RADIUS.number,
true);
590 meas::base::MeasurementError,
591 NO_FALLBACK_RADIUS.doc,
592 NO_FALLBACK_RADIUS.number
595 PTR(KronAperture) aperture(new KronAperture(source));
596 aperture->getAxes().scale(newRadius/aperture->getAxes().getDeterminantRadius());
601 #define INSTANTIATE(TYPE) \
602 template PTR(KronAperture) KronAperture::determineRadius<afw::image::MaskedImage<TYPE> >( \
603 afw::image::MaskedImage<TYPE> const&, \
604 afw::geom::ellipses::Axes, \
605 afw::geom::Point2D const&, \
606 KronFluxControl const& \
608 template std::pair<double, double> KronAperture::measureFlux<afw::image::MaskedImage<TYPE> >( \
609 afw::image::MaskedImage<TYPE> const&, \
static meas::base::FlagDefinition const SMALL_RADIUS
virtual void measureForced(afw::table::SourceRecord &measRecord, afw::image::Exposure< float > const &exposure, afw::table::SourceRecord const &refRecord, afw::image::Wcs const &refWcs) const
static meas::base::FlagDefinition const NO_MINIMUM_RADIUS
C++ control object for Kron flux.
std::pair< double, double > photometer(ImageT const &image, afw::geom::ellipses::Ellipse const &aperture, double const maxSincRadius)
afw::geom::Point2D const & getCenter() const
double calculatePsfKronRadius(boost::shared_ptr< afw::detection::Psf const > const &psf, afw::geom::Point2D const ¢er, double smoothingSigma=0.0)
double smoothingSigma
"Smooth image with N(0, smoothingSigma^2) Gaussian while estimating R_K" ;
float getRadiusForRadius() const
double minimumRadius
"Minimum Kron radius (if == 0.0 use PSF's Kron radius) if enforceMinimumRadius. " "Also functions as...
virtual void fail(afw::table::SourceRecord &measRecord, meas::base::MeasurementError *error=NULL) const
KronFluxAlgorithm(Control const &ctrl, std::string const &name, afw::table::Schema &schema, daf::base::PropertySet &metadata)
A class that knows how to calculate fluxes using the KRON photometry algorithm.
bool fixed
"if true, use existing shape and centroid measurements instead of fitting" ;
static boost::shared_ptr< KronAperture > determineRadius(ImageT const &image, afw::geom::ellipses::Axes axes, afw::geom::Point2D const ¢er, KronFluxControl const &ctrl)
Determine the Kron Aperture from an image.
static meas::base::FlagDefinition const USED_MINIMUM_RADIUS
static meas::base::FlagDefinition const USED_PSF_RADIUS
static afw::geom::ellipses::Axes getKronAxes(afw::geom::ellipses::Axes const &shape, afw::geom::LinearTransform const &transformation, double const radius)
Determine Kron axes from a reference image.
static meas::base::FlagDefinition const NO_FALLBACK_RADIUS
static meas::base::FlagDefinition const BAD_SHAPE_NO_PSF
double nSigmaForRadius
"Multiplier of rms size for aperture used to initially estimate the Kron radius" ; ...
bool enforceMinimumRadius
"If true check that the Kron radius exceeds some minimum" ;
std::pair< double, double > measureFlux(ImageT const &image, double const nRadiusForFlux, double const maxSincRadius) const
Photometer within the Kron Aperture on an image.
std::string refRadiusName
"Name of field specifying reference Kron radius for forced measurement" ;
static meas::base::FlagDefinition const FAILURE
double maxSincRadius
"Largest aperture for which to use the slow, accurate, sinc aperture code" ;
static meas::base::FlagDefinition const BAD_RADIUS
bool useFootprintRadius
"Use the Footprint size as part of initial estimate of Kron radius" ;
A measurement algorithm that estimates flux using Kron photometry.
static meas::base::FlagDefinition const EDGE
afw::geom::ellipses::Axes & getAxes()
virtual void measure(afw::table::SourceRecord &measRecord, afw::image::Exposure< float > const &exposure) const
double nRadiusForFlux
"Number of Kron radii for Kron flux" ;
#define INSTANTIATE(TYPE)
static meas::base::FlagDefinition const BAD_SHAPE
static meas::base::FlagDefinitionList const & getFlagDefinitions()