New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DM-13065: SipApproximation #303
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to postpone the rest of the review until I can see DM-13156 and DM-13157 in JIRA (which is down at the moment).
|
||
// Because binomial coefficients only depend on two ints, and we compute | ||
// them via a recurrence relation, we actually store all of the ones we | ||
// have every calculated in a static matrix. We expand that matrix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
every -> ever
* A basis concept for 1-d series expansions. | ||
* | ||
* @note This class is only present in the documentation, as it represents an | ||
* abstract concept. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have never seen this done before. Clearly you could make this a real class by using template parameters. I assume you are trying to avoid inheritance, as per your documentation in polynomials.h ("the classes... are not polymorphic (no virtual functions)"). A few more words of explanation here would be appreciated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concepts are very much a part of C++ - they're not in the language yet, but they've been a part of how people think about the language since the inception of the STL (see, e.g. http://en.cppreference.com/w/cpp/concept). Sometimes people try to implement those with a templated base class (i.e. CRTP, as in the Eigen class hierarchies), and I tried that a bit here, but I ultimately rejected that approach because I felt the associated boilerplate obfuscated things more than it helped.
But this way of documenting a Concept by telling Doxygen that it's a class is something new; it seemed the best way to provide a separate documentation page with the right sections that had more-or-less the right relationship to the concrete classes that implement the Concept. I'm pretty happy with the result, but documentation is best judged by someone other than the author and I'm open to other ways of documenting this. Given how focused it is on C++, it's a bit strange that Doxygen doesn't include something native for Concepts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few words in the documentation would be helpful -- at least a link to the cppreference page you site. I had no idea the word "concept" was formal in this note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Will do. I'll also capitalize Concept in the docs, which I should have done before.
include/lsst/afw/math/polynomials.h
Outdated
* - PackedIndexIterator and PackedIndexRange, which handle the flattening | ||
* of pairs of 1-d polynomials coefficients and basis functions into 1-d | ||
* arrays. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I strongly suspect you should put this code (including useful functions based on it) in a new package, rather than afw
, since it seems potentially generally useful and has a minimum of dependencies. I'm thinking along the lines of your RFC for using sphgeom and I'd like to avoid a change like that in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working on this was part of the inspiration for that RFC, but because some of this code does depend on afw.geom
it can't be moved out of afw yet, and I'd rather not split it up based on that dependency problem; I'd rather address the dependency problem by moving both the geometry primitives and this code to another package. But since that's a lot more work, I can't make a good case that it's what LSST should spend its time on, and hence the RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More comments. Still working my way through this.
/// Construct workspace for a basis with the given order. | ||
explicit PackedBasisWorkspace2d(std::size_t order) : _x(order + 1), _y(order + 1) {} | ||
|
||
/// Return the maximum order this workspace can support. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is picky, but I suggest "can support" -> "supports". I first misunderstood this as the maximum order you can request for this class, partly because I think I recall reading about other classes in this ticket that are vaguely similar but whose size can be increased if needed.
Also, if the workspace may contain data for a smaller order than the maximum, is it reasonable to call this getMaxOrder
in hopes of perhaps eventually having getOrder
return the current order.
|
||
|
||
/** | ||
* A workspace object that can be used to void extra memory allocations in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void -> avoid
* | ||
* If @f$B_n(x)@f$ are the basis functions for the nested Basis1d, the basis | ||
* functions of a PackedBasis2d with order @f$N@f$ are @f$B_m(x)B_n(y)@f$ for all combinations | ||
* with @f$m + n \le N@f$. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, this gives a linear way of going through x and y coefficients. I know of two standard orders for doing this: x0y0 x1y0 x0y1 x2y0 x1y1 x0y2.... and x0y0 x0y1 x1y0 x0y2 x1y1 x2y0.... I believe we have different bits of code in our stack that uses each of the two orders. The point is: please document which order you are using here, and anywhere else this ambiguity comes up. I hope whatever it is will match the existing FunctionLibrary.h code and will become our de-facto standard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is documented in the classes in PackedIndex.h
; I've added a reference to that documentation here. The convention is the latter of the two examples you gave. I'm not sure what one FunctionLibrary uses, but this is the one used by lsst.shapelet
and (internally) by ChebyshevBoundedField
.
} | ||
|
||
/// Move to the next element in the packed array and return a copy of the iterator before the move. | ||
PackedIndexIterator operator++(int) noexcept { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that pre-increment is pretty much guaranteed to be faster (especially since post-increment calls pre-increment), is it necessary to define post-increment? I'm not complaining about this code (which is nice and simple); it is more of a C++ style question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is necessary to meet the formal requirements of being a conformant STL InputIterator
.
|
||
/** | ||
* Construct a ScaledChebyshev1Basis2d that remaps the given box to [-1, 1]x[-1, 1] before | ||
* evaluating Chebyshev polynomials. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These aren't Chebyshev polynomials, right? I suggest looking for all instances of Chebyshev in this file and the 1d file.
* This operation is not numerically stable at high order, but when fitting | ||
* standard polynomials, it is still much more stable to first fit in a scaled | ||
* basis and then (if necessary) use `simplify` to compute the coefficients | ||
* of an equivalent unscaled polynomial. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I encourage you to try to find a less ambiguous name, such as unscaled
. (Note that Transform supports simplify for a very different purpose.)
It is a pity it is necessary, but I imagine it was needed in order to compute the final SIP coefficients.
5d20fcb
to
11f767f
Compare
11f767f
to
bcf9900
Compare
e268191
to
fd275a6
Compare
@r-owen, I think this is ready for another look. I actually took some time to look more closely into why Also, note that there is an afw PR for this, with mostly obsolete comments dating back to when the polynomial was in afw. Please take a look at that, too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some suggestions to improve interoperability but overall this looks very nice.
Do you anticipate any syntactic sugar for making a WCS from one of these (e.g. a new function)?
* they can generally be closely approximated by standard FITS WCS projection | ||
* with additional SIP distortions. This class fits such an approximation, | ||
* given a TransformPoint2ToPoint2 object that represents the exact mapping from | ||
* pixels to Intermediate World Coordinates with a SIP distortion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outstanding documentation. Very clear and helpful.
* @param[in] pixelToIwc The true Transform to approximate. Should go | ||
* from pixels to Intermediate World Coordinates | ||
* when applyForward is called. | ||
* @param[in] crpix Pixel origin ("CRPIX" in FITS WCS). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please state that crpix
is 0-based (I sure hope it is!). In case it helps SkyWcs::getPixelOrigin()
returns this value and its documentation string is:
``
/**
* Get the pixel origin, in pixels, using the LSST convention
*
* This is CRPIX1 - 1, CRPIX2 -1 in FITS terminology
*/
* @param[in] cd Nominal Jacobian ("CD" in FITS WCS). | ||
* @param[in] bbox Pixel-coordinate bounding box over which the | ||
* approximation should be valid. Used to set up | ||
* the grid of points to fit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider "set up" -> "generate" (or "build" or "construct" or...)
* to data points generated by calls to | ||
* pixelToIwc.applyInverse instead of | ||
* pixelToIwc.applyForward. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm uneasy about this. It's trivial to make a SkyWcs from SIP coefficients (call makeTanSipWcs
, though if you want a version that handles arbitrary projections we would have to add that, it should be trivial). Instead of having this code I suggest you make a WCS that way, extract the pixelToIwc portion by calling getPixelToIntermediateWorldCoords
, and call the regular fitter with the crpix, cd matrix and SIP coefficients you used to make the WCS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this is to be able to test that SipApproximation
internal evaluate identical transformations the same way that SkyWcs
does (which is something that needs to be verified before testing that it evaluates approximation transforms similarly), so I can't really use SkyWcs
as a replacement for this.
Point2D applyInverse(Point2D const & iwcs) const; | ||
|
||
/** | ||
* Convert a point from intermediate world coordinates to pixels. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"a point" -> "an array of points"
* declare smaller singular values zero in the least | ||
* squares solution. Negative values use Eigen's | ||
* internal default. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document behavior for invalid inputs, e.g. order < 0 (if 0 is sensible), gridDimensions <= 1 (especially <= 0) and an empty bbox. I'm hoping you raise an exception, but I suppose an assert would do.
* (respectively). Note that in the common case where the CD matrix | ||
* includes the scaling from angle units to pixel units, the *scaled* | ||
* intermediate world coordinate values are also in (nominal) pixel | ||
* units. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The units "scaled IWC" seem a bit odd. However, I assume this is naturally what comes out of the computation, and if so, I agree it's probably the best choice (especially since in the standard case it'll be in reasonable units).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that's the natural result of the computation, though putting all of the deviations in pixel units was actually the motivation.
SipApproximation( | ||
std::shared_ptr<TransformPoint2ToPoint2> pixelToIwc, | ||
Point2D const & crpix, | ||
LinearTransform const & cd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makeSkyWcs
and makeTanSipWcs
use Eigen::Matrix2d const &cdMatrix
and the latter uses the same type for the SIP coefficients. Please do the same unless there is a very strong reason not to. It would improve interoperability. For instance the user could pass in the output of wcs.getCdMatrix()
as the cd
(or, preferably, cdMatrix
) argument and use the generated SIP directly when building a TAN-SIP WCS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I think we should be probably using LinearTransform
in all of those places, but I'll bow to local precedent here.
double getAP(int p, int q) const; | ||
|
||
/// Return a coefficient of the reverse transform polynomial. | ||
double getBP(int p, int q) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add methods to return the coefficients as Eigen matrices
src/geom/SipApproximation.cc
Outdated
return points; | ||
} | ||
|
||
poly::PolynomialFunction2dYX readFunctionFromCoeffMatrix(ndarray::Array<double const, 2> const & coeffs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add brief documentation of what this does. Why this name instead of, say, "makePolynomial"?
7f661be
to
1d60a55
Compare
afw changes from the last round of review comments are now ready too.
Not immediately; I'm not entirely certain how we'll want to use this class to construct a new WCS in detail - after initializing a |
1d60a55
to
7db4252
Compare
Best to start with the docs for the afw/geom/SipApproximation class and the afw/math/polynomials namespace (in afw/math/polynomials.h) to get the big picture.