-
Notifications
You must be signed in to change notification settings - Fork 8
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-46852: Make detectAndMeasure robust to invalid PSF sigma #352
Conversation
f3c8950
to
42483b5
Compare
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 not a fan of using a default psf size, since that could produce secretly bad results, without any obvious way to know that they are bad. UpstreamFailureNoWorkFound
was made for this case.
sigma = self.config.fallbackPsfSigma | ||
self.log.warning("Could not determine average width of the difference image PSF. " | ||
"Using the default sigma of %d", self.config.fallbackPsfSigma) |
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.
Why can't we make this be an UpstreamFailureNoWorkFound
, and do it as the first step of this method? See DM-46948 for details, but this case is exactly what that exception was made for.
Not having a useable PSF on the diffim seems like a totally reasonable failure condition. I don't like using some default psf size, since that will result in unreliable measurements and we're not doing anything to mark the output catalog as being untrustworthy.
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 went back and forth on this, so I am fine checking for a valid PSF here and raising an error. I will look into UpstreamFailureNoWorkFound
clearMaskPlanes = lsst.pex.config.ListField( | ||
dtype=str, | ||
doc="Mask planes to clear before running detection.", | ||
default=("DETECTED", "DETECTED_NEGATIVE", "NOT_DEBLENDED", "STREAK"), |
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.
Just to check: these fields are set because they were propagated from the science image?
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.
Correct
mask.addMaskPlane(mp) | ||
mask &= ~mask.getPlaneBitMask(clearMaskPlanes) | ||
sigma = self._prepareInputs(difference) | ||
self.log.info("Using PSF sigma of %f to smooth the difference image for detection.", 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.
This new log is worth keeping, but I think it should go into _prepareInputs
, since you forgot to put this log after the other place and there's no need to pass sigma
to detection since it will get it from the (now guaranteed valid) psf.
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 purposely left it out of _prepareInputs
, because that is used by both regular diffim and preconvolution where it is incorrect (since we don't smooth the difference image for detection 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.
Hmmm... rethinking whether the log is worth it. Returning something just to emit a log message isn't the best. Maybe that log isn't actually necessary if we only ever used the included PSF? What would be the use case of such a log message?
@@ -274,7 +274,7 @@ def run(self, scienceExposure, templateExposure, subtractedExposure, psfMatching | |||
corr = self.computeDiffimCorrection(kArr, varianceMean, targetVarianceMean) | |||
|
|||
correctedImage = self.computeCorrectedImage(corr.corrft, subtractedExposure.image.array) | |||
correctedPsf = self.computeCorrectedDiffimPsf(corr.corrft, psfImg.array) | |||
# correctedPsf = self.computeCorrectedDiffimPsf(corr.corrft, psfImg.array) |
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, and the other commented line below, should either be deleted, or have a TODO attached to them with the ticket number where they will be restored (or deleted).
sigma : `float` | ||
The width of the PSF of the difference image. |
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.
As noted elsewhere, I think you can remove this return if you just log that info message at the end of this method.
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.
Oh, and please add a test case with a garbage PSF, to check that the correct exception is raised.
Relatedly: where does the psf==None
case get tested for and rejected?
108bbb1
to
d5b8666
Compare
I removed the default PSF size and the related sigma logging, and instead raise an error if the width is invalid. There is a new unit test for that case, as well. There are no unit tests of |
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.
Handful of new questions, but this is better, thanks.
Raises | ||
------ | ||
pipeBase.UpstreamFailureNoWorkFound | ||
If the PSF is not usable for measurement |
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 the PSF is not usable for measurement | |
If the PSF is not usable for measurement. |
|
||
Raises | ||
------ | ||
pipeBase.UpstreamFailureNoWorkFound |
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 this has to be like this in docs?
pipeBase.UpstreamFailureNoWorkFound | |
lsst.pipe.base.UpstreamFailureNoWorkFound |
if np.isnan(sigma): | ||
raise pipeBase.UpstreamFailureNoWorkFound("Invalid PSF detected! PSF width evaluates to NaN.") |
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'd put this as the first line of the method, since there's no point in doing the rest if this is raised.
tests/test_detectAndMeasure.py
Outdated
# Run detection and check the results | ||
with self.assertRaises(UpstreamFailureNoWorkFound): | ||
detectionTask.run(science, matchedTemplate, difference, | ||
idFactory=self.idGenerator.make_table_id_factory()) |
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.
No need to pass an idFactory unless you specifically care about the ids that are returned (which there won't be, since it raised).
tests/test_detectAndMeasure.py
Outdated
psfNew = rng.randn(psfShape[1], psfShape[0]) | ||
psfcI.array = psfNew |
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 guaranteed to always make a psf that will fail the tests in the Task? Would setting all the pixels to zero or -1 be just as good, and more reliable? I know you set the seed above, but I don't think that's guaranteed to produce the same results across all architectures, is it?
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 can't build a kernel with all zeros, and setting to a constant value doesn't actually raise an error. Running several different noise realizations reveals that that sometimes does result in a kernel that has a valid sigma and doesn't raise. I will instead build a fixed kernel I know should raise.
d5b8666
to
e136d38
Compare
psfNew = np.zeros(psfShape) | ||
psfNew[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.
Oh, yes, that's nice and pathological!
This is not implemented correctly, and raises occasional errors.
e136d38
to
b991cbf
Compare
No description provided.