656):
657 """Compute the delta between the maximum and minimum model PSF trace radius
658 values evaluated on a grid of points lying in the unmasked region of the
659 image.
660
661 Parameters
662 ----------
663 image_mask : `lsst.afw.image.Mask`
664 The mask plane associated with the exposure.
665 image_psf : `lsst.afw.detection.Psf`
666 The PSF model associated with the exposure.
667 sampling : `int`
668 Sampling rate in each dimension to create the grid of points at which
669 to evaluate ``image_psf``s trace radius value. The tradeoff is between
670 adequate sampling versus speed.
671 bad_mask_bits : `list` [`str`]
672 Mask bits required to be absent for a pixel to be considered
673 "unmasked".
674
675 Returns
676 -------
677 psf_trace_radius_delta : `float`
678 The delta (in pixels) between the maximum and minimum model PSF trace
679 radius values evaluated on the x,y-grid subsampled on the unmasked
680 detector pixels by a factor of ``sampling``. If any model PSF trace
681 radius value on the grid evaluates to NaN, then NaN is returned
682 immediately.
683 """
684 psf_trace_radius_list = []
685 mask_arr = image_mask.array[::sampling, ::sampling]
686 bitmask = image_mask.getPlaneBitMask(bad_mask_bits)
687 good = ((mask_arr & bitmask) == 0)
688
689 x = np.arange(good.shape[1]) * sampling
690 y = np.arange(good.shape[0]) * sampling
691 xx, yy = np.meshgrid(x, y)
692
693 for x_mesh, y_mesh, good_mesh in zip(xx, yy, good):
694 for x_point, y_point, is_good in zip(x_mesh, y_mesh, good_mesh):
695 if is_good:
696 psf_trace_radius = image_psf.computeShape(
geom.Point2D(x_point, y_point)).getTraceRadius()
697 if ~np.isfinite(psf_trace_radius):
698 return float("nan")
699 psf_trace_radius_list.append(psf_trace_radius)
700
701 psf_trace_radius_delta = np.max(psf_trace_radius_list) - np.min(psf_trace_radius_list)
702
703 return psf_trace_radius_delta