def makeTestImages(self, seed=5, nSrc=5, psfSize=2., noiseLevel=5.,
                       detectionSigma=5., sourceSigma=20., fluxRange=2.):
        """Make reproduceable PSF-convolved masked images for testing.

        Parameters
        ----------
        seed : `int`, optional
            Seed value to initialize the random number generator.
        nSrc : `int`, optional
            Number of sources to simulate.
        psfSize : `float`, optional
            Width of the PSF of the simulated sources, in pixels.
        noiseLevel : `float`, optional
            Standard deviation of the noise to add to each pixel.
        detectionSigma : `float`, optional
            Threshold amplitude of the image to set the "DETECTED" mask.
        sourceSigma : `float`, optional
            Average amplitude of the simulated sources,
            relative to ``noiseLevel``
        fluxRange : `float`, optional
            Range in flux amplitude of the simulated sources.

        Returns
        -------
        modelImages : `list` of `lsst.afw.image.Image`
            A list of images, each containing the model for one subfilter
        """
        rng = np.random.RandomState(seed)
        x0, y0 = self.bbox.getBegin()
        xSize, ySize = self.bbox.getDimensions()
        xLoc = rng.rand(nSrc)*(xSize - 2*self.bufferSize) + self.bufferSize + x0
        yLoc = rng.rand(nSrc)*(ySize - 2*self.bufferSize) + self.bufferSize + y0
        modelImages = []

        imageSum = np.zeros((ySize, xSize))
        for subfilter in range(self.dcrNumSubfilters):
            flux = (rng.rand(nSrc)*(fluxRange - 1.) + 1.)*sourceSigma*noiseLevel
            sigmas = [psfSize for src in range(nSrc)]
            coordList = list(zip(xLoc, yLoc, flux, sigmas))
            model = plantSources(self.bbox, 10, 0, coordList, addPoissonNoise=False)
            model.image.array += rng.rand(ySize, xSize)*noiseLevel
            imageSum += model.image.array
            model.mask.addMaskPlane("CLIPPED")
            modelImages.append(model.image)
        maskVals = np.zeros_like(imageSum)
        maskVals[imageSum > detectionSigma*noiseLevel] = afwImage.Mask.getPlaneBitMask('DETECTED')
        model.mask.array[:] = maskVals
        self.mask = model.mask
        return modelImages
Exemple #2
0
    def testBasics(self):
        bbox = afwGeom.Box2I(afwGeom.Point2I(256, 100),
                             afwGeom.Extent2I(128, 127))
        minCounts = 5000
        maxCounts = 50000
        starSigma = 1.5
        numX = 5
        numY = 5
        coordList = self.makeCoordList(
            bbox=bbox,
            numX=numX,
            numY=numY,
            minCounts=minCounts,
            maxCounts=maxCounts,
            sigma=starSigma,
        )
        kwid = 11
        sky = 2000
        addPoissonNoise = True
        exposure = plantSources(bbox=bbox,
                                kwid=kwid,
                                sky=sky,
                                coordList=coordList,
                                addPoissonNoise=addPoissonNoise)

        schema = afwTable.SourceTable.makeMinimalSchema()
        config = SourceDetectionTask.ConfigClass()
        config.reEstimateBackground = False
        task = SourceDetectionTask(config=config, schema=schema)
        for doSmooth in (False, True):
            taskSigma = 2.2
            res = task.detectFootprints(exposure,
                                        doSmooth=doSmooth,
                                        sigma=taskSigma)
            self.assertEqual(res.numPos, numX * numY)
            self.assertEqual(res.numNeg, 0)
            self.assertEqual(task.metadata.get("sigma"), taskSigma)
            self.assertEqual(task.metadata.get("doSmooth"), doSmooth)
            self.assertEqual(task.metadata.get("nGrow"),
                             int(taskSigma * config.nSigmaToGrow + 0.5))

            res = task.detectFootprints(exposure,
                                        doSmooth=doSmooth,
                                        sigma=None)
            taskSigma = task.metadata.get("sigma")
            self.assertTrue(abs(taskSigma - starSigma) < 0.1)
            self.assertEqual(res.numPos, numX * numY)
            self.assertEqual(res.numNeg, 0)
    def setUp(self):
        xy0 = Point2I(12345, 67890)  # xy0 for image
        dims = Extent2I(2345, 2345)  # Dimensions of image
        box = Box2I(xy0, dims)  # Bounding box of image
        sigma = 3.21  # PSF sigma
        buffer = 4.0  # Buffer for star centers around edge
        nSigmaForKernel = 5.0  # Number of PSF sigmas for kernel
        sky = 12345.6  # Sky level
        numStars = 100  # Number of stars
        noise = np.sqrt(sky)*np.pi*sigma**2  # Poisson noise per PSF
        faint = 1.0*noise  # Faintest level for star fluxes
        bright = 100.0*noise  # Brightest level for star fluxes
        starBox = Box2I(box)  # Area on image in which we can put star centers
        starBox.grow(-int(buffer*sigma))
        scale = 1.0e-5*degrees  # Pixel scale

        np.random.seed(12345)
        stars = [(xx, yy, ff, sigma) for xx, yy, ff in
                 zip(np.random.uniform(starBox.getMinX(), starBox.getMaxX(), numStars),
                     np.random.uniform(starBox.getMinY(), starBox.getMaxY(), numStars),
                     np.linspace(faint, bright, numStars))]
        self.exposure = plantSources(box, 2*int(nSigmaForKernel*sigma) + 1, sky, stars, True)
        self.exposure.setWcs(makeSkyWcs(crpix=Point2D(0, 0),
                                        crval=SpherePoint(0, 0, degrees),
                                        cdMatrix=makeCdMatrix(scale=scale)))

        # Make a large area of extra background; we should be robust against it
        # Unfortunately, some tuning is required here to get something challenging but not impossible:
        # * A very large box will cause failures because the "extra" and the "normal" are reversed.
        # * A small box will not be challenging because it's simple to clip out.
        # * A large value will cause failures because it produces large edges in background-subtrction that
        #     broaden flux distributions.
        # * A small value will not be challenging because it has little effect.
        extraBox = Box2I(xy0 + Extent2I(345, 456), Extent2I(1234, 1234))  # Box for extra background
        extraValue = 0.5*noise  # Extra background value to add in
        self.exposure.image[extraBox, PARENT] += extraValue

        self.config = DynamicDetectionTask.ConfigClass()
        self.config.skyObjects.nSources = 300
        self.config.reEstimateBackground = False
        self.config.doTempWideBackground = True
        self.config.thresholdType = "pixel_stdev"

        # Relative tolerance for tweak factor
        # Not sure why this isn't smaller; maybe due to use of Poisson instead of Gaussian noise?
        self.rtol = 0.1
    def testBasics(self):
        """Test detection and measurement on simple synthesized data
        """
        bbox = Box2I(Point2I(256, 100), Extent2I(128, 127))
        minCounts = 5000
        maxCounts = 50000
        starSigma = 1.5
        numX = 5
        numY = 5
        coordList = self.makeCoordList(
            bbox=bbox,
            numX=numX,
            numY=numY,
            minCounts=minCounts,
            maxCounts=maxCounts,
            sigma=starSigma,
        )
        kwid = 11  # kernel width
        sky = 2000
        # create an exposure without a Wcs; add the Wcs later
        exposure = plantSources(bbox=bbox, kwid=kwid, sky=sky, coordList=coordList, addPoissonNoise=True)

        schema = SourceTable.makeMinimalSchema()

        config = DetectAndMeasureTask.ConfigClass()
        task = DetectAndMeasureTask(config=config, schema=schema)

        butler = Butler(root=InputDir)
        dataRef = butler.dataRef("calexp", dataId=dict(visit=1))
        wcs = dataRef.get("raw").getWcs()
        exposure.setWcs(wcs)
        exposureIdInfo = dataRef.get("expIdInfo")
        taskRes = task.run(exposure=exposure, exposureIdInfo=exposureIdInfo)
        self.assertEqual(len(taskRes.sourceCat), numX * numY)
        schema = taskRes.sourceCat.schema
        centroidFlagKey = schema.find("slot_Centroid_flag").getKey()
        parentKey = schema.find("parent").getKey()
        psfFluxFlagKey = schema.find("slot_PsfFlux_flag").getKey()
        psfFluxKey = schema.find("slot_PsfFlux_flux").getKey()
        for src in taskRes.sourceCat:
            self.assertFalse(src.get(centroidFlagKey))  # centroid found
            self.assertEqual(src.get(parentKey), 0)     # not debelended
            self.assertFalse(src.get(psfFluxFlagKey))   # flux measured
            self.assertGreater(src.get(psfFluxKey), 4000)  # flux sane
    def setUp(self):
        self.nx = 64
        self.ny = 64
        self.kwid = 15
        self.sky = 100.0
        self.val = 10000.0
        self.sigma = 4.0
        coordList = [[self.nx / 2, self.ny / 2, self.val, self.sigma]]

        # exposure with gaussian
        bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(self.nx, self.ny))
        self.expGaussPsf = plantSources(bbox, self.kwid, self.sky, coordList, addPoissonNoise=False)

        # just plain sky (ie. a constant)
        self.mimg = afwImage.MaskedImageF(afwGeom.ExtentI(self.nx, self.ny))
        self.mimg.set(self.sky, 0x0, self.sky)
        self.expSky = afwImage.makeExposure(self.mimg)

        if display > 1:
            ds9.mtv(self.expGaussPsf)
    def testBasics(self):
        bbox = lsst.geom.Box2I(lsst.geom.Point2I(256, 100), lsst.geom.Extent2I(128, 127))
        minCounts = 5000
        maxCounts = 50000
        starSigma = 1.5
        numX = 5
        numY = 5
        coordList = self.makeCoordList(
            bbox=bbox,
            numX=numX,
            numY=numY,
            minCounts=minCounts,
            maxCounts=maxCounts,
            sigma=starSigma,
        )
        kwid = 11
        sky = 2000
        addPoissonNoise = True
        exposure = plantSources(bbox=bbox, kwid=kwid, sky=sky, coordList=coordList,
                                addPoissonNoise=addPoissonNoise)

        schema = afwTable.SourceTable.makeMinimalSchema()
        config = SourceDetectionTask.ConfigClass()
        config.reEstimateBackground = False
        task = SourceDetectionTask(config=config, schema=schema)
        for doSmooth in (False, True):
            taskSigma = 2.2
            res = task.detectFootprints(exposure, doSmooth=doSmooth, sigma=taskSigma)
            self.assertEqual(res.numPos, numX * numY)
            self.assertEqual(res.numNeg, 0)
            self.assertEqual(task.metadata.getScalar("sigma"), taskSigma)
            self.assertEqual(task.metadata.getScalar("doSmooth"), doSmooth)
            self.assertEqual(task.metadata.getScalar("nGrow"), int(taskSigma * config.nSigmaToGrow + 0.5))

            res = task.detectFootprints(exposure, doSmooth=doSmooth, sigma=None)
            taskSigma = task.metadata.getScalar("sigma")
            self.assertLess(abs(taskSigma - starSigma), 0.1)
            self.assertEqual(res.numPos, numX * numY)
            self.assertEqual(res.numNeg, 0)
Exemple #7
0
    def makeTestImages(self,
                       seed=5,
                       nSrc=5,
                       psfSize=2.,
                       noiseLevel=5.,
                       detectionSigma=5.,
                       sourceSigma=20.,
                       fluxRange=2.):
        """Make reproduceable PSF-convolved masked images for testing.

        Parameters
        ----------
        seed : `int`, optional
            Seed value to initialize the random number generator.
        nSrc : `int`, optional
            Number of sources to simulate.
        psfSize : `float`, optional
            Width of the PSF of the simulated sources, in pixels.
        noiseLevel : `float`, optional
            Standard deviation of the noise to add to each pixel.
        detectionSigma : `float`, optional
            Threshold amplitude of the image to set the "DETECTED" mask.
        sourceSigma : `float`, optional
            Average amplitude of the simulated sources,
            relative to ``noiseLevel``
        fluxRange : `float`, optional
            Range in flux amplitude of the simulated sources.

        Returns
        -------
        modelImages : `list` of `lsst.afw.image.Image`
            A list of images, each containing the model for one subfilter
        """
        rng = np.random.RandomState(seed)
        x0, y0 = self.bbox.getBegin()
        xSize, ySize = self.bbox.getDimensions()
        xLoc = rng.rand(nSrc) * (xSize -
                                 2 * self.bufferSize) + self.bufferSize + x0
        yLoc = rng.rand(nSrc) * (ySize -
                                 2 * self.bufferSize) + self.bufferSize + y0
        modelImages = []

        imageSum = np.zeros((ySize, xSize))
        for subfilter in range(self.dcrNumSubfilters):
            flux = (rng.rand(nSrc) *
                    (fluxRange - 1.) + 1.) * sourceSigma * noiseLevel
            sigmas = [psfSize for src in range(nSrc)]
            coordList = list(zip(xLoc, yLoc, flux, sigmas))
            model = plantSources(self.bbox,
                                 10,
                                 0,
                                 coordList,
                                 addPoissonNoise=False)
            model.image.array += rng.rand(ySize, xSize) * noiseLevel
            imageSum += model.image.array
            model.mask.addMaskPlane("CLIPPED")
            modelImages.append(model.image)
        maskVals = np.zeros_like(imageSum)
        maskVals[imageSum > detectionSigma *
                 noiseLevel] = afwImage.Mask.getPlaneBitMask('DETECTED')
        model.mask.array[:] = maskVals
        self.mask = model.mask
        return modelImages
Exemple #8
0
    def makeTestImage(self,
                      expId,
                      noiseLevel=None,
                      psfSize=None,
                      backgroundLevel=None,
                      detectionSigma=5.,
                      badRegionBox=None):
        """Make a reproduceable PSF-convolved masked image for testing.

        Parameters
        ----------
        expId : `int`
            A unique identifier to use to refer to the visit.
        noiseLevel : `float`, optional
            Standard deviation of the noise to add to each pixel.
        psfSize : `float`, optional
            Width of the PSF of the simulated sources, in pixels.
        backgroundLevel : `float`, optional
            Background value added to all pixels in the simulated images.
        detectionSigma : `float`, optional
            Threshold amplitude of the image to set the "DETECTED" mask.
        badRegionBox : `lsst.geom.Box2I`, optional
            Add a bad region bounding box (set to "BAD").
        """
        if backgroundLevel is None:
            backgroundLevel = self.backgroundLevel
        if noiseLevel is None:
            noiseLevel = 5.
        visitInfo = self.makeDummyVisitInfo(expId, randomizeTime=True)

        if psfSize is None:
            psfSize = self.rngMods.random() * (
                self.maxPsfSize - self.minPsfSize) + self.minPsfSize
        nSrc = len(self.flux)
        sigmas = [psfSize for src in range(nSrc)]
        sigmasPsfMatched = [self.maxPsfSize for src in range(nSrc)]
        coordList = list(zip(self.xLoc, self.yLoc, self.flux, sigmas))
        coordListPsfMatched = list(
            zip(self.xLoc, self.yLoc, self.flux, sigmasPsfMatched))
        xSize, ySize = self.bbox.getDimensions()
        model = plantSources(self.bbox,
                             self.kernelSize,
                             self.backgroundLevel,
                             coordList,
                             addPoissonNoise=False)
        modelPsfMatched = plantSources(self.bbox,
                                       self.kernelSize,
                                       self.backgroundLevel,
                                       coordListPsfMatched,
                                       addPoissonNoise=False)
        model.variance.array = np.abs(model.image.array) + noiseLevel
        modelPsfMatched.variance.array = np.abs(
            modelPsfMatched.image.array) + noiseLevel
        noise = self.rngData.random((ySize, xSize)) * noiseLevel
        noise -= np.median(noise)
        model.image.array += noise
        modelPsfMatched.image.array += noise
        detectedMask = afwImage.Mask.getPlaneBitMask("DETECTED")
        detectionThreshold = self.backgroundLevel + detectionSigma * noiseLevel
        model.mask.array[
            model.image.array > detectionThreshold] += detectedMask

        if badRegionBox is not None:
            model.mask[badRegionBox] = afwImage.Mask.getPlaneBitMask("BAD")

        exposure = self.makeCoaddTempExp(model, visitInfo, expId)
        matchedExposure = self.makeCoaddTempExp(modelPsfMatched, visitInfo,
                                                expId)
        return exposure, matchedExposure
Exemple #9
0
    def testBasics(self):
        bbox = afwGeom.Box2I(afwGeom.Point2I(256, 100),
                             afwGeom.Extent2I(128, 127))
        minCounts = 2000
        maxCounts = 20000
        starSigma = 1.5
        numX = 4
        numY = 4
        coordList = self.makeCoordList(
            bbox=bbox,
            numX=numX,
            numY=numY,
            minCounts=minCounts,
            maxCounts=maxCounts,
            sigma=starSigma,
        )
        kwid = 11
        sky = 2000
        addPoissonNoise = True
        exposure = plantSources(bbox=bbox,
                                kwid=kwid,
                                sky=sky,
                                coordList=coordList,
                                addPoissonNoise=addPoissonNoise)

        if display:
            ds9.mtv(exposure)

        schema = afwTable.SourceTable.makeMinimalSchema()
        config = SourceDetectionTask.ConfigClass()
        config.reEstimateBackground = False
        config.thresholdPolarity = 'both'
        detection = SourceDetectionTask(config=config, schema=schema)
        algMetadata = dafBase.PropertyList()
        measurement = SourceMeasurementTask(schema=schema,
                                            algMetadata=algMetadata)

        table = afwTable.SourceTable.make(schema)
        detections = detection.makeSourceCatalog(table, exposure)
        sources = detections.sources
        fpSets = detections.fpSets

        self.assertEqual(len(sources), numX * numY)
        self.assertEqual(fpSets.numPos, numX * numY / 2)
        self.assertEqual(fpSets.numNeg, numX * numY / 2)

        measurement.run(sources, exposure)

        nGoodCent = 0
        nGoodShape = 0
        for s in sources:
            cent = s.getCentroid()
            shape = s.getShape()

            if cent[0] == cent[0] and cent[1] == cent[1]:
                nGoodCent += 1

            if (shape.getIxx() == shape.getIxx()
                    and shape.getIyy() == shape.getIyy()
                    and shape.getIxy() == shape.getIxy()):
                nGoodShape += 1

            if display:
                xy = cent[0] - exposure.getX0(), cent[1] - exposure.getY0()
                ds9.dot('+', *xy)
                ds9.dot(shape, *xy, ctype=ds9.RED)

        self.assertEqual(nGoodCent, numX * numY)
        self.assertEqual(nGoodShape, numX * numY)
    def testBasics(self):
        bbox = lsst.geom.Box2I(lsst.geom.Point2I(256, 100), lsst.geom.Extent2I(128, 127))
        minCounts = 2000
        maxCounts = 20000
        starSigma = 1.5
        numX = 4
        numY = 4
        coordList = self.makeCoordList(
            bbox=bbox,
            numX=numX,
            numY=numY,
            minCounts=minCounts,
            maxCounts=maxCounts,
            sigma=starSigma,
        )
        kwid = 11
        sky = 2000
        addPoissonNoise = True
        exposure = plantSources(bbox=bbox, kwid=kwid, sky=sky, coordList=coordList,
                                addPoissonNoise=addPoissonNoise)

        if display:
            disp = afwDisplay.Display(frame=1)
            disp.mtv(exposure, title=self._testMethodName + ": image with -ve sources")

        schema = afwTable.SourceTable.makeMinimalSchema()
        config = SourceDetectionTask.ConfigClass()
        config.reEstimateBackground = False
        config.thresholdPolarity = 'both'
        detection = SourceDetectionTask(config=config, schema=schema)
        algMetadata = dafBase.PropertyList()
        measurement = SourceMeasurementTask(schema=schema, algMetadata=algMetadata)

        table = afwTable.SourceTable.make(schema)
        detections = detection.makeSourceCatalog(table, exposure)
        sources = detections.sources
        fpSets = detections.fpSets

        self.assertEqual(len(sources), numX*numY)
        self.assertEqual(fpSets.numPos, numX*numY/2)
        self.assertEqual(fpSets.numNeg, numX*numY/2)

        measurement.run(sources, exposure)

        nGoodCent = 0
        nGoodShape = 0
        for s in sources:
            cent = s.getCentroid()
            shape = s.getShape()

            if cent[0] == cent[0] and cent[1] == cent[1]:
                nGoodCent += 1

            if (shape.getIxx() == shape.getIxx() and
                shape.getIyy() == shape.getIyy() and
                    shape.getIxy() == shape.getIxy()):
                nGoodShape += 1

            if display:
                xy = cent[0], cent[1]
                disp.dot('+', *xy)
                disp.dot(shape, *xy, ctype=afwDisplay.RED)

        self.assertEqual(nGoodCent, numX*numY)
        self.assertEqual(nGoodShape, numX*numY)
Exemple #11
0
def makeTestImage(
    seed=5,
    nSrc=20,
    psfSize=2.,
    noiseLevel=5.,
    noiseSeed=6,
    fluxLevel=500.,
    fluxRange=2.,
    kernelSize=32,
    templateBorderSize=0,
    background=None,
    xSize=256,
    ySize=256,
    x0=12345,
    y0=67890,
    calibration=1.,
    doApplyCalibration=False,
):
    """Make a reproduceable PSF-convolved exposure for testing.

    Parameters
    ----------
    seed : `int`, optional
        Seed value to initialize the random number generator for sources.
    nSrc : `int`, optional
        Number of sources to simulate.
    psfSize : `float`, optional
        Width of the PSF of the simulated sources, in pixels.
    noiseLevel : `float`, optional
        Standard deviation of the noise to add to each pixel.
    noiseSeed : `int`, optional
        Seed value to initialize the random number generator for noise.
    fluxLevel : `float`, optional
        Reference flux of the simulated sources.
    fluxRange : `float`, optional
        Range in flux amplitude of the simulated sources.
    kernelSize : `int`, optional
        Size in pixels of the kernel for simulating sources.
    templateBorderSize : `int`, optional
        Size in pixels of the image border used to pad the image.
    background : `lsst.afw.math.Chebyshev1Function2D`, optional
        Optional background to add to the output image.
    xSize, ySize : `int`, optional
        Size in pixels of the simulated image.
    x0, y0 : `int`, optional
        Origin of the image.
    calibration : `float`, optional
        Conversion factor between instFlux and nJy.
    doApplyCalibration : `bool`, optional
        Apply the photometric calibration and return the image in nJy?

    Returns
    -------
    modelExposure : `lsst.afw.image.Exposure`
        The model image, with the mask and variance planes.
    sourceCat : `lsst.afw.table.SourceCatalog`
        Catalog of sources detected on the model image.
    """
    # Distance from the inner edge of the bounding box to avoid placing test
    # sources in the model images.
    bufferSize = kernelSize / 2 + templateBorderSize + 1

    bbox = lsst.geom.Box2I(lsst.geom.Point2I(x0, y0),
                           lsst.geom.Extent2I(xSize, ySize))
    if templateBorderSize > 0:
        bbox.grow(templateBorderSize)

    rng = np.random.RandomState(seed)
    rngNoise = np.random.RandomState(noiseSeed)
    x0, y0 = bbox.getBegin()
    xSize, ySize = bbox.getDimensions()
    xLoc = rng.rand(nSrc) * (xSize - 2 * bufferSize) + bufferSize + x0
    yLoc = rng.rand(nSrc) * (ySize - 2 * bufferSize) + bufferSize + y0

    flux = (rng.rand(nSrc) * (fluxRange - 1.) + 1.) * fluxLevel
    sigmas = [psfSize for src in range(nSrc)]
    coordList = list(zip(xLoc, yLoc, flux, sigmas))
    skyLevel = 0
    # Don't use the built in poisson noise: it modifies the global state of numpy random
    modelExposure = plantSources(bbox,
                                 kernelSize,
                                 skyLevel,
                                 coordList,
                                 addPoissonNoise=False)
    modelExposure.setWcs(makeFakeWcs())
    noise = rngNoise.randn(ySize, xSize) * noiseLevel
    noise -= np.mean(noise)
    modelExposure.variance.array = np.sqrt(np.abs(
        modelExposure.image.array)) + noiseLevel**2
    modelExposure.image.array += noise

    # Run source detection to set up the mask plane
    psfMatchTask = lsst.ip.diffim.imagePsfMatch.ImagePsfMatchTask()
    sourceCat = psfMatchTask.getSelectSources(modelExposure)
    modelExposure.setPhotoCalib(PhotoCalib(calibration, 0., bbox))
    if background is not None:
        modelExposure.image += background
    modelExposure.maskedImage /= calibration
    if doApplyCalibration:
        modelExposure.maskedImage = modelExposure.photoCalib.calibrateImage(
            modelExposure.maskedImage)

    return modelExposure, sourceCat
    def makeTestImages(self,
                       seed=5,
                       nSrc=5,
                       psfSize=2.,
                       noiseLevel=5.,
                       fluxLevel=500.,
                       fluxRange=2.):
        """Make reproduceable PSF-convolved masked images for testing.

        Parameters
        ----------
        seed : `int`, optional
            Seed value to initialize the random number generator.
        nSrc : `int`, optional
            Number of sources to simulate.
        psfSize : `float`, optional
            Width of the PSF of the simulated sources, in pixels.
        noiseLevel : `float`, optional
            Standard deviation of the noise to add to each pixel.
        fluxLevel : `float`, optional
            Reference flux of the simulated sources.
        fluxRange : `float`, optional
            Range in flux amplitude of the simulated sources.

        Returns
        -------
        modelImages : `lsst.afw.image.ExposureF`
            The model image, with the mask and variance planes.
        sourceCat : `lsst.afw.table.SourceCatalog`
            Catalog of sources detected on the model image.
        """
        rng = np.random.RandomState(seed)
        x0, y0 = self.bbox.getBegin()
        xSize, ySize = self.bbox.getDimensions()
        xLoc = rng.rand(nSrc) * (xSize -
                                 2 * self.bufferSize) + self.bufferSize + x0
        yLoc = rng.rand(nSrc) * (ySize -
                                 2 * self.bufferSize) + self.bufferSize + y0

        flux = (rng.rand(nSrc) * (fluxRange - 1.) + 1.) * fluxLevel
        sigmas = [psfSize for src in range(nSrc)]
        coordList = list(zip(xLoc, yLoc, flux, sigmas))
        kernelSize = int(
            xSize / 2)  # Need a careful explanation of this kernel size choice
        skyLevel = 0
        # Don't use the built in poisson noise: it modifies the global state of numpy random
        model = plantSources(self.bbox,
                             kernelSize,
                             skyLevel,
                             coordList,
                             addPoissonNoise=False)
        noise = rng.rand(ySize, xSize) * noiseLevel
        model.image.array += noise
        model.variance.array = (np.sqrt(np.abs(model.image.array)) +
                                noiseLevel - np.mean(np.sqrt(np.abs(noise))))

        # Run source detection to set up the mask plane
        psfMatchTask = ImagePsfMatchTask(config=ImagePsfMatchConfig())
        sourceCat = psfMatchTask.getSelectSources(model)

        model.setWcs(self._makeWcs())
        return model, sourceCat