Beispiel #1
0
def setM5(m5target, skysed, totalBandpass, hardware, photParams, FWHMeff=None):
    """
    Take an SED representing the sky and normalize it so that
    m5 (the magnitude at which an object is detected in this
    bandpass at 5-sigma) is set to some specified value.

    The 5-sigma limiting magnitude (m5) for an observation is
    determined by a combination of the telescope and camera parameters
    (such as diameter of the mirrors and the readnoise) together with the
    sky background. This method (setM5) scales a provided sky background
    Sed so that an observation would have a target m5 value, for the
    provided hardware parameters. Using the resulting Sed in the
    'calcM5' method will return this target value for m5.

    @param [in] the desired value of m5

    @param [in] skysed is an instantiation of the Sed class representing
    sky emission

    @param [in] totalBandpass is an instantiation of the Bandpass class
    representing the total throughput of the telescope (instrumentation
    plus atmosphere)

    @param [in] hardware is an instantiation of the Bandpass class representing
    the throughput due solely to instrumentation.

    @param [in] photParams is an instantiation of the
    PhotometricParameters class that carries details about the
    photometric response of the telescope.

    @param [in] FWHMeff in arcseconds

    @param [out] returns an instantiation of the Sed class that is the skysed renormalized
    so that m5 has the desired value.

    Note that the returned SED will be renormalized such that calling the method
    self.calcADU(hardwareBandpass) on it will yield the number of counts per square
    arcsecond in a given bandpass.
    """

    #This is based on the LSST SNR document (v1.2, May 2010)
    #www.astro.washington.edu/users/ivezic/Astr511/LSST_SNRdoc.pdf

    if FWHMeff is None:
        FWHMeff = LSSTdefaults().FWHMeff('r')

    skyCountsTarget = calcSkyCountsPerPixelForM5(m5target,
                                                 totalBandpass,
                                                 FWHMeff=FWHMeff,
                                                 photParams=photParams)

    skySedOut = Sed(wavelen=numpy.copy(skysed.wavelen),
                    flambda=numpy.copy(skysed.flambda))

    skyCounts = skySedOut.calcADU(hardware, photParams=photParams) \
                    * photParams.platescale * photParams.platescale
    skySedOut.multiplyFluxNorm(skyCountsTarget / skyCounts)

    return skySedOut
    def compareCatalogs(self, cleanCatalog, noisyCatalog, gain, readnoise):
        """
        Read in two catalogs (one with noise, one without).  Compare the flux in each image
        pixel by pixel.  Make sure that the variation between the two is within expected limits.

        @param [in] cleanCatalog is the noiseless GalSimCatalog instantiation

        @param [in] noisyCatalog is the noisy GalSimCatalog instantiation

        @param [in] gain is the electrons per ADU for the GalSimCatalogs

        @param [in] readnoise is the electrons per pixel per exposure of the GalSimCatalogs]
        """

        cleanFileList, cleanBandpassDict = self.getFilesAndBandpasses(cleanCatalog, nameRoot='clean')
        noisyFileList, noisyBandpassDict = self.getFilesAndBandpasses(noisyCatalog, nameRoot='unclean')

        #calculate the expected skyCounts in each filter
        backgroundCounts = {}
        for filterName in noisyBandpassDict.keys():
            cts = calcSkyCountsPerPixelForM5(noisyCatalog.obs_metadata.m5[filterName], noisyBandpassDict[filterName],
                                         noisyCatalog.photParams, seeing=noisyCatalog.obs_metadata.seeing[filterName])

            backgroundCounts[filterName] = cts

        # Go through each image pixel by pixel.
        # Treat the value in the clean image as the mean intensity for that pixel.
        # Sum up (noisy-clean)^2/var
        # where var is determined by Poisson statistics from mean and readnoise.
        # Divide by the number of pixel
        # Make sure that this average does not deviate from unity

        countedImages = 0
        for noisyName, cleanName in zip(noisyFileList, cleanFileList):
            noisyIm = afwImage.ImageF(noisyName).getArray()
            cleanIm = afwImage.ImageF(cleanName).getArray()

            totalVar = 0.0
            totalMean = 0.0
            ct = 0.0

            self.assertEqual(cleanIm.shape[0], noisyIm.shape[0], msg='images not same shape')
            self.assertEqual(cleanIm.shape[1], noisyIm.shape[1], msg='images not same shape')

            var = cleanIm/gain + readnoise/(gain*gain)
            totalVar = (numpy.power(noisyIm-cleanIm,2)/var).sum()
            totalMean = cleanIm.sum()
            ct = float(cleanIm.shape[0]*cleanIm.shape[1])
            totalVar = totalVar/ct
            totalMean = totalMean/ct

            if totalMean>=100.0:
                countedImages += 1
                self.assertTrue(numpy.abs(totalVar-1.0) < 0.05)

            os.unlink(noisyName)
            os.unlink(cleanName)

        self.assertTrue(countedImages>0)
def setM5(m5target, skysed, totalBandpass, hardware,
          photParams,
          FWHMeff = None):
    """
    Take an SED representing the sky and normalize it so that
    m5 (the magnitude at which an object is detected in this
    bandpass at 5-sigma) is set to some specified value.

    The 5-sigma limiting magnitude (m5) for an observation is
    determined by a combination of the telescope and camera parameters
    (such as diameter of the mirrors and the readnoise) together with the
    sky background. This method (setM5) scales a provided sky background
    Sed so that an observation would have a target m5 value, for the
    provided hardware parameters. Using the resulting Sed in the
    'calcM5' method will return this target value for m5.

    @param [in] the desired value of m5

    @param [in] skysed is an instantiation of the Sed class representing
    sky emission

    @param [in] totalBandpass is an instantiation of the Bandpass class
    representing the total throughput of the telescope (instrumentation
    plus atmosphere)

    @param [in] hardware is an instantiation of the Bandpass class representing
    the throughput due solely to instrumentation.

    @param [in] photParams is an instantiation of the
    PhotometricParameters class that carries details about the
    photometric response of the telescope.

    @param [in] FWHMeff in arcseconds

    @param [out] returns an instantiation of the Sed class that is the skysed renormalized
    so that m5 has the desired value.

    Note that the returned SED will be renormalized such that calling the method
    self.calcADU(hardwareBandpass) on it will yield the number of counts per square
    arcsecond in a given bandpass.
    """

    #This is based on the LSST SNR document (v1.2, May 2010)
    #www.astro.washington.edu/users/ivezic/Astr511/LSST_SNRdoc.pdf

    if FWHMeff is None:
        FWHMeff = LSSTdefaults().FWHMeff('r')

    skyCountsTarget = calcSkyCountsPerPixelForM5(m5target, totalBandpass, FWHMeff=FWHMeff,
                                             photParams=photParams)

    skySedOut = Sed(wavelen=numpy.copy(skysed.wavelen),
                    flambda=numpy.copy(skysed.flambda))

    skyCounts = skySedOut.calcADU(hardware, photParams=photParams) \
                    * photParams.platescale * photParams.platescale
    skySedOut.multiplyFluxNorm(skyCountsTarget/skyCounts)

    return skySedOut
Beispiel #4
0
    def testNoise(self):
        """
        Test that ExampleCCDNoise puts the expected counts on an image
        by generating a flat image, adding noise and background to it,
        and calculating the variance of counts in the image.
        """

        lsstDefaults = LSSTdefaults()
        gain = 2.5
        readnoise = 6.0
        photParams = PhotometricParameters(gain=gain, readnoise=readnoise)
        img = galsim.Image(100, 100)
        noise = ExampleCCDNoise(seed=42)
        m5 = 24.5
        bandpass = Bandpass()
        bandpass.readThroughput(
            os.path.join(getPackageDir('throughputs'), 'baseline',
                         'total_r.dat'))
        background = calcSkyCountsPerPixelForM5(
            m5,
            bandpass,
            FWHMeff=lsstDefaults.FWHMeff('r'),
            photParams=photParams)

        noisyImage = noise.addNoiseAndBackground(
            img,
            bandpass,
            m5=m5,
            FWHMeff=lsstDefaults.FWHMeff('r'),
            photParams=photParams)

        mean = 0.0
        var = 0.0
        for ix in range(1, 101):
            for iy in range(1, 101):
                mean += noisyImage(ix, iy)

        mean = mean / 10000.0

        for ix in range(1, 101):
            for iy in range(1, 101):
                var += (noisyImage(ix, iy) - mean) * (noisyImage(ix, iy) -
                                                      mean)

        var = var / 9999.0

        varElectrons = background * gain + readnoise
        varADU = varElectrons / (gain * gain)

        msg = 'background %e mean %e ' % (background, mean)
        self.assertLess(np.abs(background / mean - 1.0), 0.05, msg=msg)

        msg = 'var %e varADU %e ; ratio %e ; background %e' % (
            var, varADU, var / varADU, background)
        self.assertLess(np.abs(var / varADU - 1.0), 0.05, msg=msg)
    def addNoiseAndBackground(self, image, bandpass=None, m5=None,
                              FWHMeff=None,
                              photParams=None,
                              detector=None):
        """
        This method actually adds the sky background and noise to an image.

        Note: default parameters are defined in

        sims_photUtils/python/lsst/sims/photUtils/photometricDefaults.py

        @param [in] image is the GalSim image object to which the background
        and noise are being added.

        @param [in] bandpass is a CatSim bandpass object (not a GalSim bandpass
        object) characterizing the filter through which the image is being taken.

        @param [in] FWHMeff is the FWHMeff in arcseconds

        @param [in] photParams is an instantiation of the
        PhotometricParameters class that carries details about the
        photometric response of the telescope.  Defaults to None.

        @param [in] detector is the GalSimDetector corresponding to the image.
        Defaults to None.

        @param [out] the input image with the background and noise model added to it.

        """


        #calculate the sky background to be added to each pixel
        skyCounts = calcSkyCountsPerPixelForM5(m5, bandpass, FWHMeff=FWHMeff, photParams=photParams)

        image = image.copy()

        if self.addBackground:
            image += skyCounts
            skyLevel = 0.0 #if we are adding the skyCounts to the image,there is no need
                           #to pass a skyLevel parameter to the noise model.  skyLevel is
                           #just used to calculate the level of Poisson noise.  If the
                           #sky background is included in the image, the Poisson noise
                           #will be calculated from the actual image brightness.
        else:
            skyLevel = skyCounts*photParams.gain

        if self.addNoise:
            noiseModel = self.getNoiseModel(skyLevel=skyLevel, photParams=photParams)
            image.addNoise(noiseModel)

        return image
    def addNoiseAndBackground(self, image, bandpass=None, m5=None,
                              FWHMeff=None,
                              photParams=None):
        """
        This method actually adds the sky background and noise to an image.

        Note: default parameters are defined in

        sims_photUtils/python/lsst/sims/photUtils/photometricDefaults.py

        @param [in] image is the GalSim image object to which the background
        and noise are being added.

        @param [in] bandpass is a CatSim bandpass object (not a GalSim bandpass
        object) characterizing the filter through which the image is being taken.

        @param [in] FWHMeff is the FWHMeff in arcseconds

        @param [in] photParams is an instantiation of the
        PhotometricParameters class that carries details about the
        photometric response of the telescope.  Defaults to None.

        @param [out] the input image with the background and noise model added to it.
        """


        #calculate the sky background to be added to each pixel
        skyCounts = calcSkyCountsPerPixelForM5(m5, bandpass, FWHMeff=FWHMeff, photParams=photParams)

        image = image.copy()

        if self.addBackground:
            image += skyCounts
            skyLevel = 0.0 #if we are adding the skyCounts to the image,there is no need
                           #to pass a skyLevel parameter to the noise model.  skyLevel is
                           #just used to calculate the level of Poisson noise.  If the
                           #sky background is included in the image, the Poisson noise
                           #will be calculated from the actual image brightness.
        else:
            skyLevel = skyCounts*photParams.gain

        if self.addNoise:
            noiseModel = self.getNoiseModel(skyLevel=skyLevel, photParams=photParams)
            image.addNoise(noiseModel)

        return image
    def testNoise(self):
        """
        Test that ExampleCCDNoise puts the expected counts on an image
        by generating a flat image, adding noise and background to it,
        and calculating the variance of counts in the image.
        """

        lsstDefaults = LSSTdefaults()
        gain = 2.5
        readnoise = 6.0
        photParams=PhotometricParameters(gain=gain, readnoise=readnoise)
        img = galsim.Image(100,100)
        noise = ExampleCCDNoise(seed=42)
        m5 = 24.5
        bandpass = Bandpass()
        bandpass.readThroughput(os.path.join(lsst.utils.getPackageDir('throughputs'),'baseline','total_r.dat'))
        background = calcSkyCountsPerPixelForM5(m5, bandpass, seeing=lsstDefaults.seeing('r'),
                                            photParams=photParams)

        noisyImage = noise.addNoiseAndBackground(img, bandpass, m5=m5,
                                                 seeing=lsstDefaults.seeing('r'),
                                                 photParams=photParams)

        mean = 0.0
        var = 0.0
        for ix in range(1,101):
            for iy in range(1,101):
                mean += noisyImage(ix, iy)

        mean = mean/10000.0

        for ix in range(1,101):
            for iy in range(1,101):
                var += (noisyImage(ix, iy) - mean)*(noisyImage(ix, iy) - mean)

        var = var/9999.0

        varElectrons = background*gain + readnoise
        varADU = varElectrons/(gain*gain)

        msg = 'background %e mean %e ' % (background, mean)
        self.assertTrue(numpy.abs(background/mean - 1.0) < 0.05, msg=msg)

        msg = 'var %e varADU %e ; ratio %e ; background %e' % (var, varADU, var/varADU, background)
        self.assertTrue(numpy.abs(var/varADU - 1.0) < 0.05, msg=msg)
Beispiel #8
0
    def compareCatalogs(self, cleanCatalog, noisyCatalog, gain, readnoise):
        """
        Read in two catalogs (one with noise, one without).  Compare the flux in each image
        pixel by pixel.  Make sure that the variation between the two is within expected limits.

        @param [in] cleanCatalog is the noiseless GalSimCatalog instantiation

        @param [in] noisyCatalog is the noisy GalSimCatalog instantiation

        @param [in] gain is the electrons per ADU for the GalSimCatalogs

        @param [in] readnoise is the electrons per pixel per exposure of the GalSimCatalogs]
        """

        cleanFileList, cleanBandpassDict = self.getFilesAndBandpasses(
            cleanCatalog, nameRoot='clean')
        noisyFileList, noisyBandpassDict = self.getFilesAndBandpasses(
            noisyCatalog, nameRoot='unclean')

        # calculate the expected skyCounts in each filter
        backgroundCounts = {}
        for filterName in noisyBandpassDict.keys():
            cts = calcSkyCountsPerPixelForM5(
                noisyCatalog.obs_metadata.m5[filterName],
                noisyBandpassDict[filterName],
                noisyCatalog.photParams,
                FWHMeff=noisyCatalog.obs_metadata.seeing[filterName])

            backgroundCounts[filterName] = cts

        # Go through each image pixel by pixel.
        # Treat the value in the clean image as the mean intensity for that pixel.
        # Sum up (noisy-clean)^2/var
        # where var is determined by Poisson statistics from mean and readnoise.
        # Divide by the number of pixel
        # Make sure that this average does not deviate from unity

        countedImages = 0
        for noisyName, cleanName in zip(noisyFileList, cleanFileList):
            noisyIm = afwImage.ImageF(noisyName).getArray()
            cleanIm = afwImage.ImageF(cleanName).getArray()

            totalVar = 0.0
            totalMean = 0.0
            ct = 0.0

            self.assertEqual(cleanIm.shape[0],
                             noisyIm.shape[0],
                             msg='images not same shape')
            self.assertEqual(cleanIm.shape[1],
                             noisyIm.shape[1],
                             msg='images not same shape')

            var = cleanIm / gain + readnoise / (gain * gain)
            totalVar = (np.power(noisyIm - cleanIm, 2) / var).sum()
            totalMean = cleanIm.sum()
            ct = float(cleanIm.shape[0] * cleanIm.shape[1])
            totalVar = totalVar / ct
            totalMean = totalMean / ct

            if totalMean >= 100.0:
                countedImages += 1
                self.assertLess(np.abs(totalVar - 1.0), 0.05)

            os.unlink(noisyName)
            os.unlink(cleanName)

        self.assertGreater(countedImages, 0)
Beispiel #9
0
    def catalogTester(self,
                      catName=None,
                      catalog=None,
                      nameRoot=None,
                      bandpassDir=os.path.join(getPackageDir('throughputs'),
                                               'baseline'),
                      bandpassRoot='total_',
                      sedDir=getPackageDir('sims_sed_library')):
        """
        Reads in a GalSim Instance Catalog.  Writes the images from that catalog.
        Then reads those images back in.  Uses AFW to calculate the number of counts
        in each FITS image.  Reads in the InstanceCatalog associated with those images.
        Uses sims_photUtils code to calculate the ADU for each object on the FITS images.
        Verifies that the two independent calculations of counts agree (to within a tolerance,
        since the GalSim images are generated in a pseudo-random way).

        @param [in] catName is the name of the InstanceCatalog that has been written to disk

        @param [in] catalog is the actual InstanceCatalog instantiation

        @param [in] nameRoot is a string appended to the names of the FITS files being written

        @param [in] bandpassDir is the directory containing the bandpasses against which to test

        @param [in] bandpassRoot is the root of the name of the bandpass files, i.e.

            os.path.join(bandpassDir, bandpassRoot + bandpassName + '.dat')
        """

        # a dictionary of ADU for each FITS file as calculated by GalSim
        # (indexed on the name of the FITS file)
        galsimCounts = {}
        galsimPixels = {}

        # a dictionary of ADU for each FITS file as calculated by sims_photUtils
        # (indexed on the name of the FITS file)
        controlCounts = {}

        listOfFiles, bandpassDict = self.getFilesAndBandpasses(
            catalog,
            nameRoot=nameRoot,
            bandpassDir=bandpassDir,
            bandpassRoot=bandpassRoot)

        # read in the names of all of the written fits files directly from the
        # InstanceCatalog's GalSimInterpreter
        # Use AFW to read in the FITS files and calculate the ADU
        for name in listOfFiles:
            im = afwImage.ImageF(name)
            imArr = im.getArray()
            galsimCounts[name] = imArr.sum()
            galsimPixels[name] = imArr.shape[0] * imArr.shape[1]
            controlCounts[name] = 0.0
            os.unlink(name)

        if catalog.noise_and_background is not None and catalog.noise_and_background.addBackground:
            # calculate the expected skyCounts in each filter
            backgroundCounts = {}
            for filterName in bandpassDict.keys():
                cts = calcSkyCountsPerPixelForM5(
                    catalog.obs_metadata.m5[filterName],
                    bandpassDict[filterName],
                    catalog.photParams,
                    FWHMeff=catalog.obs_metadata.seeing[filterName])

                backgroundCounts[filterName] = cts

            for name in controlCounts:
                filterName = name[-6]
                controlCounts[
                    name] += backgroundCounts[filterName] * galsimPixels[name]

        # Read in the InstanceCatalog.  For each object in the catalog, use sims_photUtils
        # to calculate the ADU.  Keep track of how many ADU should be in each FITS file.
        with open(catName, 'r') as testFile:
            lines = testFile.readlines()
            for line in lines:
                if line[0] != '#':
                    gg = line.split('; ')
                    sedName = gg[7]
                    magNorm = float(gg[13])
                    redshift = float(gg[14])
                    internalAv = float(gg[15])
                    internalRv = float(gg[16])
                    galacticAv = float(gg[17])
                    galacticRv = float(gg[18])
                    listOfFileNames = gg[19].split('//')
                    alreadyWritten = []

                    for name in listOfFileNames:

                        # guard against objects being written on one
                        # chip more than once
                        msg = '%s was written on %s more than once' % (sedName,
                                                                       name)
                        self.assertNotIn(name, alreadyWritten, msg=msg)
                        alreadyWritten.append(name)

                        # loop over all of the detectors on which an object fell
                        # (this is not a terribly great idea, since our conservative implementation
                        # of GalSimInterpreter._doesObjectImpingeOnDetector means that some detectors
                        # will be listed here even though the object does not illumine them)
                        for filterName in bandpassDict.keys():
                            chipName = name.replace(':', '_')
                            chipName = chipName.replace(' ', '_')
                            chipName = chipName.replace(',', '_')
                            chipName = chipName.strip()

                            fullName = nameRoot + '_' + chipName + '_' + filterName + '.fits'

                            fullSedName = os.path.join(sedDir, sedName)

                            controlCounts[fullName] += calcADUwrapper(
                                sedName=fullSedName,
                                bandpass=bandpassDict[filterName],
                                redshift=redshift,
                                magNorm=magNorm,
                                internalAv=internalAv,
                                internalRv=internalRv,
                                galacticAv=galacticAv,
                                galacticRv=galacticRv)

            drawnDetectors = 0
            unDrawnDetectors = 0
            for ff in controlCounts:
                if controlCounts[ff] > 1000.0 and galsimCounts[ff] > 0.001:
                    countSigma = np.sqrt(controlCounts[ff] /
                                         catalog.photParams.gain)

                    # because, for really dim images, there could be enough
                    # statistical imprecision in the GalSim drawing routine
                    # to violate the condition below
                    drawnDetectors += 1
                    msg = 'controlCounts %e galsimCounts %e sigma %e; delta/sigma %e; %s ' % \
                          (controlCounts[ff], galsimCounts[ff], countSigma,
                           (controlCounts[ff]-galsimCounts[ff])/countSigma, nameRoot)

                    if catalog.noise_and_background is not None \
                        and catalog.noise_and_background.addBackground:

                        msg += 'background per pixel %e pixels %e %s' % \
                               (backgroundCounts[ff[-6]], galsimPixels[ff], ff)

                    self.assertLess(np.abs(controlCounts[ff] -
                                           galsimCounts[ff]),
                                    4.0 * countSigma,
                                    msg=msg)
                elif galsimCounts[ff] > 0.001:
                    unDrawnDetectors += 1

            # to make sure we did not neglect more than one detector
            self.assertLess(unDrawnDetectors, 2)
            self.assertGreater(drawnDetectors, 0)
    def catalogTester(self, catName=None, catalog=None, nameRoot=None,
                      bandpassDir=os.path.join(lsst.utils.getPackageDir('throughputs'),'baseline'),
                      bandpassRoot='total_',
                      sedDir=lsst.utils.getPackageDir('sims_sed_library')):
        """
        Reads in a GalSim Instance Catalog.  Writes the images from that catalog.
        Then reads those images back in.  Uses AFW to calculate the number of counts
        in each FITS image.  Reads in the InstanceCatalog associated with those images.
        Uses sims_photUtils code to calculate the ADU for each object on the FITS images.
        Verifies that the two independent calculations of counts agree (to within a tolerance,
        since the GalSim images are generated in a pseudo-random way).

        @param [in] catName is the name of the InstanceCatalog that has been written to disk

        @param [in] catalog is the actual InstanceCatalog instantiation

        @param [in] nameRoot is a string appended to the names of the FITS files being written

        @param [in] bandpassDir is the directory containing the bandpasses against which to test

        @param [in] bandpassRoot is the root of the name of the bandpass files, i.e.

            os.path.join(bandpassDir, bandpassRoot + bandpassName + '.dat')
        """

        #a dictionary of ADU for each FITS file as calculated by GalSim
        #(indexed on the name of the FITS file)
        galsimCounts = {}
        galsimPixels = {}

        #a dictionary of ADU for each FITS file as calculated by sims_photUtils
        #(indexed on the name of the FITS file)
        controlCounts = {}

        listOfFiles, bandpassDict = self.getFilesAndBandpasses(catalog, nameRoot=nameRoot,
                                                                 bandpassDir=bandpassDir,
                                                                 bandpassRoot=bandpassRoot)

        #read in the names of all of the written fits files directly from the
        #InstanceCatalog's GalSimInterpreter
        #Use AFW to read in the FITS files and calculate the ADU
        for name in listOfFiles:
            im = afwImage.ImageF(name)
            imArr = im.getArray()
            galsimCounts[name] = imArr.sum()
            galsimPixels[name] = imArr.shape[0]*imArr.shape[1]
            controlCounts[name] = 0.0
            os.unlink(name)

        if catalog.noise_and_background is not None and catalog.noise_and_background.addBackground:
            #calculate the expected skyCounts in each filter
            backgroundCounts = {}
            for filterName in bandpassDict.keys():
                cts = calcSkyCountsPerPixelForM5(catalog.obs_metadata.m5[filterName], bandpassDict[filterName],
                                             catalog.photParams, seeing=catalog.obs_metadata.seeing[filterName])

                backgroundCounts[filterName] = cts

            for name in controlCounts:
                filterName = name[-6]
                controlCounts[name] += backgroundCounts[filterName] * galsimPixels[name]

        #Read in the InstanceCatalog.  For each object in the catalog, use sims_photUtils
        #to calculate the ADU.  Keep track of how many ADU should be in each FITS file.
        with open(catName, 'r') as testFile:
            lines = testFile.readlines()
            for line in lines:
                if line[0] != '#':
                    gg = line.split(';')
                    sedName = gg[7]
                    magNorm = float(gg[13])
                    redshift = float(gg[14])
                    internalAv = float(gg[15])
                    internalRv = float(gg[16])
                    galacticAv = float(gg[17])
                    galacticRv = float(gg[18])
                    listOfFileNames = gg[19].split('//')
                    alreadyWritten = []

                    for name in listOfFileNames:

                        #guard against objects being written on one
                        #chip more than once
                        msg = '%s was written on %s more than once' % (sedName, name)
                        self.assertTrue(name not in alreadyWritten, msg=msg)
                        alreadyWritten.append(name)

                        #loop over all of the detectors on which an object fell
                        #(this is not a terribly great idea, since our conservative implementation
                        #of GalSimInterpreter._doesObjectImpingeOnDetector means that some detectors
                        #will be listed here even though the object does not illumine them)
                        for filterName in bandpassDict.keys():
                            chipName = name.replace(':','_')
                            chipName = chipName.replace(' ','_')
                            chipName = chipName.replace(',','_')
                            chipName = chipName.strip()

                            fullName = nameRoot+'_'+chipName+'_'+filterName+'.fits'

                            fullSedName = os.path.join(sedDir, sedName)

                            controlCounts[fullName] += calcADUwrapper(sedName=fullSedName, bandpass=bandpassDict[filterName],
                                                                        redshift=redshift, magNorm=magNorm,
                                                                        internalAv=internalAv, internalRv=internalRv,
                                                                        galacticAv=galacticAv, galacticRv=galacticRv)

            drawnDetectors = 0
            unDrawnDetectors = 0
            for ff in controlCounts:
                if controlCounts[ff] > 1000.0 and galsimCounts[ff] > 0.001:
                    #because, for really dim images, there could be enough
                    #statistical imprecision in the GalSim drawing routine
                    #to violate the condition below
                    drawnDetectors += 1
                    msg = 'controlCounts %e galsimCounts %e; %s ' % (controlCounts[ff], galsimCounts[ff],nameRoot)
                    if catalog.noise_and_background is not None and catalog.noise_and_background.addBackground:
                        msg += 'background per pixel %e pixels %e %s' % (backgroundCounts[ff[-6]], galsimPixels[ff],ff)

                    self.assertTrue(numpy.abs(controlCounts[ff] - galsimCounts[ff]) < 0.05*controlCounts[ff],
                                    msg=msg)
                elif galsimCounts[ff] > 0.001:
                    unDrawnDetectors += 1

            #to make sure we did not neglect more than one detector
            self.assertTrue(unDrawnDetectors<2)
            self.assertTrue(drawnDetectors>0)