Beispiel #1
0
    def testMatchXyMatchControl(self):
        """Test using MatchControl to return all matches, and add a test for closest==False at the same time"""
        for closest in (True, False):
            for includeMismatches in (True, False):
                mc = afwTable.MatchControl()
                mc.findOnlyClosest = closest
                mc.includeMismatches = includeMismatches
                matches = afwTable.matchXy(self.cat1, self.cat2, 0.01, mc)

                if False:
                    for m in matches:
                        print closest, m.first.getId(), m.second.getId(), m.distance
    
                if includeMismatches:
                    catMatches = afwTable.SourceCatalog(self.table)
                    catMismatches = afwTable.SourceCatalog(self.table)
                    for m in matches:
                        if m[1] != None:
                            if not any(x == m[0] for x in catMatches):
                                catMatches.append(m[0])
                        else:
                            catMismatches.append(m[0])
                    matches = afwTable.matchXy(catMatches, self.cat2, 0.01, mc)
                    mc.includeMismatches = False
                    noMatches = afwTable.matchXy(catMismatches, self.cat2, 0.01, mc)
                    self.assertEquals(len(noMatches), 0)

                self.assertEquals(len(matches), self.nUniqueMatch if closest else self.nUniqueMatch + 1)
                for m in matches:
                    if closest:
                        self.assertEquals(m.first.getId() + self.nobj, m.second.getId())
                    self.assertEquals(m.distance, 0.0)
    def testMatchXyMatchControl(self):
        """Test using MatchControl to return all matches

        Also tests closest==False at the same time
        """
        for closest in (True, False):
            for includeMismatches in (True, False):
                mc = afwTable.MatchControl()
                mc.findOnlyClosest = closest
                mc.includeMismatches = includeMismatches
                matches = afwTable.matchXy(
                    self.cat1, self.cat2, self.matchRadius, mc)

                if False:
                    for m in matches:
                        print(closest, m.first.getId(),
                              m.second.getId(), m.distance)

                if includeMismatches:
                    catMatches = afwTable.SourceCatalog(self.table)
                    catMismatches = afwTable.SourceCatalog(self.table)
                    for m in matches:
                        if m[1] is not None:
                            if not any(x == m[0] for x in catMatches):
                                catMatches.append(m[0])
                        else:
                            catMismatches.append(m[0])
                    matches = afwTable.matchXy(
                        catMatches, self.cat2, self.matchRadius, mc)
                    mc.includeMismatches = False
                    noMatches = afwTable.matchXy(
                        catMismatches, self.cat2, self.matchRadius, mc)
                    self.assertEqual(len(noMatches), 0)

                # If we're not getting only the closest match, then we get an extra match due to the
                # source we offset by 2 pixels and a bit.  Everything else
                # should match exactly.
                self.assertEqual(
                    len(matches),
                    self.nUniqueMatch if closest else self.nUniqueMatch + 1)
                self.assertEqual(
                    sum(1 for m in matches if m.distance == 0.0),
                    self.nUniqueMatch)
                for m in matches:
                    if closest:
                        self.assertEqual(m.first.getId() +
                                         self.nobj, m.second.getId())
                    else:
                        self.assertLessEqual(m.distance, self.matchRadius)
    def testMatchXyMatchControl(self):
        """Test using MatchControl to return all matches

        Also tests closest==False at the same time
        """
        for closest in (True, False):
            for includeMismatches in (True, False):
                mc = afwTable.MatchControl()
                mc.findOnlyClosest = closest
                mc.includeMismatches = includeMismatches
                matches = afwTable.matchXy(self.cat1, self.cat2,
                                           self.matchRadius, mc)

                if False:
                    for m in matches:
                        print(closest, m.first.getId(), m.second.getId(),
                              m.distance)

                if includeMismatches:
                    catMatches = afwTable.SourceCatalog(self.table)
                    catMismatches = afwTable.SourceCatalog(self.table)
                    for m in matches:
                        if m[1] is not None:
                            if not any(x == m[0] for x in catMatches):
                                catMatches.append(m[0])
                        else:
                            catMismatches.append(m[0])
                    matches = afwTable.matchXy(catMatches, self.cat2,
                                               self.matchRadius, mc)
                    mc.includeMismatches = False
                    noMatches = afwTable.matchXy(catMismatches, self.cat2,
                                                 self.matchRadius, mc)
                    self.assertEqual(len(noMatches), 0)

                # If we're not getting only the closest match, then we get an extra match due to the
                # source we offset by 2 pixels and a bit.  Everything else
                # should match exactly.
                self.assertEqual(
                    len(matches),
                    self.nUniqueMatch if closest else self.nUniqueMatch + 1)
                self.assertEqual(sum(1 for m in matches if m.distance == 0.0),
                                 self.nUniqueMatch)
                for m in matches:
                    if closest:
                        self.assertEqual(m.first.getId() + self.nobj,
                                         m.second.getId())
                    else:
                        self.assertLessEqual(m.distance, self.matchRadius)
Beispiel #4
0
def propagatePsfFlags(keysToCopy, calibSources, sources, matchRadius=1):
    """Match the calibSources and sources, and propagate Interesting Flags (e.g. PSF star) to the sources
    """
    if calibSources is None or sources is None:
        return

    closest = False                 # return all matched objects
    matched = afwTable.matchXy(calibSources, sources, matchRadius, closest)
    #
    # Because we had to allow multiple matches to handle parents, we now need to
    # prune to the best matches
    #
    bestMatches = {}
    for m0, m1, d in matched:
        id0 = m0.getId()
        if bestMatches.has_key(id0):
            if d > bestMatches[id0][2]:
                continue

        bestMatches[id0] = (m0, m1, d)

    matched = bestMatches.values()
    #
    # Check that we got it right
    #
    if len(set(m[0].getId() for m in matched)) != len(matched):
        print("At least one calibSource is matched to more than one Source")
    #
    # Copy over the desired flags
    #
    for cs, s, d in matched:
        for skey, ckey in keysToCopy:
            s.set(skey, cs.get(ckey))
Beispiel #5
0
    def testMatchXy(self):
        matches = afwTable.matchXy(self.cat1, self.cat2, self.matchRadius)
        self.assertEquals(len(matches), self.nUniqueMatch)

        for m in matches:
            self.assertEquals(m.first.getId() + self.nobj, m.second.getId())
            self.assertEquals(m.distance, 0.0)
    def testMatchXy(self):
        matches = afwTable.matchXy(self.cat1, self.cat2, self.matchRadius)
        self.assertEqual(len(matches), self.nUniqueMatch)

        for m in matches:
            self.assertEqual(m.first.getId() + self.nobj, m.second.getId())
            self.assertEqual(m.distance, 0.0)
Beispiel #7
0
def propagatePsfFlags(keysToCopy, calibSources, sources, matchRadius=1):
    """Match the calibSources and sources, and propagate Interesting Flags (e.g. PSF star) to the sources
    """
    if calibSources is None or sources is None:
        return

    closest = False  # return all matched objects
    matched = afwTable.matchXy(calibSources, sources, matchRadius, closest)
    #
    # Because we had to allow multiple matches to handle parents, we now need to
    # prune to the best matches
    #
    bestMatches = {}
    for m0, m1, d in matched:
        id0 = m0.getId()
        if bestMatches.has_key(id0):
            if d > bestMatches[id0][2]:
                continue

        bestMatches[id0] = (m0, m1, d)

    matched = bestMatches.values()
    #
    # Check that we got it right
    #
    if len(set(m[0].getId() for m in matched)) != len(matched):
        print("At least one calibSource is matched to more than one Source")
    #
    # Copy over the desired flags
    #
    for cs, s, d in matched:
        for skey, ckey in keysToCopy:
            s.set(skey, cs.get(ckey))
Beispiel #8
0
    def copyIcSourceFields(self, icSourceCat, sourceCat):
        """!Match sources in icSourceCat and sourceCat and copy the specified fields

        @param[in] icSourceCat  catalog from which to copy fields
        @param[in,out] sourceCat  catalog to which to copy fields

        The fields copied are those specified by `config.icSourceFieldsToCopy`
        that actually exist in the schema. This was set up by the constructor
        using self.schemaMapper.
        """
        if self.schemaMapper is None:
            raise RuntimeError("To copy icSource fields you must specify icSourceSchema "
                "and icSourceKeys when constructing this task")
        if icSourceCat is None or sourceCat is None:
            raise RuntimeError("icSourceCat and sourceCat must both be specified")
        if len(self.config.icSourceFieldsToCopy) == 0:
            self.log.warn("copyIcSourceFields doing nothing because icSourceFieldsToCopy is empty")
            return

        closest = False  # return all matched objects
        matches = afwTable.matchXy(icSourceCat, sourceCat, self.config.matchRadiusPix, closest)
        if self.config.detectAndMeasure.doDeblend:
            deblendKey = sourceCat.schema["deblend_nChild"].asKey()
            matches = [m for m in matches if m[1].get(deblendKey) == 0]  # if deblended, keep children

        # Because we had to allow multiple matches to handle parents, we now need to
        # prune to the best matches
        bestMatches = {}    # closest matches as a dict of icSourceCat source ID:
                            #   (icSourceCat source, sourceCat source, distance in pixels)
        for m0, m1, d in matches:
            id0 = m0.getId()
            match = bestMatches.get(id0)
            if match is None or d <= match[2]:
                bestMatches[id0] = (m0, m1, d)
        matches = bestMatches.values()

        # Check that no sourceCat sources are listed twice (we already know that each match has a unique
        # icSourceCat source ID, due to using that ID as the key in bestMatches)
        numMatches = len(matches)
        numUniqueSources = len(set(m[1].getId() for m in matches))
        if numUniqueSources != numMatches:
            self.log.warn("%d icSourceCat sources matched only %d sourceCat sources" %
                (numMatches, numUniqueSources))

        self.log.info("Copying flags from icSourceCat to sourceCat for %s sources" % (numMatches,))

        # For each match: set the calibSourceKey flag and copy the desired fields
        for icSrc, src, d in matches:
            src.setFlag(self.calibSourceKey, True)
            # src.assign copies the footprint from icSrc, which we don't want (DM-407)
            # so set icSrc's footprint to src's footprint before src.assign, then restore it
            icSrcFootprint = icSrc.getFootprint()
            try:
                icSrc.setFootprint(src.getFootprint())
                src.assign(icSrc, self.schemaMapper)
            finally:
                icSrc.setFootprint(icSrcFootprint)
Beispiel #9
0
    def testSelfMatchXy(self):
        """Test doing a self-matches"""
        for symmetric in (True, False):
            mc = afwTable.MatchControl()
            mc.symmetricMatch = symmetric
            matches = afwTable.matchXy(self.cat2, 0.01, mc)

            if False:
                for m in matches:
                    print m.first.getId(), m.second.getId(), m.distance

            self.assertEquals(len(matches), 2 if symmetric else 1)
    def testSelfMatchXy(self):
        """Test doing a self-matches"""
        for symmetric in (True, False):
            mc = afwTable.MatchControl()
            mc.symmetricMatch = symmetric
            matches = afwTable.matchXy(self.cat2, self.matchRadius, mc)

            if False:
                for m in matches:
                    print(m.first.getId(), m.second.getId(), m.distance)

            # There is only one source that matches another source when we do a self-match: the one
            # offset by 2 pixels and a bit.
            # If we're getting symmetric matches, that multiplies the expected number by 2 because it
            # produces s1,s2 and s2,s1.
            self.assertEqual(len(matches), 2 if symmetric else 1)
Beispiel #11
0
    def testSelfMatchXy(self):
        """Test doing a self-matches"""
        for symmetric in (True, False):
            mc = afwTable.MatchControl()
            mc.symmetricMatch = symmetric
            matches = afwTable.matchXy(self.cat2, self.matchRadius, mc)

            if False:
                for m in matches:
                    print m.first.getId(), m.second.getId(), m.distance

            # There is only one source that matches another source when we do a self-match: the one
            # offset by 2 pixels and a bit.
            # If we're getting symmetric matches, that multiplies the expected number by 2 because it
            # produces s1,s2 and s2,s1.
            self.assertEquals(len(matches), 2 if symmetric else 1)
Beispiel #12
0
    def find(self, objId, matchRadius=10):
        """Return the object's family (you may specify either the ID for the parent or a child)"""
        x, y = None, None
        try:
            x, y = objId
        except TypeError:
            pass

        if x is not None:
            oneObjCatalog = afwTable.SourceCatalog(self.cat.getSchema())
            centroidName = self.cat.table.getCentroidDefinition()
            oneObjCatalog.table.defineCentroid(centroidName)

            s = oneObjCatalog.addNew()
            s.set("%s_x" % centroidName, x)
            s.set("%s_y" % centroidName, y)

            matched = afwTable.matchXy(self.cat, oneObjCatalog, matchRadius)

            if len(matched) == 0:
                print >> sys.stderr, "Unable to find object at (%.2f, %.2f)" % (
                    x, y)
                return None

            if False:
                objId = self.mapperInfo.splitId(matched[0][0].getId(),
                                                asDict=True)["objId"]
            else:
                objId = matched[0][0].getId()

        if False:
            family = [f for f in self if self.mapperInfo.getId(f[0]) == objId]
        else:
            family = [f for f in self if f[0].getId() == objId]
        if family:
            return family[0]

        for family in self:
            for child in family[1]:
                if False:
                    if self.mapperInfo.getId(child) == objId:
                        return family
                else:
                    if child.getId() == objId:
                        return family

        return None
Beispiel #13
0
    def find(self, objId, matchRadius=10):
        """Return the object's family (you may specify either the ID for the parent or a child)"""
        x, y = None, None
        try:
            x, y = objId
        except TypeError:
            pass

        if x is not None:
            oneObjCatalog = afwTable.SourceCatalog(self.cat.getSchema())
            centroidName = self.cat.table.getCentroidDefinition()
            oneObjCatalog.table.defineCentroid(centroidName)

            s = oneObjCatalog.addNew()
            s.set("%s_x" % centroidName, x)
            s.set("%s_y" % centroidName, y)

            matched = afwTable.matchXy(self.cat, oneObjCatalog, matchRadius)

            if len(matched) == 0:
                print("Unable to find object at (%.2f, %.2f)" % (x, y), file=sys.stderr)
                return None

            if False:
                objId = self.mapperInfo.splitId(matched[0][0].getId(), asDict=True)["objId"]
            else:
                objId = matched[0][0].getId()

        if False:
            family = [f for f in self if self.mapperInfo.getId(f[0]) == objId]
        else:
            family = [f for f in self if f[0].getId() == objId]
        if family:
            return family[0]

        for family in self:
            for child in family[1]:
                if False:
                    if self.mapperInfo.getId(child) == objId:
                        return family
                else:
                    if child.getId() == objId:
                        return family

        return None
    def testMeasureCentroid(self):
        """Test that we can instantiate and play with a measureCentroid"""
        exposure = afwImage.makeExposure(self.mi)
        self.ds.makeSources(self.ssMeasured)
        ID = 1
        for s in self.ssMeasured:
            s.setId(ID)
            ID += 1
            foot = s.getFootprint()
            bbox = foot.getBBox()
            xc = (bbox.getMinX() + bbox.getMaxX()) // 2
            yc = (bbox.getMinY() + bbox.getMaxY()) // 2

            self.centroider.apply(s, exposure, afwGeom.Point2D(xc, yc))

            if display:
                ds9.dot("x", c.getX(), c.getY(), ctype=ds9.GREEN)
        #
        # OK, we've measured all the sources.  Compare positions with Dave Monet's values
        #

        # FIXME: this test will fail until source matching in afw is updated to use afw/table
        mat = afwTable.matchXy(self.ssTruth, self.ssMeasured, 1.0)
        #self.assertEqual(ID, len(mat))  # we matched all the input sources

        eps = 6e-6  # offset in pixels between measured centroid and the Truth
        for match in mat:
            dx = match[0].getX() - match[1].getX()
            dy = match[0].getY() - match[1].getY()

            good = True if math.hypot(dx, dy) < eps else False
            if not good:
                msg = "Star at (%.1f, %.1f): (dx, dy) = %g, %g)" % \
                    (match[0].getXAstrom(), match[0].getYAstrom(), dx, dy)
                if True:
                    print msg
                else:
                    self.assertTrue(good, msg)
Beispiel #15
0
    def testMeasureCentroid(self):
        """Test that we can instantiate and play with a measureCentroid"""
        exposure = afwImage.makeExposure(self.mi)
        self.ds.makeSources(self.ssMeasured)
        ID = 1
        self.task.run(self.ssMeasured, exposure)
        for s in self.ssMeasured:
            s.setId(ID)
            ID += 1
            foot = s.getFootprint()
            bbox = foot.getBBox()
            xc = (bbox.getMinX() + bbox.getMaxX()) // 2
            yc = (bbox.getMinY() + bbox.getMaxY()) // 2

            if display:
                ds9.dot("x", xc, yc, ctype=ds9.GREEN)
        #
        # OK, we've measured all the sources.  Compare positions with Dave Monet's values
        #

        mat = afwTable.matchXy(self.ssTruth, self.ssMeasured, 1.0)
        self.assertEqual(ID, len(mat))  # we matched all the input sources

        # offset in pixels between measured centroid and the Truth
        eps = 6e-6
        for match in mat:
            dx = match[0].getX() - match[1].getX()
            dy = match[0].getY() - match[1].getY()

            good = True if math.hypot(dx, dy) < eps else False
            if not good:
                msg = "Star at (%.1f, %.1f): (dx, dy) = %g, %g)" % \
                    (match[0].getXAstrom(), match[0].getYAstrom(), dx, dy)
                if True:
                    print(msg)
                else:
                    self.assertTrue(good, msg)
Beispiel #16
0
    def testMeasureCentroid(self):
        """Test that we can instantiate and play with a measureCentroid"""
        exposure = afwImage.makeExposure(self.mi)
        self.ds.makeSources(self.ssMeasured)
        ID = 1
        for s in self.ssMeasured:
            s.setId(ID); ID += 1
            foot = s.getFootprint()
            bbox = foot.getBBox()
            xc = (bbox.getMinX() + bbox.getMaxX())//2
            yc = (bbox.getMinY() + bbox.getMaxY())//2

            self.centroider.apply(s, exposure, afwGeom.Point2D(xc, yc))

            if display:
                ds9.dot("x", c.getX(), c.getY(), ctype=ds9.GREEN)
        #
        # OK, we've measured all the sources.  Compare positions with Dave Monet's values
        #

        # FIXME: this test will fail until source matching in afw is updated to use afw/table
        mat = afwTable.matchXy(self.ssTruth, self.ssMeasured, 1.0)
        #self.assertEqual(ID, len(mat))  # we matched all the input sources

        eps = 6e-6                      # offset in pixels between measured centroid and the Truth
        for match in mat:
            dx = match[0].getX() - match[1].getX()
            dy = match[0].getY() - match[1].getY()
            
            good = True if math.hypot(dx, dy) < eps else False
            if not good:
                msg = "Star at (%.1f, %.1f): (dx, dy) = %g, %g)" % \
                    (match[0].getXAstrom(), match[0].getYAstrom(), dx, dy)
                if True:
                    print msg
                else:
                    self.assertTrue(good, msg)
    def copyCalibrationFields(self, calibCat, sourceCat, fieldsToCopy):
        """Match sources in calibCat and sourceCat and copy the specified fields

        Parameters
        ----------
        calibCat : `lsst.afw.table.SourceCatalog`
            Catalog from which to copy fields.
        sourceCat : `lsst.afw.table.SourceCatalog`
            Catalog to which to copy fields.
        fieldsToCopy : `lsst.pex.config.listField.List`
            Fields to copy from calibCat to SoourceCat.

        Returns
        -------
        newCat : `lsst.afw.table.SourceCatalog`
            Catalog which includes the copied fields.

        The fields copied are those specified by `fieldsToCopy` that actually exist
        in the schema of `calibCat`.

        This version was based on and adapted from the one in calibrateTask.
        """

        # Make a new SourceCatalog with the data from sourceCat so that we can add the new columns to it
        sourceSchemaMapper = afwTable.SchemaMapper(sourceCat.schema)
        sourceSchemaMapper.addMinimalSchema(sourceCat.schema, True)

        calibSchemaMapper = afwTable.SchemaMapper(calibCat.schema,
                                                  sourceCat.schema)

        # Add the desired columns from the option fieldsToCopy
        missingFieldNames = []
        for fieldName in fieldsToCopy:
            if fieldName in calibCat.schema:
                schemaItem = calibCat.schema.find(fieldName)
                calibSchemaMapper.editOutputSchema().addField(
                    schemaItem.getField())
                schema = calibSchemaMapper.editOutputSchema()
                calibSchemaMapper.addMapping(schemaItem.getKey(),
                                             schema.find(fieldName).getField())
            else:
                missingFieldNames.append(fieldName)
        if missingFieldNames:
            raise RuntimeError(
                f"calibCat is missing fields {missingFieldNames} specified in "
                "fieldsToCopy")

        if "calib_detected" not in calibSchemaMapper.getOutputSchema():
            self.calibSourceKey = calibSchemaMapper.addOutputField(
                afwTable.Field["Flag"]("calib_detected",
                                       "Source was detected as an icSource"))
        else:
            self.calibSourceKey = None

        schema = calibSchemaMapper.getOutputSchema()
        newCat = afwTable.SourceCatalog(schema)
        newCat.reserve(len(sourceCat))
        newCat.extend(sourceCat, sourceSchemaMapper)

        # Set the aliases so it doesn't complain.
        for k, v in sourceCat.schema.getAliasMap().items():
            newCat.schema.getAliasMap().set(k, v)

        select = newCat["deblend_nChild"] == 0
        matches = afwTable.matchXy(newCat[select], calibCat,
                                   self.config.matchRadiusPix)
        # Check that no sourceCat sources are listed twice (we already know
        # that each match has a unique calibCat source ID, due to using
        # that ID as the key in bestMatches)
        numMatches = len(matches)
        numUniqueSources = len(set(m[1].getId() for m in matches))
        if numUniqueSources != numMatches:
            self.log.warn(
                "%d calibCat sources matched only %d sourceCat sources",
                numMatches, numUniqueSources)

        self.log.info(
            "Copying flags from calibCat to sourceCat for %s sources",
            numMatches)

        # For each match: set the calibSourceKey flag and copy the desired
        # fields
        for src, calibSrc, d in matches:
            if self.calibSourceKey:
                src.setFlag(self.calibSourceKey, True)
            # src.assign copies the footprint from calibSrc, which we don't want
            # (DM-407)
            # so set calibSrc's footprint to src's footprint before src.assign,
            # then restore it
            calibSrcFootprint = calibSrc.getFootprint()
            try:
                calibSrc.setFootprint(src.getFootprint())
                src.assign(calibSrc, calibSchemaMapper)
            finally:
                calibSrc.setFootprint(calibSrcFootprint)

        return newCat
Beispiel #18
0
    def run(self, sensorRef, templateIdList=None):
        """Subtract an image from a template coadd and measure the result

        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources

        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"

        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = int(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)

        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None  # Sources on the template image
        if self.config.doSubtract:
            template = self.getTemplate.run(exposure,
                                            sensorRef,
                                            templateIdList=templateIdList)
            templateExposure = template.exposure
            templateSources = template.sources

            # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
            scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()

            # sigma of PSF of template image before warping
            templateSigma = templateExposure.getPsf().computeShape(
            ).getDeterminantRadius()

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getLocalKernel(
                    ).getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight,
                                                   scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = srcPsf
                afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(),
                                 convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn(
                        "Src product does not exist; running detection, measurement, selection"
                    )
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure,
                        sigma=scienceSigmaPost,
                        doSmooth=not self.doPreConvolve,
                        idFactory=idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(
                    makeKernelBasisList(
                        self.subtract.config.kernel.active,
                        referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
                        targetFwhmPix=templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    kcQa = KernelCandidateQa(nparam)
                    selectSources = kcQa.addToSchema(selectSources)

                if self.config.kernelSourcesFromRef:
                    # match exposure sources to reference catalog
                    astromRet = self.astrometer.loadAndMatch(
                        exposure=exposure, sourceCat=selectSources)
                    matches = astromRet.matches
                elif templateSources:
                    # match exposure sources to template sources
                    mc = afwTable.MatchControl()
                    mc.findOnlyClosest = False
                    matches = afwTable.matchRaDec(templateSources,
                                                  selectSources,
                                                  1.0 * afwGeom.arcseconds, mc)
                else:
                    raise RuntimeError(
                        "doSelectSources=True and kernelSourcesFromRef=False,"
                        +
                        "but template sources not available. Cannot match science "
                        +
                        "sources with template sources. Run process* on data from "
                        + "which templates are built.")

                kernelSources = self.sourceSelector.selectStars(
                    exposure, selectSources, matches=matches).starCat

                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                kernelSources = [
                    k for i, k in enumerate(kernelSources)
                    if i % self.config.controlStepSize
                ]

                if self.config.doSelectDcrCatalog:
                    redSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(
                            grMin=self.sourceSelector.config.grMax,
                            grMax=99.999))
                    redSources = redSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(redSources)

                    blueSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(
                            grMin=-99.999,
                            grMax=self.sourceSelector.config.grMin))
                    blueSources = blueSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(blueSources)

                if self.config.doSelectVariableCatalog:
                    varSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(includeVariable=True))
                    varSources = varSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(varSources)

                self.log.info(
                    "Selected %d / %d sources for Psf matching (%d for control sample)"
                    % (len(kernelSources), len(selectSources),
                       len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")

                if templateSources is None:
                    # Run detection on the template, which is
                    # temporarily background-subtracted
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma=templateSigma,
                        doSmooth=True,
                        idFactory=idFactory)

                # Third step: we need to fit the relative astrometry.
                #
                wcsResults = self.fitAstrometry(templateSources,
                                                templateExposure,
                                                selectSources)
                warpedExp = self.register.warpExposure(templateExposure,
                                                       wcsResults.wcs,
                                                       exposure.getWcs(),
                                                       exposure.getBBox())
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    # Grab matches to reference catalog
                    srcToMatch = {x.second.getId(): x.first for x in matches}

                    refCoordKey = wcsResults.matches[0].first.getTable(
                    ).getCoordKey()
                    inCentroidKey = wcsResults.matches[0].second.getTable(
                    ).getCentroidKey()
                    sids = [m.first.getId() for m in wcsResults.matches]
                    positions = [
                        m.first.get(refCoordKey) for m in wcsResults.matches
                    ]
                    residuals = [
                        m.first.get(refCoordKey).getOffsetFrom(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey)))
                        for m in wcsResults.matches
                    ]
                    allresids = dict(zip(sids, zip(positions, residuals)))

                    cresiduals = [
                        m.first.get(refCoordKey).getTangentPlaneOffset(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey)))
                        for m in wcsResults.matches
                    ]
                    colors = numpy.array([
                        -2.5 * numpy.log10(srcToMatch[x].get("g")) +
                        2.5 * numpy.log10(srcToMatch[x].get("r")) for x in sids
                        if x in srcToMatch.keys()
                    ])
                    dlong = numpy.array([
                        r[0].asArcseconds() for s, r in zip(sids, cresiduals)
                        if s in srcToMatch.keys()
                    ])
                    dlat = numpy.array([
                        r[1].asArcseconds() for s, r in zip(sids, cresiduals)
                        if s in srcToMatch.keys()
                    ])
                    idx1 = numpy.where(
                        colors < self.sourceSelector.config.grMin)
                    idx2 = numpy.where(
                        (colors >= self.sourceSelector.config.grMin)
                        & (colors <= self.sourceSelector.config.grMax))
                    idx3 = numpy.where(
                        colors > self.sourceSelector.config.grMax)
                    rms1Long = IqrToSigma * \
                        (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
                    rms1Lat = IqrToSigma * (numpy.percentile(dlat[idx1], 75) -
                                            numpy.percentile(dlat[idx1], 25))
                    rms2Long = IqrToSigma * \
                        (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
                    rms2Lat = IqrToSigma * (numpy.percentile(dlat[idx2], 75) -
                                            numpy.percentile(dlat[idx2], 25))
                    rms3Long = IqrToSigma * \
                        (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
                    rms3Lat = IqrToSigma * (numpy.percentile(dlat[idx3], 75) -
                                            numpy.percentile(dlat[idx3], 25))
                    self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f" %
                                  (numpy.median(dlong[idx1]), rms1Long,
                                   numpy.median(dlat[idx1]), rms1Lat))
                    self.log.info(
                        "Green star offsets'': %.3f %.3f, %.3f %.3f" %
                        (numpy.median(dlong[idx2]), rms2Long,
                         numpy.median(dlat[idx2]), rms2Lat))
                    self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f" %
                                  (numpy.median(dlong[idx3]), rms3Long,
                                   numpy.median(dlat[idx3]), rms3Lat))

                    self.metadata.add("RegisterBlueLongOffsetMedian",
                                      numpy.median(dlong[idx1]))
                    self.metadata.add("RegisterGreenLongOffsetMedian",
                                      numpy.median(dlong[idx2]))
                    self.metadata.add("RegisterRedLongOffsetMedian",
                                      numpy.median(dlong[idx3]))
                    self.metadata.add("RegisterBlueLongOffsetStd", rms1Long)
                    self.metadata.add("RegisterGreenLongOffsetStd", rms2Long)
                    self.metadata.add("RegisterRedLongOffsetStd", rms3Long)

                    self.metadata.add("RegisterBlueLatOffsetMedian",
                                      numpy.median(dlat[idx1]))
                    self.metadata.add("RegisterGreenLatOffsetMedian",
                                      numpy.median(dlat[idx2]))
                    self.metadata.add("RegisterRedLatOffsetMedian",
                                      numpy.median(dlat[idx3]))
                    self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat)
                    self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat)
                    self.metadata.add("RegisterRedLatOffsetStd", rms3Lat)

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            # Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure=templateExposure,
                scienceExposure=exposure,
                candidateList=kernelSources,
                convolveTemplate=self.config.convolveTemplate,
                doWarping=not self.config.doUseRegister)
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure,
                              self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Computing diffim PSF")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)

            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        template = self.getTemplate.run(
                            exposure, sensorRef, templateIdList=templateIdList)
                    subtractedExposure.setPsf(template.exposure.getPsf())

        # If doSubtract is False, then subtractedExposure was fetched from disk (above), thus it may have
        # already been decorrelated. Thus, we do not do decorrelation if doSubtract is False.
        if self.config.doDecorrelation and self.config.doSubtract:
            decorrResult = self.decorrelate.run(exposure, templateExposure,
                                                subtractedExposure,
                                                subtractRes.psfMatchingKernel)
            subtractedExposure = decorrResult.correctedExposure

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            # Erase existing detection mask planes
            mask = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED")
                      | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table=table,
                exposure=subtractedExposure,
                doSmooth=not self.config.doPreConvolve)

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint,
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" %
                              (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                if not self.config.doDipoleFitting:
                    self.measurement.run(diaSources, subtractedExposure)
                else:
                    if self.config.doSubtract:
                        self.measurement.run(diaSources, subtractedExposure,
                                             exposure,
                                             subtractRes.matchedExposure)
                    else:
                        self.measurement.run(diaSources, subtractedExposure,
                                             exposure)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs(
                    ).pixelScale().asArcseconds()

                    srcMatches = afwTable.matchXy(sensorRef.get("src"),
                                                  diaSources, matchRadPixel)
                    srcMatchDict = dict([(srcMatch.second.getId(),
                                          srcMatch.first.getId())
                                         for srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" %
                                  (len(srcMatchDict), len(diaSources)))
                else:
                    self.log.warn(
                        "Src product does not exist; cannot match with diaSources"
                    )
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                refAstromConfig = AstrometryConfig()
                refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
                refAstrometer = AstrometryTask(refAstromConfig)
                astromRet = refAstrometer.run(exposure=exposure,
                                              sourceCat=diaSources)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn(
                        "No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info(
                        "Matched %d / %d diaSources to reference catalog" %
                        (len(refMatches), len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(),
                                          refMatch.first.getId())
                                         for refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:
                    sid = diaSource.getId()
                    if sid in srcMatchDict:
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if sid in refMatchDict:
                        diaSource.set("refMatchId", refMatchDict[sid])

            if diaSources is not None and self.config.doWriteSources:
                sensorRef.put(diaSources,
                              self.config.coaddName + "Diff_diaSrc")

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False):  # include bad candidates
                        kernelCandList.append(cand)

                # Get basis list to build control sample kernels
                basisList = kernelCandList[0].getKernel(
                    KernelCandidateF.ORIG).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandidateList(controlSources,
                                                           subtractRes.warpedExposure, exposure,
                                                           self.config.subtract.kernel.active,
                                                           self.config.subtract.kernel.active.detectionConfig,
                                                           self.log, doBuild=True, basisList=basisList)

                kcQa.apply(kernelCandList,
                           subtractRes.psfMatchingKernel,
                           subtractRes.backgroundModel,
                           dof=nparam)
                kcQa.apply(controlCandList, subtractRes.psfMatchingKernel,
                           subtractRes.backgroundModel)

                if self.config.doDetection:
                    kcQa.aggregate(selectSources, self.metadata, allresids,
                                   diaSources)
                else:
                    kcQa.aggregate(selectSources, self.metadata, allresids)

                sensorRef.put(selectSources,
                              self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)

        self.runDebug(exposure, subtractRes, selectSources, kernelSources,
                      diaSources)
        return pipeBase.Struct(
            subtractedExposure=subtractedExposure,
            subtractRes=subtractRes,
            sources=diaSources,
        )
Beispiel #19
0
    def copyIcSourceFields(self, icSourceCat, sourceCat):
        """!Match sources in icSourceCat and sourceCat and copy the specified fields

        @param[in] icSourceCat  catalog from which to copy fields
        @param[in,out] sourceCat  catalog to which to copy fields

        The fields copied are those specified by `config.icSourceFieldsToCopy`
        that actually exist in the schema. This was set up by the constructor
        using self.schemaMapper.
        """
        if self.schemaMapper is None:
            raise RuntimeError("To copy icSource fields you must specify "
                               "icSourceSchema nd icSourceKeys when "
                               "constructing this task")
        if icSourceCat is None or sourceCat is None:
            raise RuntimeError("icSourceCat and sourceCat must both be "
                               "specified")
        if len(self.config.icSourceFieldsToCopy) == 0:
            self.log.warn("copyIcSourceFields doing nothing because "
                          "icSourceFieldsToCopy is empty")
            return

        mc = afwTable.MatchControl()
        mc.findOnlyClosest = False  # return all matched objects
        matches = afwTable.matchXy(icSourceCat, sourceCat,
                                   self.config.matchRadiusPix, mc)
        if self.config.doDeblend:
            deblendKey = sourceCat.schema["deblend_nChild"].asKey()
            # if deblended, keep children
            matches = [m for m in matches if m[1].get(deblendKey) == 0]

        # Because we had to allow multiple matches to handle parents, we now
        # need to prune to the best matches
        # closest matches as a dict of icSourceCat source ID:
        # (icSourceCat source, sourceCat source, distance in pixels)
        bestMatches = {}
        for m0, m1, d in matches:
            id0 = m0.getId()
            match = bestMatches.get(id0)
            if match is None or d <= match[2]:
                bestMatches[id0] = (m0, m1, d)
        matches = list(bestMatches.values())

        # Check that no sourceCat sources are listed twice (we already know
        # that each match has a unique icSourceCat source ID, due to using
        # that ID as the key in bestMatches)
        numMatches = len(matches)
        numUniqueSources = len(set(m[1].getId() for m in matches))
        if numUniqueSources != numMatches:
            self.log.warn("{} icSourceCat sources matched only {} sourceCat "
                          "sources".format(numMatches, numUniqueSources))

        self.log.info("Copying flags from icSourceCat to sourceCat for "
                      "%s sources" % (numMatches,))

        # For each match: set the calibSourceKey flag and copy the desired
        # fields
        for icSrc, src, d in matches:
            src.setFlag(self.calibSourceKey, True)
            # src.assign copies the footprint from icSrc, which we don't want
            # (DM-407)
            # so set icSrc's footprint to src's footprint before src.assign,
            # then restore it
            icSrcFootprint = icSrc.getFootprint()
            try:
                icSrc.setFootprint(src.getFootprint())
                src.assign(icSrc, self.schemaMapper)
            finally:
                icSrc.setFootprint(icSrcFootprint)
Beispiel #20
0
 def testMatchXy(self):
     matches = afwTable.matchXy(self.cat1, self.cat2, 0.01)
     self.assertEquals(len(matches), 6)
     for m in matches:
         self.assertEquals(m.first.getId() + 10, m.second.getId())
         self.assertEquals(m.distance, 0.0)
Beispiel #21
0
    def doPlotWithDetectionsHighlighted(self, runTestResult=None, transientsOnly=True, addPresub=False,
                                        xaxisIsScienceForcedPhot=False, alpha=0.5,
                                        divideByInput=False, actuallyPlot=True, skyLimited=False,
                                        matchDist=np.sqrt(1.5), **kwargs):

        import lsst.afw.table as afwTable
        import lsst.daf.base as dafBase
        import lsst.afw.table.catalogMatches as catMatch

        if actuallyPlot:
            import matplotlib.pyplot as plt
            import matplotlib
            matplotlib.style.use('ggplot')

        #fp_DIFFIM=fp_Zogy, label='Zogy', color='b', alpha=1.0,

        res = runTestResult
        if runTestResult is None or (runTestResult is not None and 'sources' not in runTestResult):
            res = self.runTest(returnSources=True, matchDist=matchDist)

        src = res['sources']
        #del res['sources']
        #print res

        cats = self.doForcedPhot(transientsOnly=transientsOnly)
        sources, fp1, fp2, fp_Zogy, fp_AL, fp_ALd = cats

        # if xaxisIsScienceForcedPhot is True, then don't use sources['inputFlux_science'] --
        #    use fp2['base_PsfFlux_flux'] instead.
        if not xaxisIsScienceForcedPhot:
            srces = sources['inputFlux_science']
        else:
            srces = fp2['base_PsfFlux_flux']

        df = pd.DataFrame()
        df['inputFlux'] = sources['inputFlux_science']
        df['templateFlux'] = fp1['base_PsfFlux_flux']
        df['scienceFlux'] = fp2['base_PsfFlux_flux']
        df['inputId'] = sources['id']
        df['inputCentroid_x'] = sources['centroid_x']
        df['inputCentroid_y'] = sources['centroid_y']

        snrCalced = self.im2.calcSNR(sources['inputFlux_science'], skyLimited=skyLimited)
        df['inputSNR'] = snrCalced

        fp_DIFFIM = [fp_Zogy, fp_AL, fp_ALd]
        label = ['Zogy', 'ALstack', 'ALstack_decorr']
        color = ['b', 'r', 'g']

        for i, fp_d in enumerate(fp_DIFFIM):
            df[label[i] + '_SNR'] = fp_d['base_PsfFlux_flux']/fp_d['base_PsfFlux_fluxSigma']
            df[label[i] + '_flux'] = fp_d['base_PsfFlux_flux']
            df[label[i] + '_fluxSigma'] = fp_d['base_PsfFlux_fluxSigma']

            if actuallyPlot:
                # Plot all sources
                yvals = fp_d['base_PsfFlux_flux']/fp_d['base_PsfFlux_fluxSigma']
                if divideByInput:
                    yvals /= df['inputSNR']
                plt.scatter(srces, yvals,
                            color=color[i], alpha=alpha, label=label[i])
                #plt.scatter(srces,
                #            fp_d['base_PsfFlux_flux']/fp_d['base_PsfFlux_fluxSigma'],
                #            color='k', marker='x', alpha=alpha, label=None)

            if not xaxisIsScienceForcedPhot:
                matches = afwTable.matchXy(sources, src[label[i]], matchDist)
                metadata = dafBase.PropertyList()
                matchCat = catMatch.matchesToCatalog(matches, metadata)
                sources_detected = catalogToDF(sources)
                detected = np.in1d(sources_detected['id'], matchCat['ref_id'])
                sources_detected = sources_detected[detected]
                sources_detected = sources_detected['inputFlux_science']
                snrCalced_detected = snrCalced[detected]
                fp_Zogy_detected = catalogToDF(fp_d)
                detected = np.in1d(fp_Zogy_detected['id'], matchCat['ref_id'])
                fp_Zogy_detected = fp_Zogy_detected[detected]
            else:
                matches = afwTable.matchXy(fp2, src[label[i]], matchDist)
                metadata = dafBase.PropertyList()
                matchCat = catMatch.matchesToCatalog(matches, metadata)
                sources_detected = catalogToDF(fp2)
                detected = np.in1d(sources_detected['id'], matchCat['ref_id'])
                sources_detected = sources_detected[detected]
                sources_detected = sources_detected['base_PsfFlux_flux']
                snrCalced_detected = snrCalced[detected]
                fp_Zogy_detected = catalogToDF(fp_d)
                detected = np.in1d(fp_Zogy_detected['id'], matchCat['ref_id'])
                fp_Zogy_detected = fp_Zogy_detected[detected]

            df[label[i] + '_detected'] = detected
            if actuallyPlot and len(detected) > 0:
                mStyle = matplotlib.markers.MarkerStyle('o', 'none')
                yvals = fp_Zogy_detected['base_PsfFlux_flux']/fp_Zogy_detected['base_PsfFlux_fluxSigma']
                if divideByInput:
                    yvals /= snrCalced_detected
                plt.scatter(sources_detected, yvals,
                            #label=label[i], s=20, color=color[i], alpha=alpha) #, edgecolors='r')
                            label=None, s=30, edgecolors='r', facecolors='none', marker='o', alpha=1.0) # edgecolors=color[i],

        if addPresub:  # Add measurements in original science and template images
            df['templateSNR'] = fp1['base_PsfFlux_flux']/fp1['base_PsfFlux_fluxSigma']
            df['scienceSNR'] = fp2['base_PsfFlux_flux']/fp2['base_PsfFlux_fluxSigma']
            if actuallyPlot:
                yvals = fp1['base_PsfFlux_flux']/fp1['base_PsfFlux_fluxSigma']
                if divideByInput:
                    yvals /= df['inputSNR']
                plt.scatter(srces, yvals,
                            label='template', color='y', alpha=alpha)
                yvals = fp2['base_PsfFlux_flux']/fp2['base_PsfFlux_fluxSigma']
                if divideByInput:
                    yvals /= df['inputSNR']
                plt.scatter(srces, yvals,
                            label='science', color='orange', alpha=alpha-0.2)

        if actuallyPlot:
            if not divideByInput:
                if xaxisIsScienceForcedPhot:
                    plt.scatter(srces, snrCalced, color='k', alpha=alpha-0.2, s=7, label='Input SNR')
                else:
                    plt.plot(srces, snrCalced, color='k', alpha=alpha-0.2, label='Input SNR')
                plt.ylabel('measured SNR')
            else:
                plt.ylabel('measured SNR / input SNR')

            if len(detected) > 0:
                plt.scatter([10000], [0], s=30, edgecolors='r', facecolors='none', marker='o', label='Detected')
            legend = plt.legend(loc='upper left', scatterpoints=3)
            for label in legend.get_texts():
                label.set_fontsize('x-small')
            if not xaxisIsScienceForcedPhot:
                plt.xlabel('input flux')
            else:
                plt.xlabel('science flux (measured)')

        return df, res
Beispiel #22
0
 def testMatchXy(self):
     matches = afwTable.matchXy(self.cat1, self.cat2, 0.01)
     self.assertEquals(len(matches), 6)
     for m in matches:
         self.assertEquals(m.first.getId() + 10, m.second.getId())
         self.assertEquals(m.distance, 0.0)
Beispiel #23
0
    def run(self, sensorRef):
        """Subtract an image from a template coadd and measure the result
    
        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources
        
        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"
            
        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = long(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
        
        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        if self.config.useWinter2013Hacks and self.config.winter2013borderMask > 0:
            self.log.warn("USING WINTER2013 HACK: MASKING BORDER PIXELS")
            bbox = exposure.getBBox(afwImage.PARENT)
            bbox.grow(-self.config.winter2013borderMask)
            self.setEdgeBits(exposure.getMaskedImage(), bbox, 
                             exposure.getMaskedImage().getMask().getPlaneBitMask("NO_DATA"))
            
        # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
        ctr = afwGeom.Box2D(exposure.getBBox(afwImage.PARENT)).getCenter()
        psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr))
        scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
        
        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None   # Sources on the template image
        if self.config.doSubtract:
            templateExposure, templateSources = self.getTemplate(exposure, sensorRef)

            # sigma of PSF of template image before warping
            ctr = afwGeom.Box2D(templateExposure.getBBox(afwImage.PARENT)).getCenter()
            psfAttr = PsfAttributes(templateExposure.getPsf(), afwGeom.Point2I(ctr))
            templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getKernel().getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = psf
                afwMath.convolve(destMI, srcMI, preConvPsf.getKernel(), convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn("Src product does not exist; running detection, measurement, selection")
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure, 
                        sigma = scienceSigmaPost, 
                        doSmooth = not self.doPreConvolve,
                        idFactory = idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
                                                 referenceFwhmPix = scienceSigmaPost * FwhmPerSigma,
                                                 targetFwhmPix = templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    self.kcQa = diUtils.KernelCandidateQa(nparam, self.log)
                    selectSources = self.kcQa.addToSchema(selectSources)

                astrometer = measAstrom.Astrometry(measAstrom.MeasAstromConfig())
                astromRet = astrometer.useKnownWcs(selectSources, exposure=exposure)
                matches = astromRet.matches
                kernelSources = self.sourceSelector.selectSources(exposure, selectSources, matches=matches)
                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                [kernelSources.remove(x) for x in controlSources]

                self.log.info("Selected %d / %d sources for Psf matching (%d for control sample)" \
                                  % (len(kernelSources), len(selectSources), len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")
                
                if templateSources is None:
                    # First step: we need to subtract the background out
                    # for detection and measurement.  Use large binsize
                    # for the background estimation.
                    binsize = self.config.templateBgBinSize
    
                    # Second step: we need to run detection on the
                    # background-subtracted template
                    #
                    # Estimate FWHM for detection
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma = templateSigma,
                        doSmooth = True,
                        idFactory = idFactory,
                        binsize = binsize,
                    )

                # Third step: we need to fit the relative astrometry.
                #
                # One problem is that the SIP fits are w.r.t. CRPIX,
                # and these coadd patches have the CRPIX of the entire
                # tract, i.e. off the image.  This causes
                # register.fitWcs to fail.  A workaround for now is to
                # re-fit the Wcs which returns with a CRPIX that is on
                # the image, and *then* to fit for the relative Wcs.
                #
                # Requires low Sip order to avoid overfitting

                # useWinter2013Hacks includes us using the deep calexp
                # as the template.  In this case we don't need to
                # refit the Wcs.
                if not self.config.useWinter2013Hacks:
                    sipOrder = self.config.templateSipOrder
                    astrometer = measAstrom.Astrometry(measAstrom.MeasAstromConfig(sipOrder=sipOrder))
                    newWcs = astrometer.determineWcs(templateSources, templateExposure).getWcs()
                    results = self.register.run(templateSources, newWcs, 
                                                templateExposure.getBBox(afwImage.PARENT), selectSources)
                else:
                    if self.config.winter2013WcsShift > 0.0:
                        offset = afwGeom.Extent2D(self.config.winter2013WcsShift, 
                                                  self.config.winter2013WcsShift)
                        cKey = templateSources[0].getTable().getCentroidKey()
                        for source in templateSources:
                            centroid = source.get(cKey)
                            source.set(cKey, centroid+offset)
                    elif self.config.winter2013WcsRms > 0.0:
                        cKey = templateSources[0].getTable().getCentroidKey()
                        for source in templateSources:
                            offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(), 
                                                      self.config.winter2013WcsRms*numpy.random.normal())
                            centroid = source.get(cKey)
                            source.set(cKey, centroid+offset)

                    results = self.register.run(templateSources, templateExposure.getWcs(),
                                                templateExposure.getBBox(afwImage.PARENT), selectSources)

                warpedExp = self.register.warpExposure(templateExposure, results.wcs, 
                                            exposure.getWcs(), exposure.getBBox(afwImage.PARENT))
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    refCoordKey = results.matches[0].first.getTable().getCoordKey()
                    inCentroidKey = results.matches[0].second.getTable().getCentroidKey()
                    sids      = [m.first.getId() for m in results.matches]
                    positions = [m.first.get(refCoordKey) for m in results.matches]
                    residuals = [m.first.get(refCoordKey).getOffsetFrom(
                                   results.wcs.pixelToSky(m.second.get(inCentroidKey))) for
                                 m in results.matches]
                    allresids = dict(zip(sids, zip(positions, residuals)))

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            #Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure = templateExposure,
                scienceExposure = exposure,
                scienceFwhmPix = scienceSigmaPost * FwhmPerSigma,
                templateFwhmPix = templateSigma * FwhmPerSigma,
                candidateList = kernelSources,
                convolveTemplate = self.config.convolveTemplate,
                doWarping = not self.config.doUseRegister
            )
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure, self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)
            
            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        templateExposure, templateSources = self.getTemplate(exposure, sensorRef)
                    subtractedExposure.setPsf(templateExposure.getPsf())

            # Erase existing detection mask planes
            mask  = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table = table,
                exposure = subtractedExposure,
                doSmooth = not self.config.doPreConvolve
                )

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint, 
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" % (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                if len(diaSources) < self.config.maxDiaSourcesToMeasure:
                    self.dipolemeasurement.run(subtractedExposure, diaSources)
                else:
                    self.measurement.run(subtractedExposure, diaSources)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()

                    # This does not do what I expect so I cobbled together a brute force method in python 
                    srcMatches = afwTable.matchXy(sensorRef.get("src"), diaSources, matchRadPixel, True) 
                    srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) for \
                                             srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" % (len(srcMatchDict), 
                                                                             len(diaSources)))
                else:
                    self.log.warn("Src product does not exist; cannot match with diaSources")
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                astrometer = measAstrom.Astrometry(measAstrom.MeasAstromConfig(catalogMatchDist=matchRadAsec))
                astromRet = astrometer.useKnownWcs(diaSources, exposure=exposure)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn("No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info("Matched %d / %d diaSources to reference catalog" % (len(refMatches), 
                                                                                       len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for \
                                             refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:                    
                    sid = diaSource.getId()
                    if srcMatchDict.has_key(sid):
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if refMatchDict.has_key(sid):
                        diaSource.set("refMatchId", refMatchDict[sid])
                        
            if diaSources is not None and self.config.doWriteSources:
                sourceWriteFlags = (0 if self.config.doWriteHeavyFootprintsInSources
                                    else afwTable.SOURCE_IO_NO_HEAVY_FOOTPRINTS)
                sensorRef.put(diaSources, self.config.coaddName + "Diff_diaSrc", flags=sourceWriteFlags)

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False): # include bad candidates
                        kernelCandList.append(cast_KernelCandidateF(cand))

                # Get basis list to build control sample kernels
                basisList = afwMath.cast_LinearCombinationKernel(
                    kernelCandList[0].getKernel(KernelCandidateF.ORIG)).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandList(controlSources, subtractRes.warpedExposure, exposure, 
                                                      self.config.subtract.kernel.active, 
                                                      self.config.subtract.kernel.active.detectionConfig, 
                                                      self.log, dobuild=True, basisList=basisList)

                self.kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel, 
                                dof=nparam)
                self.kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)

                if self.config.doDetection:
                    self.kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
                else:
                    self.kcQa.aggregate(selectSources, self.metadata, allresids)

                #Persist using butler
                sensorRef.put(selectSources, self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)
 
        self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
        return pipeBase.Struct(
            subtractedExposure = subtractedExposure,
            subtractRes = subtractRes,
            sources = diaSources,
        )
Beispiel #24
0
    def catalogStatistics(self, exposure, catalog, uncorrectedCatalog,
                          statControl):
        """Measure the catalog statistics.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            The exposure to measure.
        catalog : `lsst.afw.table.Table`
            The catalog to measure.
        uncorrectedCatalog : `lsst.afw.table.Table`
            The uncorrected catalog to measure.
        statControl : `lsst.afw.math.StatisticsControl`
            Statistics control object with parameters defined by
            the config.

        Returns
        -------
        outputStatistics : `dict` [`str`, `dict` [`str`, scalar]]
            A dictionary indexed by the amplifier name, containing
            dictionaries of the statistics measured and their values.
        """
        outputStatistics = {}

        matches = afwTable.matchXy(catalog, uncorrectedCatalog,
                                   self.config.matchRadiusPix)
        outputStatistics['NUM_MATCHES'] = len(matches)

        magnitude = []
        sizeDiff = []
        if not matches:
            outputStatistics['MAGNITUDES'] = magnitude
            outputStatistics['SIZE_DIFF'] = sizeDiff
            return outputStatistics

        for source, uncorrectedSource, d in matches:
            # This uses the simple difference in source moments.
            sourceMagnitude = -2.5 * np.log10(source.getPsfInstFlux())
            sourceSize = source['base_SdssShape_xx'] + source[
                'base_SdssShape_yy']
            uncorrectedSize = uncorrectedSource[
                'base_SdssShape_xx'] + uncorrectedSource['base_SdssShape_yy']

            magnitude.append(float(sourceMagnitude))
            sizeDiff.append(float(uncorrectedSize - sourceSize))

        mask = np.isfinite(magnitude) * np.isfinite(sizeDiff)
        if 'BRIGHT_SLOPE' in self.config.catalogStatKeywords:
            exponentialFit = least_squares(modelResidual, [0.0, -0.01, 0.0],
                                           args=(np.array(magnitude)[mask],
                                                 np.array(sizeDiff)[mask]),
                                           loss='cauchy')

            outputStatistics['BRIGHT_SLOPE'] = float(exponentialFit.x[1])
            outputStatistics['FIT_SUCCESS'] = exponentialFit.success
            self.debugFit('brightSlope', magnitude, sizeDiff, exponentialFit.x)

        outputStatistics['MAGNITUDES'] = magnitude
        outputStatistics['SIZE_DIFF'] = sizeDiff

        return outputStatistics
Beispiel #25
0
    def run(self, sensorRef, templateIdList=None):
        """Subtract an image from a template coadd and measure the result

        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources

        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"

        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = long(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)

        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None   # Sources on the template image
        if self.config.doSubtract:
            print templateIdList
            template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
            templateExposure = template.exposure
            templateSources = template.sources

            # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
            ctr = afwGeom.Box2D(exposure.getBBox()).getCenter()
            psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr))
            scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)

            # sigma of PSF of template image before warping
            ctr = afwGeom.Box2D(templateExposure.getBBox()).getCenter()
            psfAttr = PsfAttributes(templateExposure.getPsf(), afwGeom.Point2I(ctr))
            templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = srcPsf
                afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn("Src product does not exist; running detection, measurement, selection")
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure,
                        sigma = scienceSigmaPost,
                        doSmooth = not self.doPreConvolve,
                        idFactory = idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
                                                 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
                                                 targetFwhmPix=templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    kcQa = KernelCandidateQa(nparam)
                    selectSources = kcQa.addToSchema(selectSources)

                if self.config.kernelSourcesFromRef:
                    # match exposure sources to reference catalog
                    astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
                    matches = astromRet.matches
                elif templateSources:
                    # match exposure sources to template sources
                    matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
                                                  False)
                else:
                    raise RuntimeError("doSelectSources=True and kernelSourcesFromRef=False," +
                                       "but template sources not available. Cannot match science " +
                                       "sources with template sources. Run process* on data from " +
                                       "which templates are built.")

                kernelSources = self.sourceSelector.selectStars(exposure, selectSources,
                    matches=matches).starCat

                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                kernelSources = [k for i,k in enumerate(kernelSources) if i % self.config.controlStepSize]

                if self.config.doSelectDcrCatalog:
                    redSelector  = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
                    redSources   = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
                    controlSources.extend(redSources)

                    blueSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
                    blueSources  = blueSelector.selectStars(exposure, selectSources, matches=matches).starCat
                    controlSources.extend(blueSources)

                if self.config.doSelectVariableCatalog:
                    varSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(includeVariable=True))
                    varSources  = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
                    controlSources.extend(varSources)

                self.log.info("Selected %d / %d sources for Psf matching (%d for control sample)" 
                              % (len(kernelSources), len(selectSources), len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")

                if templateSources is None:
                    # Run detection on the template, which is
                    # temporarily background-subtracted
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma=templateSigma,
                        doSmooth=True,
                        idFactory=idFactory
                    )

                # Third step: we need to fit the relative astrometry.
                #
                wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources)
                warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
                                            exposure.getWcs(), exposure.getBBox())
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    # Grab matches to reference catalog
                    srcToMatch = {x.second.getId() : x.first for x in matches}

                    refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
                    inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
                    sids      = [m.first.getId() for m in wcsResults.matches]
                    positions = [m.first.get(refCoordKey) for m in wcsResults.matches]
                    residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey))) for m in wcsResults.matches]
                    allresids = dict(zip(sids, zip(positions, residuals)))

                    cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey))) for m in wcsResults.matches]
                    colors    = numpy.array([-2.5*numpy.log10(srcToMatch[x].get("g"))
                                              + 2.5*numpy.log10(srcToMatch[x].get("r")) 
                                              for x in sids if x in srcToMatch.keys()])
                    dlong     = numpy.array([r[0].asArcseconds() for s,r in zip(sids, cresiduals) 
                                             if s in srcToMatch.keys()])
                    dlat      = numpy.array([r[1].asArcseconds() for s,r in zip(sids, cresiduals) 
                                             if s in srcToMatch.keys()])
                    idx1      = numpy.where(colors<self.sourceSelector.config.grMin)
                    idx2      = numpy.where((colors>=self.sourceSelector.config.grMin)&
                                            (colors<=self.sourceSelector.config.grMax))
                    idx3      = numpy.where(colors>self.sourceSelector.config.grMax)
                    rms1Long  = IqrToSigma*(numpy.percentile(dlong[idx1],75)-numpy.percentile(dlong[idx1],25))
                    rms1Lat   = IqrToSigma*(numpy.percentile(dlat[idx1],75)-numpy.percentile(dlat[idx1],25))
                    rms2Long  = IqrToSigma*(numpy.percentile(dlong[idx2],75)-numpy.percentile(dlong[idx2],25))
                    rms2Lat   = IqrToSigma*(numpy.percentile(dlat[idx2],75)-numpy.percentile(dlat[idx2],25))
                    rms3Long  = IqrToSigma*(numpy.percentile(dlong[idx3],75)-numpy.percentile(dlong[idx3],25))
                    rms3Lat   = IqrToSigma*(numpy.percentile(dlat[idx3],75)-numpy.percentile(dlat[idx3],25))
                    self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f"  % (numpy.median(dlong[idx1]), 
                                                                                  rms1Long,
                                                                                  numpy.median(dlat[idx1]), 
                                                                                  rms1Lat))
                    self.log.info("Green star offsets'': %.3f %.3f, %.3f %.3f"  % (numpy.median(dlong[idx2]), 
                                                                                   rms2Long,
                                                                                   numpy.median(dlat[idx2]), 
                                                                                   rms2Lat))
                    self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f"  % (numpy.median(dlong[idx3]), 
                                                                                 rms3Long,
                                                                                 numpy.median(dlat[idx3]), 
                                                                                 rms3Lat))

                    self.metadata.add("RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
                    self.metadata.add("RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
                    self.metadata.add("RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
                    self.metadata.add("RegisterBlueLongOffsetStd", rms1Long)
                    self.metadata.add("RegisterGreenLongOffsetStd", rms2Long)
                    self.metadata.add("RegisterRedLongOffsetStd", rms3Long)

                    self.metadata.add("RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
                    self.metadata.add("RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
                    self.metadata.add("RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
                    self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat)
                    self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat)
                    self.metadata.add("RegisterRedLatOffsetStd", rms3Lat)

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            #Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure=templateExposure,
                scienceExposure=exposure,
                candidateList=kernelSources,
                convolveTemplate=self.config.convolveTemplate,
                doWarping=not self.config.doUseRegister
            )
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure, self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)

            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
                    subtractedExposure.setPsf(template.exposure.getPsf())

            # Erase existing detection mask planes
            mask  = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table=table,
                exposure=subtractedExposure,
                doSmooth=not self.config.doPreConvolve
                )

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint,
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" % (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                self.measurement.run(diaSources, subtractedExposure)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()

                    srcMatches = afwTable.matchXy(sensorRef.get("src"), diaSources, matchRadPixel, True)
                    srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) for 
                                         srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" % (len(srcMatchDict),
                                                                             len(diaSources)))
                else:
                    self.log.warn("Src product does not exist; cannot match with diaSources")
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                refAstromConfig = measAstrom.AstrometryConfig()
                refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
                refAstrometer = measAstrom.AstrometryTask(refAstromConfig)
                astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn("No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info("Matched %d / %d diaSources to reference catalog" % (len(refMatches),
                                                                                       len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for \
                                             refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:
                    sid = diaSource.getId()
                    if srcMatchDict.has_key(sid):
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if refMatchDict.has_key(sid):
                        diaSource.set("refMatchId", refMatchDict[sid])

            if diaSources is not None and self.config.doWriteSources:
                sensorRef.put(diaSources, self.config.coaddName + "Diff_diaSrc")

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False): # include bad candidates
                        kernelCandList.append(cast_KernelCandidateF(cand))

                # Get basis list to build control sample kernels
                basisList = afwMath.cast_LinearCombinationKernel(
                    kernelCandList[0].getKernel(KernelCandidateF.ORIG)).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandidateList(controlSources, 
                                                           subtractRes.warpedExposure, exposure,
                                                           self.config.subtract.kernel.active,
                                                           self.config.subtract.kernel.active.detectionConfig,
                                                           self.log, doBuild=True, basisList=basisList)

                kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
                                dof=nparam)
                kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)

                if self.config.doDetection:
                    kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
                else:
                    kcQa.aggregate(selectSources, self.metadata, allresids)

                sensorRef.put(selectSources, self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)

        self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
        return pipeBase.Struct(
            subtractedExposure=subtractedExposure,
            subtractRes=subtractRes,
            sources=diaSources,
        )
Beispiel #26
0
    def runTest(self, subtractMethods=['ALstack', 'Zogy_S', 'Zogy', 'ALstack_decorr'],
                zogyImageSpace=False, matchDist=np.sqrt(1.5), returnSources=False, **kwargs):
        D_Zogy = S_Zogy = res = D_AL = None
        src = {}
        # Run diffim first
        for subMethod in subtractMethods:
            if subMethod is 'ALstack' or subMethod is 'ALstack_decorr':
                if self.ALres is None:
                    self.ALres = self.doAlInStack(doPreConv=False, doDecorr=True, **kwargs)
            if subMethod is 'Zogy_S':
                if self.S_Zogy is None:
                    self.doZogy(computeScorr=True, inImageSpace=zogyImageSpace)
                S_Zogy = self.S_Zogy
                D_Zogy = self.D_Zogy
            if subMethod is 'Zogy':
                if self.D_Zogy is None:
                    self.doZogy(computeScorr=False, inImageSpace=zogyImageSpace)
                D_Zogy = self.D_Zogy
            if subMethod is 'AL':  # my clean-room (pure python, slow) version of A&L
                try:
                    if self.D_AL is None:
                        self.doAL(spatialKernelOrder=0, spatialBackgroundOrder=1)
                    D_AL = self.D_AL
                except Exception as e:
                    print(e)
                    D_AL = None

            # Run detection next
            #try:
            if subMethod is 'ALstack':  # Note we DONT set it to 5.5 -- best for noise-free template.
                src_AL = doDetection(self.ALres.subtractedExposure)
                src['ALstack'] = src_AL
            elif subMethod is 'ALstack_decorr':
                src_AL2 = doDetection(self.ALres.decorrelatedDiffim)
                src['ALstack_decorr'] = src_AL2
            elif subMethod is 'Zogy':
                D_Zogy = afwExp(D_Zogy)
                src_Zogy = doDetection(D_Zogy)
                src['Zogy'] = src_Zogy
            elif subMethod is 'Zogy_S':  # 'pixel_stdev' doesn't work, so just divide the image by
                S_Zogy = afwExp(S_Zogy)
                tmp_S = S_Zogy.clone()   # the variance and use a 'value' threshold.
                #tmp_S.im /= tmp_S.var
                #tmp_S.var /= tmp_S.var
                tmp_S_i = tmp_S.getMaskedImage().getImage().getArray()
                tmp_S_i[:, :] /= tmp_S.getMaskedImage().getVariance().getArray()[:, :]
                #src_SZogy = doDetection(S_ZOGY,
                #                        thresholdType='pixel_stdev', doSmooth=False)
                src_SZogy = doDetection(tmp_S, thresholdType='value', doSmooth=False)
                src['SZogy'] = src_SZogy
            elif subMethod is 'AL' and D_AL is not None:
                D_AL = afwExp(D_AL)
                src_AL = doDetection(D_AL)
                src['AL'] = src_AL
            #except Exception as e:
            #    print(e)
            #    pass

        # Compare detections to input sources and get true positives and false negatives
        changedCentroid = self.getCentroidsCatalog(transientsOnly=True)

        import lsst.afw.table as afwTable
        detections = matchCat = {}
        for key in src:
            srces = src[key]
            srces = srces[~srces['base_PsfFlux_flag']]  # this works!
            matches = afwTable.matchXy(changedCentroid, srces, matchDist)  # these should not need uniquifying
            true_pos = len(matches)
            false_neg = len(changedCentroid) - len(matches)
            false_pos = len(srces) - len(matches)
            detections[key] = {'TP': true_pos, 'FN': false_neg, 'FP': false_pos}

        # sources, fp1, fp2, fp_Zogy, fp_AL, fp_ALd = self.doForcedPhot(transientsOnly=True)
        # if mc_Zogy is not None:
        #     matches = afwTable.matchXy(pp_Zogy, sources, 1.0)
        #     matchedCat = catMatch.matchesToCatalog(matches, metadata)

        if returnSources:
            detections['sources'] = src

        return detections