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-14786 implement ConstrainedMagnitudeModel #102
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.
My main comment is that there is a lot of copy+pasted code between the FluxModel and MagnitudeModel versions of classes that appears to be easily avoidable in Python and somewhat less so in C++. I don't see a compelling reason for the repeated code as it is. Everything else is either minor or a request for clarification of something that I've likely misunderstood.
|
||
/// @copydoc PhotometryModel::offsetFittedStar | ||
void offsetFittedStar(FittedStar &fittedStar, double delta) const override { | ||
fittedStar.getFlux() -= delta; |
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 realize that this is the same behaviour as in the non-constrained version but subtracting delta instead of adding it is counterintuitive to me. I would expect a method offsetting x by delta to add delta and nothing in the copied docs suggests that the offset is negative, so perhaps the docs should be updated.
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 catch. I have some notes about it in tests, but it doesn't say it in any of the model docs. I've updated the docs.
|
||
/// @copydoc PhotometryModel::computeResidual | ||
double computeResidual(CcdImage const &ccdImage, MeasuredStar const &measuredStar) const override { | ||
return transform(ccdImage, measuredStar) - measuredStar.getFittedStar()->getFlux(); |
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.
Although it's a one-liner, this seems like more than a simple accessor and shouldn't be inlined.
* @note This method takes instFlux and instFluxErr: the error calculation has to | ||
* use fluxes to get the math right. | ||
*/ | ||
double transformError(MeasuredStar const &measuredStar, double value, double valueErr) const override; |
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.
You could rename the value and valueErr arguments to instFlux and instFluxErr if it only works properly that way. I don't think the style guide forbids this, although I guess you wouldn't be able to copy the docs then.
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 think I'd rather avoid duplicating docs when possible and clarifying as necessary (as above). The SimpleMagnitudeModel
does take mag
and magErr
, whereas the two FluxModel
s take flux
and fluxErr
, hence my choice of value
and valueErr
.
python/lsst/jointcal/jointcal.py
Outdated
@@ -624,7 +635,7 @@ def _fit_photometry(self, associations, dataName=None): | |||
# The constrained model needs the visit transfo fit first; the chip | |||
# transfo is initialized from the singleFrame PhotoCalib, so it's close. | |||
dumpMatrixFile = "photometry_preinit" if self.config.writeInitMatrix else "" | |||
if self.config.photometryModel == "constrained": | |||
if "constrained" in self.config.photometryModel: |
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 self.config.photometryModel.startswith("constrained")
src/ConstrainedPhotometryModel.cc
Outdated
afw::geom::Box2D const &focalPlaneBBox, int visitOrder) | ||
: ConstrainedPhotometryModel(ccdImageList, focalPlaneBBox, visitOrder) { | ||
// keep track of which chip we want to constrain (the one closest to the middle of the focal plane) | ||
double minRadius2 = std::numeric_limits<double>::infinity(); |
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 code is almost identical to the ConstrainedFluxModel constructor; the only differences are the type of chipTransfo and a few shared vs unique pointers. Can you not find a better way to share the code? For example:
- template the entire class, e.g. with a bool isFluxModel or similar (probably too much work),
- split it into a function that takes a visitMap, chipMap, chipVisitMap and is templated by the transform type
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've figured out a templated version that seems to do the trick.
tests/test_photometryModel.py
Outdated
self.assertEqual(index, expect) | ||
|
||
|
||
class ConstrainedFluxModelTestCase(ConstrainedPhotometryModelTestCase, lsst.utils.tests.TestCase): | ||
def setUp(self): |
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.
Again, this should definitely be shared between Flux and Magnitude, and it seems very easy to do since there's already a useMagnitude property in the MagnitudeModelTestCase.
tests/test_photometryTransfo.py
Outdated
expect1 = np.zeros((self.order1+1, self.order1+1), dtype=float) | ||
|
||
expect2 = np.zeros((self.order1+1, self.order1+1), dtype=float) | ||
expect2[0, 0] = -1 |
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.
Besides the usual suggestion to reuse code as above, it would be helpful to add a comment explaining why expect1[0,0] and expect2[0,0] are 0, -1 in the Magnitude case.
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've restructured it to make it obvious.
python/lsst/jointcal/jointcal.py
Outdated
r = fit.minimize(whatToFit, 5) # outliers removal at 5 sigma. | ||
chi2 = fit.computeChi2() | ||
r = fitter.minimize(whatToFit, 5) # outliers removal at 5 sigma. | ||
chi2 = fitter.computeChi2() | ||
self.log.info("Fit completed with: %s", str(chi2)) | ||
break | ||
elif r == MinimizeResult.Chi2Increased: | ||
self.log.warn("still some ouliers but chi2 increases - retry") |
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.
ouliers -> outliers
python/lsst/jointcal/jointcal.py
Outdated
RuntimeError | ||
Raised if the fitter fails for some other reason; | ||
log messages will provide further details. | ||
""" | ||
|
||
dumpMatrixFile = "%s_postinit" % name if self.config.writeInitMatrix else "" | ||
for i in range(max_steps): | ||
# outlier removal at 5 sigma. |
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.
Isn't it outlier rejection at self.config.outlierRejectSigma sigma, which may not be 5? I see that it's set to 4 for CFHT and DECam.
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've removed the comment: it's unnecessary.
python/lsst/jointcal/jointcal.py
Outdated
self.log.info(str(chi2)) | ||
if r == MinimizeResult.Converged: | ||
if doRankUpdate: | ||
self.log.debug("fit has converged - no more outliers - redo minimization " | ||
"one more time in case we have lost accuracy in rank update.") | ||
# Redo minimization one more time in case we have lost accuracy in rank update | ||
r = fit.minimize(whatToFit, 5) # outliers removal at 5 sigma. | ||
chi2 = fit.computeChi2() | ||
r = fitter.minimize(whatToFit, 5) # outliers removal at 5 sigma. |
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.
Is this really meant to be hardcoded to 5? I would have expected it to use self.config.outlierRejectSigma, otherwise that should be named something like outlierRejectSigmaInitial.
3893824
to
7353d3d
Compare
Several classes were split up to make this possible: * ConstrainedPhotometryModel * ChipVisitPhotometryMapping * PhotometryTransfoChebyshev * tests were refactored to test the new classes. * minimized duplicate code between the flux/magnitude versions of the above. Fix constrained model initialization step to apply to both flux and magnitude. Revert chi2 value in cfht lineSearch test that came from me not fixing this in DM-14574 when I changed "constrained"->"constrainedFlux". Add missing overrides to SimplePhotometryModel. Cleanup some docstrings and variable names for consistence between flux and magnitude-based models.
Nonfinite chi2 is now discovered in minimize() and we abort outlier rejection and dump the chi2 meas/ref files to aid in debugging. Add docstring to jointcal._iterate_fit()
This tests for an error in construction that I introduced while cleaning up duplicate code: the magnitude and flux versions are almost identical, except for values of the chip transfos: one is just the photoCalib mean, the other is the log() of that.
7353d3d
to
b240722
Compare
No description provided.