590 """Apply brighter fatter correction in place for the image.
594 exposure : `lsst.afw.image.Exposure`
595 Exposure to have brighter-fatter correction applied. Modified
597 kernel : `numpy.ndarray`
598 Brighter-fatter kernel to apply.
600 Number of correction iterations to run.
602 Convergence threshold in terms of the sum of absolute
603 deviations between an iteration and the previous one.
605 If True, then the exposure values are scaled by the gain prior
607 gains : `dict` [`str`, `float`]
608 A dictionary, keyed by amplifier name, of the gains to use.
609 If gains is None, the nominal gains in the amplifier object are used.
614 Final difference between iterations achieved in correction.
616 Number of iterations used to calculate correction.
620 This correction takes a kernel that has been derived from flat
621 field images to redistribute the charge. The gradient of the
622 kernel is the deflection field due to the accumulated charge.
624 Given the original image I(x) and the kernel K(x) we can compute
625 the corrected image Ic(x) using the following equation:
627 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
629 To evaluate the derivative term we expand it as follows:
631 0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y)))
632 + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )
634 Because we use the measured counts instead of the incident counts
635 we apply the correction iteratively to reconstruct the original
636 counts and the correction. We stop iterating when the summed
637 difference between the current corrected image and the one from
638 the previous iteration is below the threshold. We do not require
639 convergence because the number of iterations is too large a
640 computational cost. How we define the threshold still needs to be
641 evaluated, the current default was shown to work reasonably well
642 on a small set of images. For more information on the method see
643 DocuShare Document-19407.
645 The edges as defined by the kernel are not corrected because they
646 have spurious values due to the convolution.
648 image = exposure.getMaskedImage().getImage()
651 with gainContext(exposure, image, applyGain, gains):
653 kLx = np.shape(kernel)[0]
654 kLy = np.shape(kernel)[1]
655 kernelImage = afwImage.ImageD(kLx, kLy)
656 kernelImage.getArray()[:, :] = kernel
657 tempImage = afwImage.ImageD(image, deep=
True)
659 nanIndex = np.isnan(tempImage.getArray())
660 tempImage.getArray()[nanIndex] = 0.
662 corr = np.zeros(image.array.shape, dtype=np.float64)
663 prev_image = np.zeros(image.array.shape, dtype=np.float64)
675 for iteration
in range(maxIter):
677 outArray = scipy.signal.convolve(
683 tmpArray = tempImage.getArray()
685 with np.errstate(invalid=
"ignore", over=
"ignore"):
687 gradTmp = np.gradient(tmpArray[startY:endY, startX:endX])
688 gradOut = np.gradient(outArray[startY:endY, startX:endX])
689 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
692 diffOut20 = np.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
693 diffOut21 = np.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
694 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
696 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
698 tmpArray[:, :] = image.getArray()[:, :]
699 tmpArray[nanIndex] = 0.
700 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
703 diff = np.sum(np.abs(prev_image - tmpArray), dtype=np.float64)
707 prev_image[:, :] = tmpArray[:, :]
709 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
710 corr[startY + 1:endY - 1, startX + 1:endX - 1]
712 return diff, iteration
716 """Take the input convolved deflection potential and the flux array
717 to compute and apply the flux transfer into the correction array.
722 Deflection potential, being the convolution of the flux F with the
725 The array of flux values which act as the source of the flux transfer.
726 correctionMode: `bool`
727 Defines if applying correction (True) or generating sims (False).
735 if cFunc.shape != fStep.shape:
736 raise RuntimeError(f
'transferFlux: array shapes do not match: {cFunc.shape}, {fStep.shape}')
748 corr = np.zeros(cFunc.shape, dtype=np.float64)
751 yDim, xDim = cFunc.shape
752 y = np.arange(yDim, dtype=int)
753 x = np.arange(xDim, dtype=int)
754 xc, yc = np.meshgrid(x, y)
760 diff = np.diff(cFunc, axis=ax)
763 gx = np.zeros(cFunc.shape, dtype=np.float64)
764 yDiff, xDiff = diff.shape
765 gx[:yDiff, :xDiff] += diff
770 for i, sel
in enumerate([gx > 0, gx < 0]):
786 flux = factor * fStep[sel]*gx[sel]
789 flux = factor * fStep[yPix, xPix]*gx[sel]
793 corr[yPix, xPix] += flux
800 gains=None, correctionMode=True):
801 """Apply brighter fatter correction in place for the image.
803 This version presents a modified version of the algorithm
804 found in ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``
805 which conserves the image flux, resulting in improved
806 correction of the cores of stars. The convolution has also been
807 modified to mitigate edge effects.
811 exposure : `lsst.afw.image.Exposure`
812 Exposure to have brighter-fatter correction applied. Modified
814 kernel : `np.ndarray`
815 Brighter-fatter kernel to apply.
817 Number of correction iterations to run.
819 Convergence threshold in terms of the sum of absolute
820 deviations between an iteration and the previous one.
822 If True, then the exposure values are scaled by the gain prior
824 gains : `dict` [`str`, `float`]
825 A dictionary, keyed by amplifier name, of the gains to use.
826 If gains is None, the nominal gains in the amplifier object are used.
827 correctionMode : `Bool`
828 If True (default) the function applies correction for BFE. If False,
829 the code can instead be used to generate a simulation of BFE (sign
830 change in the direction of the effect)
835 Final difference between iterations achieved in correction.
837 Number of iterations used to calculate correction.
841 Modified version of ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``.
843 This correction takes a kernel that has been derived from flat
844 field images to redistribute the charge. The gradient of the
845 kernel is the deflection field due to the accumulated charge.
847 Given the original image I(x) and the kernel K(x) we can compute
848 the corrected image Ic(x) using the following equation:
850 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
852 Improved algorithm at this step applies the divergence theorem to
853 obtain a pixelised correction.
855 Because we use the measured counts instead of the incident counts
856 we apply the correction iteratively to reconstruct the original
857 counts and the correction. We stop iterating when the summed
858 difference between the current corrected image and the one from
859 the previous iteration is below the threshold. We do not require
860 convergence because the number of iterations is too large a
861 computational cost. How we define the threshold still needs to be
862 evaluated, the current default was shown to work reasonably well
863 on a small set of images.
865 Edges are handled in the convolution by padding. This is still not
866 a physical model for the edge, but avoids discontinuity in the correction.
868 Author of modified version: Lance.Miller@physics.ox.ac.uk
871 image = exposure.getMaskedImage().getImage()
874 with gainContext(exposure, image, applyGain, gains):
877 kLy, kLx = kernel.shape
878 kernelImage = afwImage.ImageD(kLx, kLy)
879 kernelImage.getArray()[:, :] = kernel
880 tempImage = afwImage.ImageD(image, deep=
True)
882 nanIndex = np.isnan(tempImage.getArray())
883 tempImage.getArray()[nanIndex] = 0.
885 outImage = afwImage.ImageD(image.getDimensions())
886 corr = np.zeros(image.array.shape, dtype=np.float64)
887 prevImage = np.zeros(image.array.shape, dtype=np.float64)
888 convCntrl = afwMath.ConvolutionControl(
False,
False, 1)
889 fixedKernel = afwMath.FixedKernel(kernelImage)
893 kLy = 2 * ((1+kLy)//2)
894 kLx = 2 * ((1+kLx)//2)
901 imYdimension, imXdimension = tempImage.array.shape
902 imean = np.mean(tempImage.getArray()[~nanIndex], dtype=np.float64)
905 tempImage.array[nanIndex] = 0.0
906 padArray = np.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
907 outImage = afwImage.ImageD(np.pad(outImage.getArray(), ((0, kLy), (0, kLx))))
909 padImage = afwImage.ImageD(padArray.shape[1], padArray.shape[0])
910 padImage.array[:] = padArray
912 for iteration
in range(maxIter):
916 afwMath.convolve(outImage, padImage, fixedKernel, convCntrl)
917 tmpArray = tempImage.getArray()
918 outArray = outImage.getArray()
921 outArray = outArray[:imYdimension, :imXdimension]
924 corr[...] =
transferFlux(outArray, tmpArray, correctionMode=correctionMode)
927 tmpArray[:, :] = image.getArray()[:, :]
929 tmpArray[nanIndex] = 0.
933 tempImage.array[nanIndex] = 0.
934 padArray = np.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
937 diff = np.sum(np.abs(prevImage - tmpArray), dtype=np.float64)
941 prevImage[:, :] = tmpArray[:, :]
943 image.getArray()[:] += corr[:]
945 return diff, iteration