LSST Applications g034a557a3c+0df69e75ff,g0afe43252f+b86e4b8053,g11f7dcd041+017865fdd3,g1cd03abf6b+1c8dc8730a,g1ce3e0751c+f991eae79d,g28da252d5a+a136f03385,g2bbee38e9b+b6588ad223,g2bc492864f+b6588ad223,g2cdde0e794+8523d0dbb4,g347aa1857d+b6588ad223,g35bb328faa+b86e4b8053,g3a166c0a6a+b6588ad223,g461a3dce89+b86e4b8053,g52b1c1532d+b86e4b8053,g6233c72cae+da9c58a417,g7f3b0d46df+ad13c1b82d,g80478fca09+f29c5d6c70,g858d7b2824+4fc997592f,g8cd86fa7b1+5f14beadf5,g965a9036f2+4fc997592f,g979bb04a14+87f76c17e6,g9ddcbc5298+f24b38b85a,gae0086650b+b86e4b8053,gbb886bcc26+77117948e7,gc28159a63d+b6588ad223,gc30aee3386+a2f0f6cab9,gcaf7e4fdec+4fc997592f,gcd45df26be+4fc997592f,gcdd4ae20e8+0acf6430b1,gcf0d15dbbd+0acf6430b1,gdaeeff99f8+006e14e809,gdbce86181e+467b805b48,ge3d4d395c2+224150c836,ge5f7162a3a+1d9667e7ad,ge6cb8fbbf7+0992c83eee,ge79ae78c31+b6588ad223,gf048a9a2f4+41d6ddaca1,gf0baf85859+b4cca3d10f,w.2024.30
LSST Data Management Base Package
Loading...
Searching...
No Matches
Public Member Functions | Public Attributes | Static Protected Attributes | List of all members
lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm Class Reference

Public Member Functions

 __init__ (self, diffim, posImage=None, negImage=None)
 
 fitDipoleImpl (self, source, tol=1e-7, rel_weight=0.5, fitBackground=1, bgGradientOrder=1, maxSepInSigma=5., separateNegParams=True, verbose=False)
 
 fitDipole (self, source, tol=1e-7, rel_weight=0.1, fitBackground=1, maxSepInSigma=5., separateNegParams=True, bgGradientOrder=1, verbose=False, display=False)
 
 displayFitResults (self, footprint, result)
 

Public Attributes

 diffim
 
 posImage
 
 negImage
 
 psfSigma
 
 log
 
 debug
 

Static Protected Attributes

str _private_version_ = '0.0.5'
 

Detailed Description

Fit a dipole model using an image difference.

See also:
`DMTN-007: Dipole characterization for image differencing  <https://dmtn-007.lsst.io>`_.

Definition at line 489 of file dipoleFitTask.py.

Constructor & Destructor Documentation

◆ __init__()

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.__init__ ( self,
diffim,
posImage = None,
negImage = None )
Algorithm to run dipole measurement on a diaSource

Parameters
----------
diffim : `lsst.afw.image.Exposure`
    Exposure on which the diaSources were detected
posImage : `lsst.afw.image.Exposure`
    "Positive" exposure from which the template was subtracted
negImage : `lsst.afw.image.Exposure`
    "Negative" exposure which was subtracted from the posImage

Definition at line 515 of file dipoleFitTask.py.

515 def __init__(self, diffim, posImage=None, negImage=None):
516 """Algorithm to run dipole measurement on a diaSource
517
518 Parameters
519 ----------
520 diffim : `lsst.afw.image.Exposure`
521 Exposure on which the diaSources were detected
522 posImage : `lsst.afw.image.Exposure`
523 "Positive" exposure from which the template was subtracted
524 negImage : `lsst.afw.image.Exposure`
525 "Negative" exposure which was subtracted from the posImage
526 """
527
528 self.diffim = diffim
529 self.posImage = posImage
530 self.negImage = negImage
531 self.psfSigma = None
532 if diffim is not None:
533 diffimPsf = diffim.getPsf()
534 diffimAvgPos = diffimPsf.getAveragePosition()
535 self.psfSigma = diffimPsf.computeShape(diffimAvgPos).getDeterminantRadius()
536
537 self.log = logging.getLogger(__name__)
538
539 import lsstDebug
540 self.debug = lsstDebug.Info(__name__).debug
541

Member Function Documentation

◆ displayFitResults()

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.displayFitResults ( self,
footprint,
result )
Display data, model fits and residuals (currently uses matplotlib display functions).

Parameters
----------
footprint : `lsst.afw.detection.Footprint`
    Footprint containing the dipole that was fit
result : `lmfit.MinimizerResult`
    `lmfit.MinimizerResult` object returned by `lmfit` optimizer

Definition at line 905 of file dipoleFitTask.py.

905 def displayFitResults(self, footprint, result):
906 """Display data, model fits and residuals (currently uses matplotlib display functions).
907
908 Parameters
909 ----------
910 footprint : `lsst.afw.detection.Footprint`
911 Footprint containing the dipole that was fit
912 result : `lmfit.MinimizerResult`
913 `lmfit.MinimizerResult` object returned by `lmfit` optimizer
914 """
915 try:
916 import matplotlib.pyplot as plt
917 except ImportError as err:
918 self.log.warning('Unable to import matplotlib: %s', err)
919 raise err
920
921 def display2dArray(arr, title='Data', extent=None):
922 """Use `matplotlib.pyplot.imshow` to display a 2-D array with a given coordinate range.
923 """
924 fig = plt.imshow(arr, origin='lower', interpolation='none', cmap='gray', extent=extent)
925 plt.title(title)
926 plt.colorbar(fig, cmap='gray')
927 return fig
928
929 z = result.data
930 fit = result.best_fit
931 bbox = footprint.getBBox()
932 extent = (bbox.getBeginX(), bbox.getEndX(), bbox.getBeginY(), bbox.getEndY())
933 if z.shape[0] == 3:
934 plt.figure(figsize=(8, 8))
935 for i in range(3):
936 plt.subplot(3, 3, i*3+1)
937 display2dArray(z[i, :], 'Data', extent=extent)
938 plt.subplot(3, 3, i*3+2)
939 display2dArray(fit[i, :], 'Model', extent=extent)
940 plt.subplot(3, 3, i*3+3)
941 display2dArray(z[i, :] - fit[i, :], 'Residual', extent=extent)
942 else:
943 plt.figure(figsize=(8, 2.5))
944 plt.subplot(1, 3, 1)
945 display2dArray(z, 'Data', extent=extent)
946 plt.subplot(1, 3, 2)
947 display2dArray(fit, 'Model', extent=extent)
948 plt.subplot(1, 3, 3)
949 display2dArray(z - fit, 'Residual', extent=extent)
950
951 plt.show()
952
953
954@measBase.register("ip_diffim_DipoleFit")

◆ fitDipole()

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.fitDipole ( self,
source,
tol = 1e-7,
rel_weight = 0.1,
fitBackground = 1,
maxSepInSigma = 5.,
separateNegParams = True,
bgGradientOrder = 1,
verbose = False,
display = False )
Fit a dipole model to an input ``diaSource`` (wraps `fitDipoleImpl`).

Actually, fits the subimage bounded by the input source's
footprint) and optionally constrain the fit using the
pre-subtraction images self.posImage (science) and
self.negImage (template). Wraps the output into a
`pipeBase.Struct` named tuple after computing additional
statistics such as orientation and SNR.

Parameters
----------
source : `lsst.afw.table.SourceRecord`
    Record containing the (merged) dipole source footprint detected on the diffim
tol : `float`, optional
    Tolerance parameter for scipy.leastsq() optimization
rel_weight : `float`, optional
    Weighting of posImage/negImage relative to the diffim in the fit
fitBackground : `int`, {0, 1, 2}, optional
    How to fit linear background gradient in posImage/negImage

        - 0: do not fit background at all
        - 1 (default): pre-fit the background using linear least squares and then do not fit it
          as part of the dipole fitting optimization
        - 2: pre-fit the background using linear least squares (as in 1), and use the parameter
          estimates from that fit as starting parameters for an integrated "re-fit" of the
          background as part of the overall dipole fitting optimization.
maxSepInSigma : `float`, optional
    Allowed window of centroid parameters relative to peak in input source footprint
separateNegParams : `bool`, optional
    Fit separate parameters to the flux and background gradient in
bgGradientOrder : `int`, {0, 1, 2}, optional
    Desired polynomial order of background gradient
verbose: `bool`, optional
    Be verbose
display
    Display input data, best fit model(s) and residuals in a matplotlib window.

Returns
-------
result : `struct`
    `pipeBase.Struct` object containing the fit parameters and other information.

result : `callable`
    `lmfit.MinimizerResult` object for debugging and error estimation, etc.

Notes
-----
Parameter `fitBackground` has three options, thus it is an integer:

Definition at line 798 of file dipoleFitTask.py.

800 bgGradientOrder=1, verbose=False, display=False):
801 """Fit a dipole model to an input ``diaSource`` (wraps `fitDipoleImpl`).
802
803 Actually, fits the subimage bounded by the input source's
804 footprint) and optionally constrain the fit using the
805 pre-subtraction images self.posImage (science) and
806 self.negImage (template). Wraps the output into a
807 `pipeBase.Struct` named tuple after computing additional
808 statistics such as orientation and SNR.
809
810 Parameters
811 ----------
812 source : `lsst.afw.table.SourceRecord`
813 Record containing the (merged) dipole source footprint detected on the diffim
814 tol : `float`, optional
815 Tolerance parameter for scipy.leastsq() optimization
816 rel_weight : `float`, optional
817 Weighting of posImage/negImage relative to the diffim in the fit
818 fitBackground : `int`, {0, 1, 2}, optional
819 How to fit linear background gradient in posImage/negImage
820
821 - 0: do not fit background at all
822 - 1 (default): pre-fit the background using linear least squares and then do not fit it
823 as part of the dipole fitting optimization
824 - 2: pre-fit the background using linear least squares (as in 1), and use the parameter
825 estimates from that fit as starting parameters for an integrated "re-fit" of the
826 background as part of the overall dipole fitting optimization.
827 maxSepInSigma : `float`, optional
828 Allowed window of centroid parameters relative to peak in input source footprint
829 separateNegParams : `bool`, optional
830 Fit separate parameters to the flux and background gradient in
831 bgGradientOrder : `int`, {0, 1, 2}, optional
832 Desired polynomial order of background gradient
833 verbose: `bool`, optional
834 Be verbose
835 display
836 Display input data, best fit model(s) and residuals in a matplotlib window.
837
838 Returns
839 -------
840 result : `struct`
841 `pipeBase.Struct` object containing the fit parameters and other information.
842
843 result : `callable`
844 `lmfit.MinimizerResult` object for debugging and error estimation, etc.
845
846 Notes
847 -----
848 Parameter `fitBackground` has three options, thus it is an integer:
849
850 """
851
852 fitResult = self.fitDipoleImpl(
853 source, tol=tol, rel_weight=rel_weight, fitBackground=fitBackground,
854 maxSepInSigma=maxSepInSigma, separateNegParams=separateNegParams,
855 bgGradientOrder=bgGradientOrder, verbose=verbose)
856
857 # Display images, model fits and residuals (currently uses matplotlib display functions)
858 if display:
859 fp = source.getFootprint()
860 self.displayFitResults(fp, fitResult)
861
862 fitParams = fitResult.best_values
863 if fitParams['flux'] <= 1.: # usually around 0.1 -- the minimum flux allowed -- i.e. bad fit.
864 return None, fitResult
865
866 centroid = ((fitParams['xcenPos'] + fitParams['xcenNeg']) / 2.,
867 (fitParams['ycenPos'] + fitParams['ycenNeg']) / 2.)
868 dx, dy = fitParams['xcenPos'] - fitParams['xcenNeg'], fitParams['ycenPos'] - fitParams['ycenNeg']
869 angle = np.arctan2(dy, dx)
870
871 # Exctract flux value, compute signalToNoise from flux/variance_within_footprint
872 # Also extract the stderr of flux estimate.
873 def computeSumVariance(exposure, footprint):
874 return np.sqrt(np.nansum(exposure[footprint.getBBox(), afwImage.PARENT].variance.array))
875
876 fluxVal = fluxVar = fitParams['flux']
877 fluxErr = fluxErrNeg = fitResult.params['flux'].stderr
878 if self.posImage is not None:
879 fluxVar = computeSumVariance(self.posImage, source.getFootprint())
880 else:
881 fluxVar = computeSumVariance(self.diffim, source.getFootprint())
882
883 fluxValNeg, fluxVarNeg = fluxVal, fluxVar
884 if separateNegParams:
885 fluxValNeg = fitParams['fluxNeg']
886 fluxErrNeg = fitResult.params['fluxNeg'].stderr
887 if self.negImage is not None:
888 fluxVarNeg = computeSumVariance(self.negImage, source.getFootprint())
889
890 try:
891 signalToNoise = np.sqrt((fluxVal/fluxVar)**2 + (fluxValNeg/fluxVarNeg)**2)
892 except ZeroDivisionError: # catch divide by zero - should never happen.
893 signalToNoise = np.nan
894
895 out = Struct(posCentroidX=fitParams['xcenPos'], posCentroidY=fitParams['ycenPos'],
896 negCentroidX=fitParams['xcenNeg'], negCentroidY=fitParams['ycenNeg'],
897 posFlux=fluxVal, negFlux=-fluxValNeg, posFluxErr=fluxErr, negFluxErr=fluxErrNeg,
898 centroidX=centroid[0], centroidY=centroid[1], orientation=angle,
899 signalToNoise=signalToNoise, chi2=fitResult.chisqr, redChi2=fitResult.redchi,
900 nData=fitResult.ndata)
901
902 # fitResult may be returned for debugging
903 return out, fitResult
904

◆ fitDipoleImpl()

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.fitDipoleImpl ( self,
source,
tol = 1e-7,
rel_weight = 0.5,
fitBackground = 1,
bgGradientOrder = 1,
maxSepInSigma = 5.,
separateNegParams = True,
verbose = False )
Fit a dipole model to an input difference image.

Actually, fits the subimage bounded by the input source's
footprint) and optionally constrain the fit using the
pre-subtraction images posImage and negImage.

Parameters
----------
source : TODO: DM-17458
    TODO: DM-17458
tol : float, optional
    TODO: DM-17458
rel_weight : `float`, optional
    TODO: DM-17458
fitBackground : `int`, optional
    TODO: DM-17458
bgGradientOrder : `int`, optional
    TODO: DM-17458
maxSepInSigma : `float`, optional
    TODO: DM-17458
separateNegParams : `bool`, optional
    TODO: DM-17458
verbose : `bool`, optional
    TODO: DM-17458

Returns
-------
result : `lmfit.MinimizerResult`
    return `lmfit.MinimizerResult` object containing the fit
    parameters and other information.

Definition at line 542 of file dipoleFitTask.py.

544 separateNegParams=True, verbose=False):
545 """Fit a dipole model to an input difference image.
546
547 Actually, fits the subimage bounded by the input source's
548 footprint) and optionally constrain the fit using the
549 pre-subtraction images posImage and negImage.
550
551 Parameters
552 ----------
553 source : TODO: DM-17458
554 TODO: DM-17458
555 tol : float, optional
556 TODO: DM-17458
557 rel_weight : `float`, optional
558 TODO: DM-17458
559 fitBackground : `int`, optional
560 TODO: DM-17458
561 bgGradientOrder : `int`, optional
562 TODO: DM-17458
563 maxSepInSigma : `float`, optional
564 TODO: DM-17458
565 separateNegParams : `bool`, optional
566 TODO: DM-17458
567 verbose : `bool`, optional
568 TODO: DM-17458
569
570 Returns
571 -------
572 result : `lmfit.MinimizerResult`
573 return `lmfit.MinimizerResult` object containing the fit
574 parameters and other information.
575 """
576
577 # Only import lmfit if someone wants to use the new DipoleFitAlgorithm.
578 import lmfit
579
580 fp = source.getFootprint()
581 bbox = fp.getBBox()
582 subim = afwImage.MaskedImageF(self.diffim.getMaskedImage(), bbox=bbox, origin=afwImage.PARENT)
583
584 z = diArr = subim.image.array
585 # Make sure we don't overwrite buffers.
586 z = z.copy()
587 weights = 1. / subim.variance.array # get the weights (=1/variance)
588
589 if rel_weight > 0. and ((self.posImage is not None) or (self.negImage is not None)):
590 if self.negImage is not None:
591 negSubim = afwImage.MaskedImageF(self.negImage.getMaskedImage(), bbox, origin=afwImage.PARENT)
592 if self.posImage is not None:
593 posSubim = afwImage.MaskedImageF(self.posImage.getMaskedImage(), bbox, origin=afwImage.PARENT)
594 if self.posImage is None: # no science image provided; generate it from diffim + negImage
595 posSubim = subim.clone()
596 posSubim += negSubim
597 if self.negImage is None: # no template provided; generate it from the posImage - diffim
598 negSubim = posSubim.clone()
599 negSubim -= subim
600
601 z = np.append([z], [posSubim.image.array,
602 negSubim.image.array], axis=0)
603 # Weight the pos/neg images by rel_weight relative to the diffim
604 weights = np.append([weights], [1. / posSubim.variance.array * rel_weight,
605 1. / negSubim.variance.array * rel_weight], axis=0)
606 else:
607 rel_weight = 0. # a short-cut for "don't include the pre-subtraction data"
608
609 # It seems that `lmfit` requires a static functor as its optimized method, which eliminates
610 # the ability to pass a bound method or other class method. Here we write a wrapper which
611 # makes this possible.
612 def dipoleModelFunctor(x, flux, xcenPos, ycenPos, xcenNeg, ycenNeg, fluxNeg=None,
613 b=None, x1=None, y1=None, xy=None, x2=None, y2=None,
614 bNeg=None, x1Neg=None, y1Neg=None, xyNeg=None, x2Neg=None, y2Neg=None,
615 **kwargs):
616 """Generate dipole model with given parameters.
617
618 It simply defers to `modelObj.makeModel()`, where `modelObj` comes
619 out of `kwargs['modelObj']`.
620 """
621 modelObj = kwargs.pop('modelObj')
622 return modelObj.makeModel(x, flux, xcenPos, ycenPos, xcenNeg, ycenNeg, fluxNeg=fluxNeg,
623 b=b, x1=x1, y1=y1, xy=xy, x2=x2, y2=y2,
624 bNeg=bNeg, x1Neg=x1Neg, y1Neg=y1Neg, xyNeg=xyNeg,
625 x2Neg=x2Neg, y2Neg=y2Neg, **kwargs)
626
627 dipoleModel = DipoleModel()
628
629 modelFunctor = dipoleModelFunctor # dipoleModel.makeModel does not work for now.
630 # Create the lmfit model (lmfit uses scipy 'leastsq' option by default - Levenberg-Marquardt)
631 # We have to (later) filter out the nans by hand in our input to gmod.fit().
632 # The only independent variable in the model is "x"; lmfit tries to
633 # introspect variables and parameters from the function signature, but
634 # gets it wrong for the model signature above.
635 gmod = lmfit.Model(modelFunctor, independent_vars=["x"], verbose=verbose)
636
637 # Add the constraints for centroids, fluxes.
638 # starting constraint - near centroid of footprint
639 fpCentroid = np.array([fp.getCentroid().getX(), fp.getCentroid().getY()])
640 cenNeg = cenPos = fpCentroid
641
642 pks = fp.getPeaks()
643
644 if len(pks) >= 1:
645 cenPos = pks[0].getF() # if individual (merged) peaks were detected, use those
646 if len(pks) >= 2: # peaks are already sorted by centroid flux so take the most negative one
647 cenNeg = pks[-1].getF()
648
649 # For close/faint dipoles the starting locs (min/max) might be way off, let's help them a bit.
650 # First assume dipole is not separated by more than 5*psfSigma.
651 maxSep = self.psfSigma * maxSepInSigma
652
653 # As an initial guess -- assume the dipole is close to the center of the footprint.
654 if np.sum(np.sqrt((np.array(cenPos) - fpCentroid)**2.)) > maxSep:
655 cenPos = fpCentroid
656 if np.sum(np.sqrt((np.array(cenNeg) - fpCentroid)**2.)) > maxSep:
657 cenPos = fpCentroid
658
659 # parameter hints/constraints: https://lmfit.github.io/lmfit-py/model.html#model-param-hints-section
660 # might make sense to not use bounds -- see http://lmfit.github.io/lmfit-py/bounds.html
661 # also see this discussion -- https://github.com/scipy/scipy/issues/3129
662 gmod.set_param_hint('xcenPos', value=cenPos[0],
663 min=cenPos[0]-maxSep, max=cenPos[0]+maxSep)
664 gmod.set_param_hint('ycenPos', value=cenPos[1],
665 min=cenPos[1]-maxSep, max=cenPos[1]+maxSep)
666 gmod.set_param_hint('xcenNeg', value=cenNeg[0],
667 min=cenNeg[0]-maxSep, max=cenNeg[0]+maxSep)
668 gmod.set_param_hint('ycenNeg', value=cenNeg[1],
669 min=cenNeg[1]-maxSep, max=cenNeg[1]+maxSep)
670
671 # Use the (flux under the dipole)*5 for an estimate.
672 # Lots of testing showed that having startingFlux be too high was better than too low.
673 startingFlux = np.nansum(np.abs(diArr) - np.nanmedian(np.abs(diArr))) * 5.
674 posFlux = negFlux = startingFlux
675
676 # TBD: set max. flux limit?
677 gmod.set_param_hint('flux', value=posFlux, min=0.1)
678
679 if separateNegParams:
680 # TBD: set max negative lobe flux limit?
681 gmod.set_param_hint('fluxNeg', value=np.abs(negFlux), min=0.1)
682
683 # Fixed parameters (don't fit for them if there are no pre-sub images or no gradient fit requested):
684 # Right now (fitBackground == 1), we fit a linear model to the background and then subtract
685 # it from the data and then don't fit the background again (this is faster).
686 # A slower alternative (fitBackground == 2) is to use the estimated background parameters as
687 # starting points in the integrated model fit. That is currently not performed by default,
688 # but might be desirable in some cases.
689 bgParsPos = bgParsNeg = (0., 0., 0.)
690 if ((rel_weight > 0.) and (fitBackground != 0) and (bgGradientOrder >= 0)):
691 pbg = 0.
692 bgFitImage = self.posImage if self.posImage is not None else self.negImage
693 # Fit the gradient to the background (linear model)
694 bgParsPos = bgParsNeg = dipoleModel.fitFootprintBackground(source, bgFitImage,
695 order=bgGradientOrder)
696
697 # Generate the gradient and subtract it from the pre-subtraction image data
698 if fitBackground == 1:
699 in_x = dipoleModel._generateXYGrid(bbox)
700 pbg = dipoleModel.makeBackgroundModel(in_x, tuple(bgParsPos))
701 z[1, :] -= pbg
702 z[1, :] -= np.nanmedian(z[1, :])
703 posFlux = np.nansum(z[1, :])
704 gmod.set_param_hint('flux', value=posFlux*1.5, min=0.1)
705
706 if separateNegParams and self.negImage is not None:
707 bgParsNeg = dipoleModel.fitFootprintBackground(source, self.negImage,
708 order=bgGradientOrder)
709 pbg = dipoleModel.makeBackgroundModel(in_x, tuple(bgParsNeg))
710 z[2, :] -= pbg
711 z[2, :] -= np.nanmedian(z[2, :])
712 if separateNegParams:
713 negFlux = np.nansum(z[2, :])
714 gmod.set_param_hint('fluxNeg', value=negFlux*1.5, min=0.1)
715
716 # Do not subtract the background from the images but include the background parameters in the fit
717 if fitBackground == 2:
718 if bgGradientOrder >= 0:
719 gmod.set_param_hint('b', value=bgParsPos[0])
720 if separateNegParams:
721 gmod.set_param_hint('bNeg', value=bgParsNeg[0])
722 if bgGradientOrder >= 1:
723 gmod.set_param_hint('x1', value=bgParsPos[1])
724 gmod.set_param_hint('y1', value=bgParsPos[2])
725 if separateNegParams:
726 gmod.set_param_hint('x1Neg', value=bgParsNeg[1])
727 gmod.set_param_hint('y1Neg', value=bgParsNeg[2])
728 if bgGradientOrder >= 2:
729 gmod.set_param_hint('xy', value=bgParsPos[3])
730 gmod.set_param_hint('x2', value=bgParsPos[4])
731 gmod.set_param_hint('y2', value=bgParsPos[5])
732 if separateNegParams:
733 gmod.set_param_hint('xyNeg', value=bgParsNeg[3])
734 gmod.set_param_hint('x2Neg', value=bgParsNeg[4])
735 gmod.set_param_hint('y2Neg', value=bgParsNeg[5])
736
737 y, x = np.mgrid[bbox.getBeginY():bbox.getEndY(), bbox.getBeginX():bbox.getEndX()]
738 in_x = np.array([x, y]).astype(np.float64)
739 in_x[0, :] -= in_x[0, :].mean() # center it!
740 in_x[1, :] -= in_x[1, :].mean()
741
742 # Instead of explicitly using a mask to ignore flagged pixels, just set the ignored pixels'
743 # weights to 0 in the fit. TBD: need to inspect mask planes to set this mask.
744 mask = np.ones_like(z, dtype=bool) # TBD: set mask values to False if the pixels are to be ignored
745
746 # I'm not sure about the variance planes in the diffim (or convolved pre-sub. images
747 # for that matter) so for now, let's just do an un-weighted least-squares fit
748 # (override weights computed above).
749 weights = mask.astype(np.float64)
750 if self.posImage is not None and rel_weight > 0.:
751 weights = np.array([np.ones_like(diArr), np.ones_like(diArr)*rel_weight,
752 np.ones_like(diArr)*rel_weight])
753
754 # Set the weights to zero if mask is False
755 if np.any(~mask):
756 weights[~mask] = 0.
757
758 # Filter out any nans, and make the weights 0.
759 nans = (np.isnan(z) | np.isnan(weights))
760 nNans = nans.sum()
761 if nNans > 0:
762 if nNans < len(z):
763 z[nans] = np.nanmedian(z)
764 else:
765 z[nans] = 0
766 weights[nans] = 0
767
768 # Note that although we can, we're not required to set initial values for params here,
769 # since we set their param_hint's above.
770 # Can add "method" param to not use 'leastsq' (==levenberg-marquardt), e.g. "method='nelder'"
771 with warnings.catch_warnings():
772 # Ignore lmfit unknown argument warnings:
773 # "psf, rel_weight, footprint, modelObj" all become pass-through kwargs for makeModel.
774 warnings.filterwarnings("ignore", "The keyword argument .* does not match", UserWarning)
775 result = gmod.fit(z, weights=weights, x=in_x, max_nfev=250,
776 method="leastsq", # TODO: try using `least_squares` here for speed/robustness
777 verbose=verbose,
778 # see scipy docs for the meaning of these keywords
779 fit_kws={'ftol': tol, 'xtol': tol, 'gtol': tol,
780 # Our model is float32 internally, so we need a larger epsfcn.
781 'epsfcn': 1e-8},
782 psf=self.diffim.getPsf(), # hereon: kwargs that get passed to makeModel()
783 rel_weight=rel_weight,
784 footprint=fp,
785 modelObj=dipoleModel)
786
787 if verbose: # the ci_report() seems to fail if neg params are constrained -- TBD why.
788 # Never wanted in production - this takes a long time (longer than the fit!)
789 # This is how to get confidence intervals out:
790 # https://lmfit.github.io/lmfit-py/confidence.html and
791 # http://cars9.uchicago.edu/software/python/lmfit/model.html
792 print(result.fit_report(show_correl=False))
793 if separateNegParams:
794 print(result.ci_report())
795
796 return result
797

Member Data Documentation

◆ _private_version_

str lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm._private_version_ = '0.0.5'
staticprotected

Definition at line 498 of file dipoleFitTask.py.

◆ debug

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.debug

Definition at line 540 of file dipoleFitTask.py.

◆ diffim

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.diffim

Definition at line 528 of file dipoleFitTask.py.

◆ log

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.log

Definition at line 537 of file dipoleFitTask.py.

◆ negImage

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.negImage

Definition at line 530 of file dipoleFitTask.py.

◆ posImage

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.posImage

Definition at line 529 of file dipoleFitTask.py.

◆ psfSigma

lsst.ip.diffim.dipoleFitTask.DipoleFitAlgorithm.psfSigma

Definition at line 531 of file dipoleFitTask.py.


The documentation for this class was generated from the following file: