Skip to content
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

Merged
merged 3 commits into from
Nov 9, 2024

Conversation

isullivan
Copy link
Contributor

No description provided.

Copy link
Contributor

@parejkoj parejkoj left a 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.

Comment on lines 398 to 400
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)
Copy link
Contributor

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.

Copy link
Contributor Author

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"),
Copy link
Contributor

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?

Copy link
Contributor Author

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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)

Copy link
Contributor

@parejkoj parejkoj Nov 7, 2024

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)
Copy link
Contributor

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).

Comment on lines 387 to 388
sigma : `float`
The width of the PSF of the difference image.
Copy link
Contributor

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.

Copy link
Contributor

@parejkoj parejkoj left a 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?

@isullivan isullivan force-pushed the tickets/DM-46852 branch 2 times, most recently from 108bbb1 to d5b8666 Compare November 8, 2024 01:29
@isullivan
Copy link
Contributor Author

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 subtractImages._applyExternalCalibrations, which is where a test of the case of the external PSF being None would be relevant. Adding that test seems beyond the scope of this ticket.

Copy link
Contributor

@parejkoj parejkoj left a 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If the PSF is not usable for measurement
If the PSF is not usable for measurement.


Raises
------
pipeBase.UpstreamFailureNoWorkFound
Copy link
Contributor

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?

Suggested change
pipeBase.UpstreamFailureNoWorkFound
lsst.pipe.base.UpstreamFailureNoWorkFound

Comment on lines 389 to 390
if np.isnan(sigma):
raise pipeBase.UpstreamFailureNoWorkFound("Invalid PSF detected! PSF width evaluates to NaN.")
Copy link
Contributor

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.

# Run detection and check the results
with self.assertRaises(UpstreamFailureNoWorkFound):
detectionTask.run(science, matchedTemplate, difference,
idFactory=self.idGenerator.make_table_id_factory())
Copy link
Contributor

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).

Comment on lines 182 to 183
psfNew = rng.randn(psfShape[1], psfShape[0])
psfcI.array = psfNew
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Comment on lines +181 to +182
psfNew = np.zeros(psfShape)
psfNew[0, :] = 1
Copy link
Contributor

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!

@isullivan isullivan merged commit d3624f5 into main Nov 9, 2024
2 checks passed
@isullivan isullivan deleted the tickets/DM-46852 branch November 9, 2024 03:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants