|
45 | 45 | sigma2fwhm = 2.*np.sqrt(2.*np.log(2.)) |
46 | 46 |
|
47 | 47 |
|
| 48 | +def _safeEstimateRoughShape(psf, position, jitter=2.0, maxTries=5, log=None): |
| 49 | + |
| 50 | + """ |
| 51 | + Try to compute the shape of `psf` at `position`. |
| 52 | +
|
| 53 | + If it fails, perturb the position until success or maxTries. |
| 54 | +
|
| 55 | + Parameters |
| 56 | + ---------- |
| 57 | + psf : `lsst.afw.detection.Psf` |
| 58 | + The PSF object. |
| 59 | + position : `lsst.geom.Point2D` |
| 60 | + The nominal position at which to evaluate the PSF shape. |
| 61 | + jitter : `float` |
| 62 | + Size of random offset in pixels to try when retrying. |
| 63 | + maxTries : `int` |
| 64 | + Maximum number of attempts (including the original). |
| 65 | + log : `lsst.utils.logging.LsstLogAdapter` optional |
| 66 | + logger to use for logging retries. |
| 67 | +
|
| 68 | + Returns |
| 69 | + ------- |
| 70 | + shape : lsst.afw.geom.ellipses.Quadrupole |
| 71 | + The shape of the PSF at the (possibly perturbed) position. |
| 72 | + """ |
| 73 | + try: |
| 74 | + return psf.computeShape(position) |
| 75 | + except pexExceptions.Exception as err: |
| 76 | + if log: |
| 77 | + log.info(f"safeEstimateRoughShape: initial evaluation of PSF failed with message: {str(err)}." |
| 78 | + " Retrying nearby") |
| 79 | + firstErr = err |
| 80 | + |
| 81 | + # random offsets must be deterministic |
| 82 | + offsets = np.random.default_rng(seed=40).uniform(-jitter, jitter, size=(maxTries - 1, 2)) |
| 83 | + candidates = [position + geom.Extent2D(dx, dy) for dx, dy in offsets] |
| 84 | + for i, candidate in enumerate(candidates): |
| 85 | + try: |
| 86 | + shape = psf.computeShape(candidate) |
| 87 | + if log: |
| 88 | + log.info(f"safeEstimateRoughShape succeeded on try {i + 2} at position {candidate}") |
| 89 | + return shape |
| 90 | + except pexExceptions.Exception: |
| 91 | + continue |
| 92 | + |
| 93 | + # If nothing worked, raise AlgorithmError from original position |
| 94 | + raise PsfComputeShapeError(position) from firstErr |
| 95 | + |
| 96 | + |
48 | 97 | def nextOddInteger(x): |
49 | 98 | nextInt = int(np.ceil(x)) |
50 | 99 | return nextInt + 1 if nextInt%2 == 0 else nextInt |
@@ -175,15 +224,12 @@ def run(self, exposure, referencePsfModel, kernelSum=1.0): |
175 | 224 | # exposure's bounding box in DM-32756. |
176 | 225 | sciAvgPos = exposure.getPsf().getAveragePosition() |
177 | 226 | modelAvgPos = referencePsfModel.getAveragePosition() |
178 | | - try: |
179 | | - fwhmScience = exposure.getPsf().computeShape(sciAvgPos).getDeterminantRadius()*sigma2fwhm |
180 | | - except pexExceptions.RangeError as err: |
181 | | - raise WarpedPsfTransformTooBigError( |
182 | | - f"Unable to compute the FWHM of the science Psf at {sciAvgPos}" |
183 | | - "due to an unexpectedly large transform." |
184 | | - ) from err |
185 | | - except pexExceptions.Exception as err: |
186 | | - raise PsfComputeShapeError(sciAvgPos) from err |
| 227 | + |
| 228 | + # TODO If DM-52537 eliminates the need for _safeEstimateRoughShape, |
| 229 | + # the remove it, and replace next line with a direct call to |
| 230 | + # computeShape in a try/except block. |
| 231 | + fwhmScience = _safeEstimateRoughShape(exposure.getPsf(), sciAvgPos, |
| 232 | + log=self.log).getDeterminantRadius()*sigma2fwhm |
187 | 233 | fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm |
188 | 234 |
|
189 | 235 | basisList = makeKernelBasisList(self.kConfig, fwhmScience, fwhmModel, metadata=self.metadata) |
|
0 commit comments