Beispiel #1
0
    def testLogLevel(self):
        """Test --loglevel"""
        for logLevel in ("trace", "debug", "Info", "WARN", "eRRoR", "fatal"):
            namespace = self.ap.parse_args(
                config=self.config,
                args=[DataPath, "--loglevel", logLevel],
            )
            intLevel = getattr(namespace.log, logLevel.upper())
            print("testing logLevel=%r" % (logLevel,))
            self.assertEqual(namespace.log.getEffectiveLevel(), intLevel)
            self.assertFalse(hasattr(namespace, "loglevel"))

            bazLevel = "TRACE"
            namespace = self.ap.parse_args(
                config=self.config,
                args=[DataPath, "--loglevel", logLevel,
                      "foo.bar=%s" % (logLevel,),
                      "baz=INFO",
                      "baz=%s" % bazLevel,  # test that later values override earlier values
                      ],
            )
            self.assertEqual(namespace.log.getEffectiveLevel(), intLevel)
            self.assertEqual(getLogger("foo.bar").getEffectiveLevel(), intLevel)
            self.assertEqual(getLogger("baz").getEffectiveLevel(),
                             getattr(getLogger(), bazLevel))

        with self.assertRaises(SystemExit):
            self.ap.parse_args(config=self.config,
                               args=[DataPath, "--loglevel", "1234"],
                               )

        with self.assertRaises(SystemExit):
            self.ap.parse_args(config=self.config,
                               args=[DataPath, "--loglevel", "INVALID_LEVEL"],
                               )
Beispiel #2
0
    def __init__(self):
        # Set up defaults to send to deblender

        # Always deblend as Psf
        self.psfChisqCut1 = self.psfChisqCut2 = self.psfChisqCut2b = np.inf
        self.log = getLogger('lsst.ip.diffim.DipoleDeblender')
        self.sigma2fwhm = 2. * np.sqrt(2. * np.log(2.))
Beispiel #3
0
def getTaskLogger(name=None, logger=None):
    """Get a logger compatible with LSST usage.

    Parameters
    ----------
    name : `str`, optional
        Name of the logger. Root logger if `None`.
    logger : `logging.Logger`
        If given the logger is converted to the relevant logger class.
        If ``name`` is given the logger is assumed to be a child of the
        supplied logger.

    Returns
    -------
    logger : `lsst.utils.logging.LsstLogAdapter`
        The relevant logger.

    Notes
    -----
    A `logging.LoggerAdapter` is used since it is easier to provide a more
    uniform interface than when using `logging.setLoggerClass`. An adapter
    can be wrapped around the root logger and the `~logging.setLoggerClass`
    will return the logger first given that name even if the name was
    used before the `Task` was created.
    """
    return getLogger(name=name, logger=logger)
Beispiel #4
0
    def testLogCommands(self):
        """Check that all the log commands work."""

        root = getLogger()

        with self.assertLogs(level=root.TRACE) as cm:
            root.trace("Trace")
            root.debug("Debug")
            root.verbose("Verbose")
            root.info("Info")
            root.warning("Warning")
            root.fatal("Fatal")
            root.critical("Critical")
            root.error("Error")

        self.assertEqual(len(cm.records), 8)

        # Check that each record has an explicit level name rather than
        # "Level N" and comes from this file (and not the logging.py).
        for record in cm.records:
            self.assertRegex(record.levelname, "^[A-Z]+$")
            self.assertEqual(record.filename, "test_logging.py")

        with self.assertLogs(level=root.DEBUG) as cm:
            # Should only issue the INFO message.
            with root.temporary_log_level(root.INFO):
                root.info("Info")
                root.debug("Debug")
        self.assertEqual(len(cm.records), 1)

        child = root.getChild("child")
        self.assertEqual(child.getEffectiveLevel(), root.getEffectiveLevel())
        child.setLevel(root.DEBUG)
        self.assertNotEqual(child.getEffectiveLevel(), root.getEffectiveLevel())
Beispiel #5
0
    def testLogLevels(self):
        """Check that the new log levels look reasonable."""

        root = getLogger()

        self.assertEqual(root.DEBUG, logging.DEBUG)
        self.assertGreater(root.VERBOSE, logging.DEBUG)
        self.assertLess(root.VERBOSE, logging.INFO)
        self.assertLess(root.TRACE, logging.DEBUG)
Beispiel #6
0
    def testTraceSetAt(self):
        log_name = "lsst.afw"
        trace_set_at(log_name, 2)
        trace2_log = getLogger(f"TRACE2.{log_name}")
        trace3_log = getLogger(f"TRACE3.{log_name}")
        self.assertEqual(trace2_log.getEffectiveLevel(), logging.DEBUG)
        self.assertEqual(trace3_log.getEffectiveLevel(), logging.INFO)

        # Check that child loggers are affected.
        log_name = "lsst.daf"
        child3_log = getLogger("TRACE3.lsst.daf")
        child2_log = getLogger("TRACE2.lsst.daf")
        self.assertEqual(child3_log.getEffectiveLevel(), logging.WARNING)
        self.assertEqual(child2_log.getEffectiveLevel(), logging.WARNING)
        trace_set_at("lsst", 2)
        self.assertEqual(child3_log.getEffectiveLevel(), logging.INFO)
        self.assertEqual(child2_log.getEffectiveLevel(), logging.DEBUG)

        # Also check the root logger.
        trace_set_at("", 3)
        self.assertEqual(trace3_log.getEffectiveLevel(), logging.INFO)
        self.assertEqual(getLogger("TRACE3.test").getEffectiveLevel(), logging.DEBUG)
Beispiel #7
0
    def test_periodic(self):
        logger = getLogger("test.periodicity")
        periodic = PeriodicLogger(logger)

        # First message will not be issued.
        periodic.log("Message")
        self.assertEqual(periodic.num_issued, 0)

        # Create a new periodic logger with no delay.
        # Every message should be issued.
        periodic = PeriodicLogger(logger, interval=0.0)
        with self.assertLogs(logger.name, level=logger.VERBOSE) as cm:
            periodic.log("Message")
            periodic.log("Message %d", 1)
        self.assertEqual(len(cm.output), 2)
        self.assertEqual(periodic.num_issued, 2)
        self.assertEqual(cm.output[0], f"VERBOSE:{logger.name}:Message")
        self.assertEqual(cm.output[1], f"VERBOSE:{logger.name}:Message 1")
        self.assertEqual(cm.records[0].filename, "test_logging.py", str(cm.records[0]))

        # Create a new periodic logger with small delay.
        # One message should be issued.
        periodic = PeriodicLogger(logger, interval=0.2, level=logger.INFO)
        with self.assertLogs(logger.name, level=logger.INFO) as cm:
            periodic.log("Message")
            time.sleep(0.5)
            issued = periodic.log("Message %d", 1)
            self.assertTrue(issued)
            issued = periodic.log("Message %d", 2)
            self.assertFalse(issued)
        self.assertEqual(periodic.num_issued, 1)
        self.assertEqual(cm.output[0], f"INFO:{logger.name}:Message 1")

        # Again with a standard python Logger.
        pylog = logging.getLogger("python.logger")
        periodic = PeriodicLogger(pylog, interval=0.0, level=logging.DEBUG)
        with self.assertLogs(pylog.name, level=logging.DEBUG) as cm:
            periodic.log("Message")
        self.assertEqual(cm.records[0].filename, "test_logging.py", str(cm.records[0]))
Beispiel #8
0
def backgroundSubtract(config, maskedImages):
    """Subtract the background from masked images.

    Parameters
    ----------
    config : TODO: DM-17458
        TODO: DM-17458
    maskedImages : `list` of `lsst.afw.image.MaskedImage`
        TODO: DM-17458

    Returns
    -------
    TODO: DM-17458
        TODO: DM-17458
    """
    backgrounds = []
    t0 = time.time()
    algorithm = config.algorithm
    binsize = config.binSize
    undersample = config.undersampleStyle
    bctrl = afwMath.BackgroundControl(algorithm)
    bctrl.setUndersampleStyle(undersample)
    for maskedImage in maskedImages:
        bctrl.setNxSample(maskedImage.getWidth() // binsize + 1)
        bctrl.setNySample(maskedImage.getHeight() // binsize + 1)
        image = maskedImage.getImage()
        backobj = afwMath.makeBackground(image, bctrl)

        image -= backobj.getImageF()
        backgrounds.append(backobj.getImageF())
        del backobj

    t1 = time.time()
    logger = getLogger("lsst.ip.diffim.backgroundSubtract")
    logger.debug("Total time for background subtraction : %.2f s", (t1 - t0))
    return backgrounds
Beispiel #9
0
import sys
import unittest

import lsst.utils.tests as tests
import lsst.utils
import lsst.afw.display as afwDisplay
import lsst.afw.geom as afwGeom
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.ip.diffim as ipDiffim
import lsst.ip.diffim.diffimTools as diffimTools
import lsst.utils.logging as logUtils
import lsst.pex.config as pexConfig

logUtils.trace_set_at("lsst.ip.diffim", 6)
logger = logUtils.getLogger("lsst.ip.diffim.compareLambdaTypes")
logger.setLevel(logging.DEBUG)

display = True
writefits = False

# This one compares DeltaFunction kernels of different types; iterate lambdaVal for different strengths

CFHTTORUN = 'cal-53535-i-797722_1'


class DiffimTestCases(unittest.TestCase):

    # D = I - (K.x.T + bg)
    def setUp(self, CFHT=True):
        lambdaValue = 1.0
Beispiel #10
0
def generateAlardLuptonBasisList(config, targetFwhmPix=None, referenceFwhmPix=None,
                                 basisDegGauss=None, basisSigmaGauss=None, metadata=None):
    """Generate an Alard-Lupton kernel basis list based upon the Config and
    the input FWHM of the science and template images.

    Parameters
    ----------
    config : `lsst.ip.diffim.PsfMatchConfigAL`
        Configuration object for the Alard-Lupton algorithm.
    targetFwhmPix : `float`, optional
        Fwhm width (pixel) of the template exposure characteristic psf.
        This is the _target_ that will be matched to the science exposure.
    referenceFwhmPix : `float`, optional
        Fwhm width (pixel) of the science exposure characteristic psf.
    basisDegGauss : `list` of `int`, optional
        Polynomial degree of each Gaussian (sigma) basis. If None, defaults to `config.alardDegGauss`.
    basisSigmaGauss : `list` of `int`, optional
        Sigmas of each Gaussian basis. If None, defaults to `config.alardSigGauss`.
    metadata : `lsst.daf.base.PropertySet`, optional
        If specified, object to collect metadata fields about the kernel basis list.

    Returns
    -------
    basisList : `list` of `lsst.afw.math.kernel.FixedKernel`
        List of basis kernels. For each degree value ``n`` in ``config.basisDegGauss`` (n+2)(n+1)/2 kernels
        are generated and appended to the list in the order of the polynomial parameter number.
        See `lsst.afw.math.polynomialFunction2D` documentation for more details.

    Raises
    ------
    RuntimeError
        - if ``config.kernelBasisSet`` is not equal to "alard-lupton"
    ValueError
        - if ``config.kernelSize`` is even
        - if the number of Gaussians and the number of given
          sigma values are not equal or
        - if the number of Gaussians and the number of given
          polynomial degree values are not equal

    Notes
    -----
    The polynomial functions (``f``) are always evaluated in the -1.0, +1.0 range in both x, y directions,
    edge to edge, with ``f(0,0)`` evaluated at the kernel center pixel, ``f(-1.0,-1.0)`` at the kernel
    ``(0,0)`` pixel. They are not scaled by the sigmas of the Gaussians.

    Base Gaussian widths (sigmas in pixels) of the kernels are determined as:
        - If not all fwhm parameters are provided or ``config.scaleByFwhm==False``
          then ``basisSigmaGauss`` is used. If ``basisSigmaGauss`` is not
          provided, then ``config.alardSigGauss`` is used. In both cases, the
          length of sigmas must be equal to ``config.alardNGauss``.
        - If ``targetFwhmPix<referenceFwhmPix`` (normal convolution):
          First sigma ``Sig_K`` is determined to satisfy: ``Sig_reference**2 = Sig_target**2 + Sig_K**2``.
          If it's larger than ``config.alardMinSig * config.alardGaussBeta``, make it the
          second kernel. Else make it the smallest kernel, unless only 1 kernel is asked for.
        - If ``referenceFwhmPix < targetFwhmPix`` (deconvolution):
          Define the progression of Gaussians using a
          method to derive a deconvolution sum-of-Gaussians from it's
          convolution counterpart. [1]_ Only use 3 since the algorithm
          assumes 3 components.

    **Metadata fields**

    ALBasisNGauss : `int`
        The number of base Gaussians in the AL basis functions.
    ALBasisDegGauss : `list` of `int`
        Polynomial order of spatial modification of the base Gaussian functions.
    ALBasisSigGauss : `list` of `float`
        Sigmas in pixels of the base Gaussians.
    ALKernelSize : `int`
        Kernel stamp size is (ALKernelSize pix, ALKernelSize pix).
    ALBasisMode : `str`, either of ``config``, ``convolution``, ``deconvolution``
        Indicates whether the config file values, the convolution or deconvolution algorithm
        was used to determine the base Gaussian sigmas and the kernel stamp size.

    References
    ----------
    .. [1] Ulmer, W.: Inverse problem of linear combinations of Gaussian convolution kernels
       (deconvolution) and some applications to proton/photon dosimetry and image
       processing. http://iopscience.iop.org/0266-5611/26/8/085002  Equation 40
    """

    if config.kernelBasisSet != "alard-lupton":
        raise RuntimeError("Cannot generate %s basis within generateAlardLuptonBasisList" %
                           config.kernelBasisSet)

    kernelSize = config.kernelSize
    fwhmScaling = config.kernelSizeFwhmScaling
    basisNGauss = config.alardNGauss
    basisGaussBeta = config.alardGaussBeta
    basisMinSigma = config.alardMinSig
    if basisDegGauss is None:
        basisDegGauss = config.alardDegGauss
    if basisSigmaGauss is None:
        basisSigmaGauss = config.alardSigGauss

    if len(basisDegGauss) != basisNGauss:
        raise ValueError("len(basisDegGauss) != basisNGauss : %d vs %d" % (len(basisDegGauss), basisNGauss))
    if len(basisSigmaGauss) != basisNGauss:
        raise ValueError("len(basisSigmaGauss) != basisNGauss : %d vs %d" %
                         (len(basisSigmaGauss), basisNGauss))
    if (kernelSize % 2) != 1:
        raise ValueError("Only odd-sized Alard-Lupton bases allowed")

    logger = getLogger("lsst.ip.diffim.generateAlardLuptonBasisList")
    if (targetFwhmPix is None) or (referenceFwhmPix is None) or (not config.scaleByFwhm):
        logger.info("PSF sigmas are not available or scaling by fwhm disabled, "
                    "falling back to config values")
        if metadata is not None:
            metadata.add("ALBasisNGauss", basisNGauss)
            metadata.add("ALBasisDegGauss", basisDegGauss)
            metadata.add("ALBasisSigGauss", basisSigmaGauss)
            metadata.add("ALKernelSize", kernelSize)
            metadata.add("ALBasisMode", "config")

        return diffimLib.makeAlardLuptonBasisList(kernelSize//2, basisNGauss, basisSigmaGauss, basisDegGauss)

    targetSigma = targetFwhmPix / sigma2fwhm
    referenceSigma = referenceFwhmPix / sigma2fwhm
    logger.debug("Generating matching bases for sigma %.2f pix -> %.2f pix", targetSigma, referenceSigma)

    # Modify the size of Alard Lupton kernels based upon the images FWHM
    #
    # Note the operation is : template.x.kernel = science
    #
    # Assuming the template and science image Psfs are Gaussians with
    # the Fwhm above, Fwhm_T **2 + Fwhm_K **2 = Fwhm_S **2
    #
    if targetSigma == referenceSigma:
        # Leave defaults as-is
        logger.debug("Target and reference psf fwhms are equal, falling back to config values")
        basisMode = "config"
    elif referenceSigma > targetSigma:
        # Normal convolution

        # First Gaussian has the sigma that comes from the convolution
        # of two Gaussians : Sig_S**2 = Sig_T**2 + Sig_K**2
        #
        # If it's larger than basisMinSigma * basisGaussBeta, make it the
        # second kernel.  Else make it the smallest kernel.  Unless
        # only 1 kernel is asked for.
        logger.debug("Reference psf fwhm is the greater, normal convolution mode")
        basisMode = "convolution"
        kernelSigma = np.sqrt(referenceSigma**2 - targetSigma**2)
        if kernelSigma < basisMinSigma:
            kernelSigma = basisMinSigma

        basisSigmaGauss = []
        if basisNGauss == 1:
            basisSigmaGauss.append(kernelSigma)
            nAppended = 1
        else:
            if (kernelSigma/basisGaussBeta) > basisMinSigma:
                basisSigmaGauss.append(kernelSigma/basisGaussBeta)
                basisSigmaGauss.append(kernelSigma)
                nAppended = 2
            else:
                basisSigmaGauss.append(kernelSigma)
                nAppended = 1

        # Any other Gaussians above basisNGauss=1 come from a scaling
        # relationship: Sig_i+1 / Sig_i = basisGaussBeta
        for i in range(nAppended, basisNGauss):
            basisSigmaGauss.append(basisSigmaGauss[-1]*basisGaussBeta)

        kernelSize = int(fwhmScaling * basisSigmaGauss[-1])
        kernelSize += 0 if kernelSize%2 else 1  # Make sure it's odd
        kernelSize = min(config.kernelSizeMax, max(kernelSize, config.kernelSizeMin))

    else:
        # Deconvolution; Define the progression of Gaussians using a
        # method to derive a deconvolution sum-of-Gaussians from it's
        # convolution counterpart.  Only use 3 since the algorithm
        # assumes 3 components.
        #
        # http://iopscience.iop.org/0266-5611/26/8/085002  Equation 40

        # Use specializations for deconvolution
        logger.debug("Target psf fwhm is the greater, deconvolution mode")
        basisMode = "deconvolution"
        basisNGauss = config.alardNGaussDeconv
        basisMinSigma = config.alardMinSigDeconv

        kernelSigma = np.sqrt(targetSigma**2 - referenceSigma**2)
        if kernelSigma < basisMinSigma:
            kernelSigma = basisMinSigma

        basisSigmaGauss = []
        if (kernelSigma/basisGaussBeta) > basisMinSigma:
            basisSigmaGauss.append(kernelSigma/basisGaussBeta)
            basisSigmaGauss.append(kernelSigma)
            nAppended = 2
        else:
            basisSigmaGauss.append(kernelSigma)
            nAppended = 1

        for i in range(nAppended, basisNGauss):
            basisSigmaGauss.append(basisSigmaGauss[-1]*basisGaussBeta)

        kernelSize = int(fwhmScaling * basisSigmaGauss[-1])
        kernelSize += 0 if kernelSize%2 else 1  # Make sure it's odd
        kernelSize = min(config.kernelSizeMax, max(kernelSize, config.kernelSizeMin))

        # Now build a deconvolution set from these sigmas
        sig0 = basisSigmaGauss[0]
        sig1 = basisSigmaGauss[1]
        sig2 = basisSigmaGauss[2]
        basisSigmaGauss = []
        for n in range(1, 3):
            for j in range(n):
                sigma2jn = (n - j)*sig1**2
                sigma2jn += j * sig2**2
                sigma2jn -= (n + 1)*sig0**2
                sigmajn = np.sqrt(sigma2jn)
                basisSigmaGauss.append(sigmajn)

        basisSigmaGauss.sort()
        basisNGauss = len(basisSigmaGauss)
        basisDegGauss = [config.alardDegGaussDeconv for x in basisSigmaGauss]

    if metadata is not None:
        metadata.add("ALBasisNGauss", basisNGauss)
        metadata.add("ALBasisDegGauss", basisDegGauss)
        metadata.add("ALBasisSigGauss", basisSigmaGauss)
        metadata.add("ALKernelSize", kernelSize)
        metadata.add("ALBasisMode", basisMode)

    logger.debug("basisSigmaGauss: %s basisDegGauss: %s",
                 ','.join(['{:.1f}'.format(v) for v in basisSigmaGauss]),
                 ','.join(['{:d}'.format(v) for v in basisDegGauss]))

    return diffimLib.makeAlardLuptonBasisList(kernelSize//2, basisNGauss, basisSigmaGauss, basisDegGauss)
import lsst.utils
import lsst.afw.display as afwDisplay
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.ip.diffim as ipDiffim
import lsst.ip.diffim.diffimTools as diffimTools
import lsst.utils.logging as logUtils
import lsst.pex.config as pexConfig

afwDisplay.setDefaultMaskTransparency(75)

verbosity = 2
logUtils.trace_set_at("lsst.ip.diffim", verbosity)

logger = logUtils.getLogger("lsst.ip.diffim.JackknifeResampleKernel")

display = False
writefits = False

try:
    defDataDir = lsst.utils.getPackageDir('afwdata')
except Exception:
    defDataDir = None

if defDataDir:
    defTemplatePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v5-e0",
                                   "v5-e0-c011-a00.sci.fits")
    defSciencePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v26-e0",
                                  "v26-e0-c011-a00.sci.fits")
Beispiel #12
0
from __future__ import annotations
"""Module defining a butler like object specialized to a specific quantum.
"""

__all__ = ("ButlerQuantumContext", )

from typing import Any, List, Sequence, Union

from lsst.daf.butler import Butler, DatasetRef, Quantum
from lsst.utils.introspection import get_full_type_name
from lsst.utils.logging import PeriodicLogger, getLogger

from .connections import DeferredDatasetRef, InputQuantizedConnection, OutputQuantizedConnection
from .struct import Struct

_LOG = getLogger(__name__)


class ButlerQuantumContext:
    """A Butler-like class specialized for a single quantum

    A ButlerQuantumContext class wraps a standard butler interface and
    specializes it to the context of a given quantum. What this means
    in practice is that the only gets and puts that this class allows
    are DatasetRefs that are contained in the quantum.

    In the future this class will also be used to record provenance on
    what was actually get and put. This is in contrast to what the
    preflight expects to be get and put by looking at the graph before
    execution.
import os
import unittest

import lsst.utils.tests
import lsst.utils
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
import lsst.geom as geom
import lsst.utils.logging as logUtils
import lsst.meas.algorithms as measAlg
import lsst.ip.diffim as ipDiffim
import lsst.ip.diffim.diffimTools as diffimTools

verbosity = 4
logUtils.trace_set_at("lsst.ip.diffim", verbosity)
logUtils.getLogger('lsst.psfMatch').setLevel(logging.INFO)

display = False

# known input images
try:
    defDataDir = lsst.utils.getPackageDir('afwdata')
except Exception:
    defDataDir = None


class DiffimTestCases(lsst.utils.tests.TestCase):

    # D = I - (K.x.T + bg)

    def setUp(self):
Beispiel #14
0
def plotPixelResiduals(exposure,
                       warpedTemplateExposure,
                       diffExposure,
                       kernelCellSet,
                       kernel,
                       background,
                       testSources,
                       config,
                       origVariance=False,
                       nptsFull=1e6,
                       keepPlots=True,
                       titleFs=14):
    """Plot diffim residuals for LOCAL and SPATIAL models.
    """
    candidateResids = []
    spatialResids = []
    nonfitResids = []

    for cell in kernelCellSet.getCellList():
        for cand in cell.begin(True):  # only look at good ones
            # Be sure
            if not (cand.getStatus() == afwMath.SpatialCellCandidate.GOOD):
                continue

            diffim = cand.getDifferenceImage(diffimLib.KernelCandidateF.ORIG)
            orig = cand.getScienceMaskedImage()

            ski = afwImage.ImageD(kernel.getDimensions())
            kernel.computeImage(ski, False, int(cand.getXCenter()),
                                int(cand.getYCenter()))
            sk = afwMath.FixedKernel(ski)
            sbg = background(int(cand.getXCenter()), int(cand.getYCenter()))
            sdiffim = cand.getDifferenceImage(sk, sbg)

            # trim edgs due to convolution
            bbox = kernel.shrinkBBox(diffim.getBBox())
            tdiffim = diffim.Factory(diffim, bbox)
            torig = orig.Factory(orig, bbox)
            tsdiffim = sdiffim.Factory(sdiffim, bbox)

            if origVariance:
                candidateResids.append(
                    np.ravel(tdiffim.getImage().getArray() /
                             np.sqrt(torig.getVariance().getArray())))
                spatialResids.append(
                    np.ravel(tsdiffim.getImage().getArray() /
                             np.sqrt(torig.getVariance().getArray())))
            else:
                candidateResids.append(
                    np.ravel(tdiffim.getImage().getArray() /
                             np.sqrt(tdiffim.getVariance().getArray())))
                spatialResids.append(
                    np.ravel(tsdiffim.getImage().getArray() /
                             np.sqrt(tsdiffim.getVariance().getArray())))

    fullIm = diffExposure.getMaskedImage().getImage().getArray()
    fullMask = diffExposure.getMaskedImage().getMask().getArray()
    if origVariance:
        fullVar = exposure.getMaskedImage().getVariance().getArray()
    else:
        fullVar = diffExposure.getMaskedImage().getVariance().getArray()

    bitmaskBad = 0
    bitmaskBad |= afwImage.Mask.getPlaneBitMask('NO_DATA')
    bitmaskBad |= afwImage.Mask.getPlaneBitMask('SAT')
    idx = np.where((fullMask & bitmaskBad) == 0)
    stride = int(len(idx[0]) // nptsFull)
    sidx = idx[0][::stride], idx[1][::stride]
    allResids = fullIm[sidx] / np.sqrt(fullVar[sidx])

    testFootprints = diffimTools.sourceToFootprintList(
        testSources, warpedTemplateExposure, exposure, config,
        getLogger(__name__).getChild("plotPixelResiduals"))
    for fp in testFootprints:
        subexp = diffExposure.Factory(diffExposure, fp["footprint"].getBBox())
        subim = subexp.getMaskedImage().getImage()
        if origVariance:
            subvar = afwImage.ExposureF(
                exposure,
                fp["footprint"].getBBox()).getMaskedImage().getVariance()
        else:
            subvar = subexp.getMaskedImage().getVariance()
        nonfitResids.append(
            np.ravel(subim.getArray() / np.sqrt(subvar.getArray())))

    candidateResids = np.ravel(np.array(candidateResids))
    spatialResids = np.ravel(np.array(spatialResids))
    nonfitResids = np.ravel(np.array(nonfitResids))

    try:
        import pylab
        from matplotlib.font_manager import FontProperties
    except ImportError as e:
        print("Unable to import pylab: %s" % e)
        return

    fig = pylab.figure()
    fig.clf()
    try:
        fig.canvas._tkcanvas._root().lift(
        )  # == Tk's raise, but raise is a python reserved word
    except Exception:  # protect against API changes
        pass
    if origVariance:
        fig.suptitle("Diffim residuals: Normalized by sqrt(input variance)",
                     fontsize=titleFs)
    else:
        fig.suptitle("Diffim residuals: Normalized by sqrt(diffim variance)",
                     fontsize=titleFs)

    sp1 = pylab.subplot(221)
    sp2 = pylab.subplot(222, sharex=sp1, sharey=sp1)
    sp3 = pylab.subplot(223, sharex=sp1, sharey=sp1)
    sp4 = pylab.subplot(224, sharex=sp1, sharey=sp1)
    xs = np.arange(-5, 5.05, 0.1)
    ys = 1. / np.sqrt(2 * np.pi) * np.exp(-0.5 * xs**2)

    sp1.hist(candidateResids,
             bins=xs,
             normed=True,
             alpha=0.5,
             label="N(%.2f, %.2f)" %
             (np.mean(candidateResids), np.var(candidateResids)))
    sp1.plot(xs, ys, "r-", lw=2, label="N(0,1)")
    sp1.set_title("Candidates: basis fit", fontsize=titleFs - 2)
    sp1.legend(loc=1,
               fancybox=True,
               shadow=True,
               prop=FontProperties(size=titleFs - 6))

    sp2.hist(spatialResids,
             bins=xs,
             normed=True,
             alpha=0.5,
             label="N(%.2f, %.2f)" %
             (np.mean(spatialResids), np.var(spatialResids)))
    sp2.plot(xs, ys, "r-", lw=2, label="N(0,1)")
    sp2.set_title("Candidates: spatial fit", fontsize=titleFs - 2)
    sp2.legend(loc=1,
               fancybox=True,
               shadow=True,
               prop=FontProperties(size=titleFs - 6))

    sp3.hist(nonfitResids,
             bins=xs,
             normed=True,
             alpha=0.5,
             label="N(%.2f, %.2f)" %
             (np.mean(nonfitResids), np.var(nonfitResids)))
    sp3.plot(xs, ys, "r-", lw=2, label="N(0,1)")
    sp3.set_title("Control sample: spatial fit", fontsize=titleFs - 2)
    sp3.legend(loc=1,
               fancybox=True,
               shadow=True,
               prop=FontProperties(size=titleFs - 6))

    sp4.hist(allResids,
             bins=xs,
             normed=True,
             alpha=0.5,
             label="N(%.2f, %.2f)" % (np.mean(allResids), np.var(allResids)))
    sp4.plot(xs, ys, "r-", lw=2, label="N(0,1)")
    sp4.set_title("Full image (subsampled)", fontsize=titleFs - 2)
    sp4.legend(loc=1,
               fancybox=True,
               shadow=True,
               prop=FontProperties(size=titleFs - 6))

    pylab.setp(sp1.get_xticklabels() + sp1.get_yticklabels(),
               fontsize=titleFs - 4)
    pylab.setp(sp2.get_xticklabels() + sp2.get_yticklabels(),
               fontsize=titleFs - 4)
    pylab.setp(sp3.get_xticklabels() + sp3.get_yticklabels(),
               fontsize=titleFs - 4)
    pylab.setp(sp4.get_xticklabels() + sp4.get_yticklabels(),
               fontsize=titleFs - 4)

    sp1.set_xlim(-5, 5)
    sp1.set_ylim(0, 0.5)
    fig.show()

    global keptPlots
    if keepPlots and not keptPlots:
        # Keep plots open when done
        def show():
            print("%s: Please close plots when done." % __name__)
            try:
                pylab.show()
            except Exception:
                pass
            print("Plots closed, exiting...")

        import atexit
        atexit.register(show)
        keptPlots = True