LSST Applications g00274db5b6+edbf708997,g00d0e8bbd7+edbf708997,g199a45376c+5137f08352,g1fd858c14a+1d4b6db739,g262e1987ae+f4d9505c4f,g29ae962dfc+7156fb1a53,g2cef7863aa+73c82f25e4,g35bb328faa+edbf708997,g3e17d7035e+5b3adc59f5,g3fd5ace14f+852fa6fbcb,g47891489e3+6dc8069a4c,g53246c7159+edbf708997,g64539dfbff+9f17e571f4,g67b6fd64d1+6dc8069a4c,g74acd417e5+ae494d68d9,g786e29fd12+af89c03590,g7ae74a0b1c+a25e60b391,g7aefaa3e3d+536efcc10a,g7cc15d900a+d121454f8d,g87389fa792+a4172ec7da,g89139ef638+6dc8069a4c,g8d7436a09f+28c28d8d6d,g8ea07a8fe4+db21c37724,g92c671f44c+9f17e571f4,g98df359435+b2e6376b13,g99af87f6a8+b0f4ad7b8d,gac66b60396+966efe6077,gb88ae4c679+7dec8f19df,gbaa8f7a6c5+38b34f4976,gbf99507273+edbf708997,gc24b5d6ed1+9f17e571f4,gca7fc764a6+6dc8069a4c,gcc769fe2a4+97d0256649,gd7ef33dd92+6dc8069a4c,gdab6d2f7ff+ae494d68d9,gdbb4c4dda9+9f17e571f4,ge410e46f29+6dc8069a4c,geaed405ab2+e194be0d2b,w.2025.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
lsst.ip.isr.brighterFatterKernel Namespace Reference

Classes

class  BrighterFatterKernel
 

Functions

 brighterFatterCorrection (exposure, kernel, maxIter, threshold, applyGain, gains=None)
 
 transferFlux (cFunc, fStep, correctionMode=True)
 
 fluxConservingBrighterFatterCorrection (exposure, kernel, maxIter, threshold, applyGain, gains=None, correctionMode=True)
 

Detailed Description

Brighter Fatter Kernel calibration definition.

Function Documentation

◆ brighterFatterCorrection()

lsst.ip.isr.brighterFatterKernel.brighterFatterCorrection ( exposure,
kernel,
maxIter,
threshold,
applyGain,
gains = None )
Apply brighter fatter correction in place for the image.

Parameters
----------
exposure : `lsst.afw.image.Exposure`
    Exposure to have brighter-fatter correction applied.  Modified
    by this method.
kernel : `numpy.ndarray`
    Brighter-fatter kernel to apply.
maxIter : scalar
    Number of correction iterations to run.
threshold : scalar
    Convergence threshold in terms of the sum of absolute
    deviations between an iteration and the previous one.
applyGain : `Bool`
    If True, then the exposure values are scaled by the gain prior
    to correction.
gains : `dict` [`str`, `float`]
    A dictionary, keyed by amplifier name, of the gains to use.
    If gains is None, the nominal gains in the amplifier object are used.

Returns
-------
diff : `float`
    Final difference between iterations achieved in correction.
iteration : `int`
    Number of iterations used to calculate correction.

Notes
-----
This correction takes a kernel that has been derived from flat
field images to redistribute the charge.  The gradient of the
kernel is the deflection field due to the accumulated charge.

Given the original image I(x) and the kernel K(x) we can compute
the corrected image Ic(x) using the following equation:

Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))

To evaluate the derivative term we expand it as follows:

0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y)))
    + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )

Because we use the measured counts instead of the incident counts
we apply the correction iteratively to reconstruct the original
counts and the correction.  We stop iterating when the summed
difference between the current corrected image and the one from
the previous iteration is below the threshold.  We do not require
convergence because the number of iterations is too large a
computational cost.  How we define the threshold still needs to be
evaluated, the current default was shown to work reasonably well
on a small set of images.  For more information on the method see
DocuShare Document-19407.

The edges as defined by the kernel are not corrected because they
have spurious values due to the convolution.

Definition at line 589 of file brighterFatterKernel.py.

589def brighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain, gains=None):
590 """Apply brighter fatter correction in place for the image.
591
592 Parameters
593 ----------
594 exposure : `lsst.afw.image.Exposure`
595 Exposure to have brighter-fatter correction applied. Modified
596 by this method.
597 kernel : `numpy.ndarray`
598 Brighter-fatter kernel to apply.
599 maxIter : scalar
600 Number of correction iterations to run.
601 threshold : scalar
602 Convergence threshold in terms of the sum of absolute
603 deviations between an iteration and the previous one.
604 applyGain : `Bool`
605 If True, then the exposure values are scaled by the gain prior
606 to correction.
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.
610
611 Returns
612 -------
613 diff : `float`
614 Final difference between iterations achieved in correction.
615 iteration : `int`
616 Number of iterations used to calculate correction.
617
618 Notes
619 -----
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.
623
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:
626
627 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
628
629 To evaluate the derivative term we expand it as follows:
630
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))) )
633
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.
644
645 The edges as defined by the kernel are not corrected because they
646 have spurious values due to the convolution.
647 """
648 image = exposure.getMaskedImage().getImage()
649
650 # The image needs to be units of electrons/holes
651 with gainContext(exposure, image, applyGain, gains):
652
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)
658
659 nanIndex = np.isnan(tempImage.getArray())
660 tempImage.getArray()[nanIndex] = 0.
661
662 corr = np.zeros(image.array.shape, dtype=np.float64)
663 prev_image = np.zeros(image.array.shape, dtype=np.float64)
664
665 # Define boundary by convolution region. The region that the
666 # correction will be calculated for is one fewer in each dimension
667 # because of the second derivative terms.
668 # NOTE: these need to use integer math, as we're using start:end as
669 # numpy index ranges.
670 startX = kLx//2
671 endX = -kLx//2
672 startY = kLy//2
673 endY = -kLy//2
674
675 for iteration in range(maxIter):
676
677 outArray = scipy.signal.convolve(
678 tempImage.array,
679 kernelImage.array,
680 mode="same",
681 method="fft",
682 )
683 tmpArray = tempImage.getArray()
684
685 with np.errstate(invalid="ignore", over="ignore"):
686 # First derivative term
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]
690
691 # Second derivative term
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)
695
696 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
697
698 tmpArray[:, :] = image.getArray()[:, :]
699 tmpArray[nanIndex] = 0.
700 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
701
702 if iteration > 0:
703 diff = np.sum(np.abs(prev_image - tmpArray), dtype=np.float64)
704
705 if diff < threshold:
706 break
707 prev_image[:, :] = tmpArray[:, :]
708
709 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
710 corr[startY + 1:endY - 1, startX + 1:endX - 1]
711
712 return diff, iteration
713
714

◆ fluxConservingBrighterFatterCorrection()

lsst.ip.isr.brighterFatterKernel.fluxConservingBrighterFatterCorrection ( exposure,
kernel,
maxIter,
threshold,
applyGain,
gains = None,
correctionMode = True )
Apply brighter fatter correction in place for the image.

This version presents a modified version of the algorithm
found in ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``
which conserves the image flux, resulting in improved
correction of the cores of stars. The convolution has also been
modified to mitigate edge effects.

Parameters
----------
exposure : `lsst.afw.image.Exposure`
    Exposure to have brighter-fatter correction applied.  Modified
    by this method.
kernel : `np.ndarray`
    Brighter-fatter kernel to apply.
maxIter : scalar
    Number of correction iterations to run.
threshold : scalar
    Convergence threshold in terms of the sum of absolute
    deviations between an iteration and the previous one.
applyGain : `Bool`
    If True, then the exposure values are scaled by the gain prior
    to correction.
gains : `dict` [`str`, `float`]
    A dictionary, keyed by amplifier name, of the gains to use.
    If gains is None, the nominal gains in the amplifier object are used.
correctionMode : `Bool`
    If True (default) the function applies correction for BFE.  If False,
    the code can instead be used to generate a simulation of BFE (sign
    change in the direction of the effect)

Returns
-------
diff : `float`
    Final difference between iterations achieved in correction.
iteration : `int`
    Number of iterations used to calculate correction.

Notes
-----
Modified version of ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``.

This correction takes a kernel that has been derived from flat
field images to redistribute the charge.  The gradient of the
kernel is the deflection field due to the accumulated charge.

Given the original image I(x) and the kernel K(x) we can compute
the corrected image Ic(x) using the following equation:

Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))

Improved algorithm at this step applies the divergence theorem to
obtain a pixelised correction.

Because we use the measured counts instead of the incident counts
we apply the correction iteratively to reconstruct the original
counts and the correction.  We stop iterating when the summed
difference between the current corrected image and the one from
the previous iteration is below the threshold.  We do not require
convergence because the number of iterations is too large a
computational cost.  How we define the threshold still needs to be
evaluated, the current default was shown to work reasonably well
on a small set of images.

Edges are handled in the convolution by padding.  This is still not
a physical model for the edge, but avoids discontinuity in the correction.

Author of modified version: Lance.Miller@physics.ox.ac.uk
(see DM-38555).

Definition at line 799 of file brighterFatterKernel.py.

800 gains=None, correctionMode=True):
801 """Apply brighter fatter correction in place for the image.
802
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.
808
809 Parameters
810 ----------
811 exposure : `lsst.afw.image.Exposure`
812 Exposure to have brighter-fatter correction applied. Modified
813 by this method.
814 kernel : `np.ndarray`
815 Brighter-fatter kernel to apply.
816 maxIter : scalar
817 Number of correction iterations to run.
818 threshold : scalar
819 Convergence threshold in terms of the sum of absolute
820 deviations between an iteration and the previous one.
821 applyGain : `Bool`
822 If True, then the exposure values are scaled by the gain prior
823 to correction.
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)
831
832 Returns
833 -------
834 diff : `float`
835 Final difference between iterations achieved in correction.
836 iteration : `int`
837 Number of iterations used to calculate correction.
838
839 Notes
840 -----
841 Modified version of ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``.
842
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.
846
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:
849
850 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
851
852 Improved algorithm at this step applies the divergence theorem to
853 obtain a pixelised correction.
854
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.
864
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.
867
868 Author of modified version: Lance.Miller@physics.ox.ac.uk
869 (see DM-38555).
870 """
871 image = exposure.getMaskedImage().getImage()
872
873 # The image needs to be units of electrons/holes
874 with gainContext(exposure, image, applyGain, gains):
875
876 # get kernel and its shape
877 kLy, kLx = kernel.shape
878 kernelImage = afwImage.ImageD(kLx, kLy)
879 kernelImage.getArray()[:, :] = kernel
880 tempImage = afwImage.ImageD(image, deep=True)
881
882 nanIndex = np.isnan(tempImage.getArray())
883 tempImage.getArray()[nanIndex] = 0.
884
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)
890
891 # set the padding amount
892 # ensure we pad by an even amount larger than the kernel
893 kLy = 2 * ((1+kLy)//2)
894 kLx = 2 * ((1+kLx)//2)
895
896 # The deflection potential only depends on the gradient of
897 # the convolution, so we can subtract the mean, which then
898 # allows us to pad the image with zeros and avoid wrap-around effects
899 # (although still not handling the image edges with a physical model)
900 # This wouldn't be great if there were a strong image gradient.
901 imYdimension, imXdimension = tempImage.array.shape
902 imean = np.mean(tempImage.getArray()[~nanIndex], dtype=np.float64)
903 # subtract mean from image
904 tempImage -= imean
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))))
908 # Convert array to afw image so afwMath.convolve works
909 padImage = afwImage.ImageD(padArray.shape[1], padArray.shape[0])
910 padImage.array[:] = padArray
911
912 for iteration in range(maxIter):
913
914 # create deflection potential, convolution of flux with kernel
915 # using padded counts array
916 afwMath.convolve(outImage, padImage, fixedKernel, convCntrl)
917 tmpArray = tempImage.getArray()
918 outArray = outImage.getArray()
919
920 # trim convolution output back to original shape
921 outArray = outArray[:imYdimension, :imXdimension]
922
923 # generate the correction array, with correctionMode set as input
924 corr[...] = transferFlux(outArray, tmpArray, correctionMode=correctionMode)
925
926 # update the arrays for the next iteration
927 tmpArray[:, :] = image.getArray()[:, :]
928 tmpArray += corr
929 tmpArray[nanIndex] = 0.
930 # update padded array
931 # subtract mean
932 tmpArray -= imean
933 tempImage.array[nanIndex] = 0.
934 padArray = np.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
935
936 if iteration > 0:
937 diff = np.sum(np.abs(prevImage - tmpArray), dtype=np.float64)
938
939 if diff < threshold:
940 break
941 prevImage[:, :] = tmpArray[:, :]
942
943 image.getArray()[:] += corr[:]
944
945 return diff, iteration
Parameters to control convolution.
A kernel created from an Image.
Definition Kernel.h:471
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.

◆ transferFlux()

lsst.ip.isr.brighterFatterKernel.transferFlux ( cFunc,
fStep,
correctionMode = True )
Take the input convolved deflection potential and the flux array
to compute and apply the flux transfer into the correction array.

Parameters
----------
cFunc: `np.array`
    Deflection potential, being the convolution of the flux F with the
    kernel K.
fStep: `np.array`
    The array of flux values which act as the source of the flux transfer.
correctionMode: `bool`
    Defines if applying correction (True) or generating sims (False).

Returns
-------
corr:
    BFE correction array

Definition at line 715 of file brighterFatterKernel.py.

715def transferFlux(cFunc, fStep, correctionMode=True):
716 """Take the input convolved deflection potential and the flux array
717 to compute and apply the flux transfer into the correction array.
718
719 Parameters
720 ----------
721 cFunc: `np.array`
722 Deflection potential, being the convolution of the flux F with the
723 kernel K.
724 fStep: `np.array`
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).
728
729 Returns
730 -------
731 corr:
732 BFE correction array
733 """
734
735 if cFunc.shape != fStep.shape:
736 raise RuntimeError(f'transferFlux: array shapes do not match: {cFunc.shape}, {fStep.shape}')
737
738 # set the sign of the correction and set its value for the
739 # time averaged solution
740 if correctionMode:
741 # negative sign if applying BFE correction
742 factor = -0.5
743 else:
744 # positive sign if generating BFE simulations
745 factor = 0.5
746
747 # initialise the BFE correction image to zero
748 corr = np.zeros(cFunc.shape, dtype=np.float64)
749
750 # Generate a 2D mesh of x,y coordinates
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)
755
756 # process each axis in turn
757 for ax in [0, 1]:
758
759 # gradient of phi on right/upper edge of pixel
760 diff = np.diff(cFunc, axis=ax)
761
762 # expand array back to full size with zero gradient at the end
763 gx = np.zeros(cFunc.shape, dtype=np.float64)
764 yDiff, xDiff = diff.shape
765 gx[:yDiff, :xDiff] += diff
766
767 # select pixels with either positive gradients on the right edge,
768 # flux flowing to the right/up
769 # or negative gradients, flux flowing to the left/down
770 for i, sel in enumerate([gx > 0, gx < 0]):
771 xSelPixels = xc[sel]
772 ySelPixels = yc[sel]
773 # and add the flux into the pixel to the right or top
774 # depending on which axis we are handling
775 if ax == 0:
776 xPix = xSelPixels
777 yPix = ySelPixels+1
778 else:
779 xPix = xSelPixels+1
780 yPix = ySelPixels
781 # define flux as the either current pixel value or pixel
782 # above/right
783 # depending on whether positive or negative gradient
784 if i == 0:
785 # positive gradients, flux flowing to higher coordinate values
786 flux = factor * fStep[sel]*gx[sel]
787 else:
788 # negative gradients, flux flowing to lower coordinate values
789 flux = factor * fStep[yPix, xPix]*gx[sel]
790 # change the fluxes of the donor and receiving pixels
791 # such that flux is conserved
792 corr[sel] -= flux
793 corr[yPix, xPix] += flux
794
795 # return correction array
796 return corr
797
798