Exemplo n.º 1
0
def getCornerCoords(wcs, bbox):
    """Return the coords of the four corners of a bounding box
    """
    cornerPosList = afwGeom.Box2D(bbox).getCorners()
    return wcs.pixelToSky(cornerPosList)
Exemplo n.º 2
0
 def setUp(self):
     p, e = afwGeom.Point2D(1.0, 1.0), afwGeom.Extent2D(0.5, 0.5)
     self.data = afwGeom.Box2D(p, e)
Exemplo n.º 3
0
    def test(self):
        """Check that we can create and use a coadd ApCorrMap"""
        coaddBox = afwGeom.Box2I(afwGeom.Point2I(0, 0),
                                 afwGeom.Extent2I(100, 100))
        scale = 5.0e-5  # deg/pix; for CD matrix
        coord = afwCoord.Coord(0.0 * afwGeom.degrees, 0.0 * afwGeom.degrees)
        center = afwGeom.Point2D(
            afwGeom.Extent2D(coaddBox.getDimensions()) * 0.5)
        coaddWcs = afwImage.makeWcs(coord, afwGeom.Point2D(0, 0), scale, 0.0,
                                    0.0, scale)
        schema = afwTable.ExposureTable.makeMinimalSchema()
        weightKey = schema.addField("customweightname",
                                    type="D",
                                    doc="Coadd weight")
        catalog = afwTable.ExposureCatalog(schema)

        # Non-overlapping
        num = 5
        inputBox = afwGeom.Box2I(afwGeom.Point2I(0, 0),
                                 afwGeom.Extent2I(10, 10))
        validBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(7, 7))
        pointList = []
        pointListValid = []
        overlapping = []

        for i in range(num):
            value = numpy.array([[1]],
                                dtype=float)  # Constant with value = i+1
            apCorrMap = afwImage.ApCorrMap()
            bf = afwMath.ChebyshevBoundedField(inputBox, value * (i + 1))
            apCorrMap.set("only", bf)

            point = afwGeom.Point2D(0, 0) - afwGeom.Extent2D(
                coaddBox.getDimensions()) * (i + 0.5) / num
            wcs = afwImage.makeWcs(coord, point, scale, 0.0, 0.0, scale)
            center = afwGeom.Box2D(inputBox).getCenter()
            pointList.append(coaddWcs.skyToPixel(wcs.pixelToSky(center)))

            # This point will only be valid for the second overlapping record
            pointValid = center + afwGeom.Extent2D(4, 4)
            pointListValid.append(
                coaddWcs.skyToPixel(wcs.pixelToSky(pointValid)))

            # A record with the valid polygon defining a limited region
            record = catalog.getTable().makeRecord()
            record.setWcs(wcs)
            record.setBBox(inputBox)
            record.setApCorrMap(apCorrMap)
            record.set(weightKey, i + 1)
            record['id'] = i
            record.setValidPolygon(Polygon(afwGeom.Box2D(validBox)))
            catalog.append(record)

            # An overlapping record with the whole region as valid
            record = catalog.getTable().makeRecord()
            record.setWcs(wcs)
            record.setBBox(inputBox)
            apCorrMap = afwImage.ApCorrMap()
            bf = afwMath.ChebyshevBoundedField(inputBox, value * (i + 2))
            apCorrMap.set("only", bf)
            record.setApCorrMap(apCorrMap)
            record.set(weightKey, i + 2)
            record['id'] = i + num
            record.setValidPolygon(Polygon(afwGeom.Box2D(inputBox)))
            catalog.append(record)

        apCorrMap = measAlg.makeCoaddApCorrMap(catalog, coaddBox, coaddWcs,
                                               "customweightname")
        # This will test a point where both records contribute
        self.assertApCorrMap(apCorrMap, pointList)
        # Only the second record will be valid for this point
        self.assertApCorrMapValid(apCorrMap, pointListValid)

        filename = "tests/coaddApCorrMap.fits"
        exposure = afwImage.ExposureF(1, 1)
        exposure.getInfo().setApCorrMap(apCorrMap)
        exposure.writeFits(filename)
        exposure = afwImage.ExposureF(filename)
        self.assertApCorrMap(exposure.getInfo().getApCorrMap(), pointList)
        self.assertApCorrMapValid(exposure.getInfo().getApCorrMap(),
                                  pointListValid)
        os.unlink(filename)
Exemplo n.º 4
0
    def run(self, exposure, sensorRef, templateIdList=None):
        """Retrieve and mosaic a template coadd exposure that overlaps the exposure

        Parameters
        ----------
        exposure: `lsst.afw.image.Exposure`
            an exposure for which to generate an overlapping template
        sensorRef : TYPE
            a Butler data reference that can be used to obtain coadd data
        templateIdList : TYPE, optional
            list of data ids (unused)

        Returns
        -------
        result : `struct`
            return a pipeBase.Struct:

            - ``exposure`` : a template coadd exposure assembled out of patches
            - ``sources`` :  None for this subtask
        """
        skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap")
        expWcs = exposure.getWcs()
        expBoxD = afwGeom.Box2D(exposure.getBBox())
        expBoxD.grow(self.config.templateBorderSize)
        ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())
        tractInfo = skyMap.findTract(ctrSkyPos)
        self.log.info("Using skyMap tract %s" % (tractInfo.getId(),))
        skyCorners = [expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners()]
        patchList = tractInfo.findPatchList(skyCorners)

        if not patchList:
            raise RuntimeError("No suitable tract found")
        self.log.info("Assembling %s coadd patches" % (len(patchList),))

        # compute coadd bbox
        coaddWcs = tractInfo.getWcs()
        coaddBBox = afwGeom.Box2D()
        for skyPos in skyCorners:
            coaddBBox.include(coaddWcs.skyToPixel(skyPos))
        coaddBBox = afwGeom.Box2I(coaddBBox)
        self.log.info("exposure dimensions=%s; coadd dimensions=%s" %
                      (exposure.getDimensions(), coaddBBox.getDimensions()))

        # assemble coadd exposure from subregions of patches
        coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
        coaddExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan)
        nPatchesFound = 0
        coaddFilter = None
        coaddPsf = None
        for patchInfo in patchList:
            patchSubBBox = patchInfo.getOuterBBox()
            patchSubBBox.clip(coaddBBox)
            patchArgDict = dict(
                datasetType=self.getCoaddDatasetName() + "_sub",
                bbox=patchSubBBox,
                tract=tractInfo.getId(),
                patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
                numSubfilters=self.config.numSubfilters,
            )
            if patchSubBBox.isEmpty():
                self.log.info("skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels" % patchArgDict)
                continue

            if self.config.coaddName == 'dcr':
                if not sensorRef.datasetExists(subfilter=0, **patchArgDict):
                    self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s,"
                                  " numSubfilters=%(numSubfilters)s, subfilter=0 does not exist"
                                  % patchArgDict)
                    continue
                self.log.info("Constructing DCR-matched template for patch %s" % patchArgDict)
                dcrModel = DcrModel.fromDataRef(sensorRef, **patchArgDict)
                # The edge pixels of the DcrCoadd may contain artifacts due to missing data.
                # Each patch has significant overlap, and the contaminated edge pixels in
                # a new patch will overwrite good pixels in the overlap region from
                # previous patches.
                # Shrink the BBox to remove the contaminated pixels,
                # but make sure it is only the overlap region that is reduced.
                patchInnerBBox = patchInfo.getInnerBBox()
                patchInnerBBox.clip(coaddBBox)
                dcrBBox = afwGeom.Box2I(patchSubBBox)
                dcrBBox.grow(-self.config.templateBorderSize)
                dcrBBox.include(patchInnerBBox)
                coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox,
                                                           wcs=coaddWcs,
                                                           visitInfo=exposure.getInfo().getVisitInfo())
            else:
                if not sensorRef.datasetExists(**patchArgDict):
                    self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist"
                                  % patchArgDict)
                    continue
                self.log.info("Reading patch %s" % patchArgDict)
                coaddPatch = sensorRef.get(**patchArgDict)
            nPatchesFound += 1
            coaddExposure.maskedImage.assign(coaddPatch.maskedImage, coaddPatch.getBBox())
            if coaddFilter is None:
                coaddFilter = coaddPatch.getFilter()

            # Retrieve the PSF for this coadd tract, if not already retrieved
            if coaddPsf is None and coaddPatch.hasPsf():
                coaddPsf = coaddPatch.getPsf()

        if nPatchesFound == 0:
            raise RuntimeError("No patches found!")

        if coaddPsf is None:
            raise RuntimeError("No coadd Psf found!")

        coaddExposure.setPsf(coaddPsf)
        coaddExposure.setFilter(coaddFilter)
        return pipeBase.Struct(exposure=coaddExposure,
                               sources=None)
Exemplo n.º 5
0
def showCamera(camera,
               imageSource=FakeImageDataSource(),
               imageFactory=afwImage.ImageF,
               detectorNameList=None,
               binSize=10,
               bufferSize=10,
               frame=None,
               overlay=True,
               title="",
               showWcs=None,
               ctype=afwDisplay.GREEN,
               textSize=1.25,
               originAtCenter=True,
               display=None,
               **kwargs):
    """!Show a Camera on display, with the specified display

    The rotation of the sensors is snapped to the nearest multiple of 90 deg.
    Also note that the pixel size is constant over the image array. The lower left corner (LLC) of each
    sensor amp is snapped to the LLC of the pixel containing the LLC of the image.
    if overlay show the IDs and detector boundaries

    @param[in] camera  Camera to show
    @param[in] imageSource  Source to get Ccd images from.  Must have a getCcdImage method.
    @param[in] imageFactory  Type of image to make
    @param[in] detectorNameList  List of names of Detectors to use. If None use all
    @param[in] binSize  bin factor
    @param[in] bufferSize  size of border in binned pixels to make around camera image.
    @param[in] frame  specify image display (@deprecated; new code should use display)
    @param[in] overlay  Overlay Detector IDs and boundaries?
    @param[in] title  Title in display
    @param[in] showWcs whether to include a WCS in the display
    @param[in] ctype  Color to use when drawing Detector boundaries
    @param[in] textSize  Size of detector labels
    @param[in] originAtCenter Put origin of the camera WCS at the center of the image? Else it will be LL
    @param[in] display  image display on which to display
    @param[in] **kwargs all remaining keyword arguments are passed to makeImageFromCamera
    @return the mosaic image
    """
    display = _getDisplayFromDisplayOrFrame(display, frame)

    if binSize < 1:
        binSize = 1
    cameraImage = makeImageFromCamera(camera,
                                      detectorNameList=detectorNameList,
                                      bufferSize=bufferSize,
                                      imageSource=imageSource,
                                      imageFactory=imageFactory,
                                      binSize=binSize,
                                      **kwargs)

    if detectorNameList is None:
        ccdList = [camera[name] for name in camera.getNameIter()]
    else:
        ccdList = [camera[name] for name in detectorNameList]

    if detectorNameList is None:
        camBbox = camera.getFpBBox()
    else:
        camBbox = afwGeom.Box2D()
        for detName in detectorNameList:
            for corner in camera[detName].getCorners(FOCAL_PLANE):
                camBbox.include(corner)
    pixelSize = ccdList[0].getPixelSize()

    if showWcs:
        if originAtCenter:
            wcsReferencePixel = afwGeom.Box2D(
                cameraImage.getBBox()).getCenter()
        else:
            wcsReferencePixel = afwGeom.Point2I(0, 0)
        wcs = makeFocalPlaneWcs(pixelSize * binSize, wcsReferencePixel)
    else:
        wcs = None

    if display:
        if title == "":
            title = camera.getName()
        display.mtv(cameraImage, title=title, wcs=wcs)

        if overlay:
            with display.Buffering():
                camBbox = getCameraImageBBox(camBbox, pixelSize,
                                             bufferSize * binSize)
                bboxList = getCcdInCamBBoxList(ccdList, binSize, pixelSize,
                                               camBbox.getMin())
                for bbox, ccd in zip(bboxList, ccdList):
                    nQuarter = ccd.getOrientation().getNQuarter()
                    # borderWidth to 0.5 to align with the outside edge of the
                    # pixel
                    displayUtils.drawBBox(bbox,
                                          borderWidth=0.5,
                                          ctype=ctype,
                                          display=display)
                    dims = bbox.getDimensions()
                    display.dot(ccd.getName(),
                                bbox.getMinX() + dims.getX() / 2,
                                bbox.getMinY() + dims.getY() / 2,
                                ctype=ctype,
                                size=textSize,
                                textAngle=nQuarter * 90)

    return cameraImage
Exemplo n.º 6
0
    def cutout_from_pos(self, params: dict):
        """ Get cutout of source image by supported SODA shapes:
                POS: CIRCLE, RANGE, POLYGON
                LSST extension: BRECT

        Parameters
        ----------
        params: `dict`
            the POS parameter.
        Returns
        -------
        cutout: `afwImage.Exposure`

        """
        _pos = params["POS"]
        _id = params["ID"]
        db, ds, filt = _id.split(".")
        pos_items = _pos.split()
        shape = pos_items[0]
        if shape == "BRECT":
            if len(pos_items) < 6:
                raise Exception("BRECT: invalid parameters")
            ra, dec = float(pos_items[1]), float(pos_items[2])
            w, h = float(pos_items[3]), float(pos_items[4])
            unit_size = pos_items[5]
            cutout = self.cutout_from_nearest(ra, dec, w, h, unit_size, filt)
            return cutout
        elif shape == "CIRCLE":
            if len(pos_items) < 4:
                raise Exception("CIRCLE: invalid parameters")
            ra, dec = float(pos_items[1]), float(pos_items[2])
            radius = float(pos_items[3])
            # convert from deg to pixels by wcs (ICRS)
            q_result = self._metaservget.nearest_image_containing(ra, dec, filt)
            data_id = self._data_id_from_qr(q_result)
            metadata = self._metadata_from_data_id(data_id)
            wcs = afwGeom.makeSkyWcs(metadata, strip=False)
            pix_r = int(radius / wcs.getPixelScale().asArcseconds())
            ss = SpanSet.fromShape(pix_r)
            ss_width = ss.getBBox().getWidth()
            src_image = self.cutout_from_nearest(ra, dec, ss_width, ss_width,
                                                 "pixel", filt)
            src_cutout = src_image.getMaskedImage()
            circle_cutout = afwImage.MaskedImageF(src_cutout.getBBox())
            spanset = SpanSet.fromShape(pix_r, Stencil.CIRCLE,
                                      offset=src_cutout.getXY0() +
                                             afwGeom.Extent2I(pix_r, pix_r))
            spanset.copyMaskedImage(src_cutout, circle_cutout)
            # make an Exposure cutout with WCS info
            cutout = afwImage.ExposureF(circle_cutout,
                                            afwImage.ExposureInfo(wcs))
            return cutout
        elif shape == "RANGE":
            if len(pos_items) < 5:
                raise Exception("RANGE: invalid parameters")
            # convert the pair of (ra,dec) to bbox
            ra1, ra2 = float(pos_items[1]), float(pos_items[2])
            dec1, dec2 = float(pos_items[3]), float(pos_items[4])
            box = afwGeom.Box2D(afwGeom.Point2D(ra1, dec1),
                                  afwGeom.Point2D(ra2, dec2))
            # convert from deg to arcsec
            w = box.getWidth()*3600
            h = box.getHeight()*3600
            # compute the arithmetic center (ra, dec) of the range
            ra = (ra1 + ra2) / 2
            dec = (dec1 + dec2) / 2
            cutout = self.cutout_from_nearest(ra, dec, w, h, "arcsec", filt)
            return cutout
        elif shape == "POLYGON":
            if len(pos_items) < 7:
                raise Exception("POLYGON: invalid parameters")
            vertices = []
            pos_items.pop(0)
            for long, lat in zip(pos_items[::2], pos_items[1::2]):
                pt = afwGeom.Point2D(float(long), float(lat))
                vertices.append(pt)
            polygon = afwGeom.Polygon(vertices)
            center = polygon.calculateCenter()
            ra, dec = center.getX(), center.getY()
            # afw limitation: can only return the bbox of the polygon
            bbox = polygon.getBBox()
            # convert from 'deg' to 'arcsec'
            w = bbox.getWidth()*3600
            h = bbox.getHeight()*3600
            cutout = self.cutout_from_nearest(ra, dec, w, h, "arcsec", filt)
            return cutout
Exemplo n.º 7
0
def approximateWcs(wcs,
                   camera_wrapper=None,
                   detector_name=None,
                   obs_metadata=None,
                   order=3,
                   nx=20,
                   ny=20,
                   iterations=3,
                   skyTolerance=0.001 * afwGeom.arcseconds,
                   pixelTolerance=0.02):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box.

    @param[in] wcs  wcs to approximate
    @param[in] camera_wrapper is an instantiation of GalSimCameraWrapper
    @param[in] detector_name is the name of the detector
    @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
    @param[in] order  order of SIP fit
    @param[in] nx  number of grid points along x
    @param[in] ny  number of grid points along y
    @param[in] iterations number of times to iterate over fitting
    @param[in] skyTolerance maximum allowed difference in world coordinates between
               input wcs and approximate wcs (default is 0.001 arcsec)
    @param[in] pixelTolerance maximum allowed difference in pixel coordinates between
               input wcs and approximate wcs (default is 0.02 pixels)
    @return the fit TAN-SIP WCS
    """
    tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema)  # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    # 20 March 2017
    # the 'try' block is how it works in swig;
    # the 'except' block is how it works in pybind11
    try:
        matchList = afwTable.ReferenceMatchVector()
    except AttributeError:
        matchList = []

    bbox = camera_wrapper.getBBox(detector_name)
    bboxd = afwGeom.Box2D(bbox)

    for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = afwGeom.Point2D(x, y)

            ra, dec = camera_wrapper.raDecFromPixelCoords(
                np.array([x]),
                np.array([y]),
                detector_name,
                obs_metadata=obs_metadata,
                epoch=2000.0,
                includeDistortion=True)

            skyCoord = afwCoord.Coord(afwGeom.Point2D(ra[0], dec[0]))

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations):
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    return fitWcs
Exemplo n.º 8
0
def _compareWcsOverBBox(wcs0,
                        wcs1,
                        bbox,
                        maxDiffSky=0.01 * afwGeom.arcseconds,
                        maxDiffPix=0.01,
                        nx=5,
                        ny=5,
                        doShortCircuit=True):
    """!Compare two WCS over a rectangular grid of pixel positions

    @param[in] wcs0  WCS 0 (an lsst.afw.image.Wcs)
    @param[in] wcs1  WCS 1 (an lsst.afw.image.Wcs)
    @param[in] bbox  boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
    @param[in] maxDiffSky  maximum separation between sky positions computed using Wcs.pixelToSky
        (an lsst.afw.geom.Angle)
    @param[in] maxDiffPix  maximum separation between pixel positions computed using Wcs.skyToPixel
    @param[in] nx  number of points in x for the grid of pixel positions
    @param[in] ny  number of points in y for the grid of pixel positions
    @param[in] doShortCircuit  if True then stop at the first error, else test all values in the grid
        and return information about the worst violations found

    @return return an empty string if the WCS are sufficiently close; else return a string describing
    the largest error measured in pixel coordinates (if sky to pixel error was excessive) and sky coordinates
    (if pixel to sky error was excessive). If doShortCircuit is true then the reported error is likely to be
    much less than the maximum error across the whole pixel grid.
    """
    if nx < 1 or ny < 1:
        raise RuntimeError("nx = %s and ny = %s must both be positive" %
                           (nx, ny))
    if maxDiffSky <= 0 * afwGeom.arcseconds:
        raise RuntimeError("maxDiffSky = %s must be positive" % (maxDiffSky, ))
    if maxDiffPix <= 0:
        raise RuntimeError("maxDiffPix = %s must be positive" % (maxDiffPix, ))

    bboxd = afwGeom.Box2D(bbox)
    xList = numpy.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx)
    yList = numpy.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny)
    # we don't care about measured error unless it is too large, so initialize to max allowed
    measDiffSky = (maxDiffSky, "?")  # (sky diff, pix pos)
    measDiffPix = (maxDiffPix, "?")  # (pix diff, sky pos)
    for x, y in itertools.product(xList, yList):
        fromPixPos = afwGeom.Point2D(x, y)
        sky0 = wcs0.pixelToSky(fromPixPos)
        sky1 = wcs1.pixelToSky(fromPixPos)
        diffSky = sky0.angularSeparation(sky1)
        if diffSky > measDiffSky[0]:
            measDiffSky = (diffSky, fromPixPos)
            if doShortCircuit:
                break

        toPixPos0 = wcs0.skyToPixel(sky0)
        toPixPos1 = wcs1.skyToPixel(sky0)
        diffPix = math.hypot(*(toPixPos0 - toPixPos1))
        if diffPix > measDiffPix[0]:
            measDiffPix = (diffPix, sky0)
            if doShortCircuit:
                break

    msgList = []
    if measDiffSky[0] > maxDiffSky:
        msgList.append(
            "%s arcsec max measured sky error > %s arcsec max allowed sky error at pix pos=%s"
            % (measDiffSky[0].asArcseconds(), maxDiffSky.asArcseconds(),
               measDiffSky[1]))
    if measDiffPix[0] > maxDiffPix:
        msgList.append(
            "%s max measured pix error > %s max allowed pix error at sky pos=%s"
            % (measDiffPix[0], maxDiffPix, measDiffPix[1]))

    return "; ".join(msgList)
Exemplo n.º 9
0
 def makePolygon(wcs, bbox):
     """Return a polygon for the image, given Wcs and bounding box"""
     boxPixelCorners = afwGeom.Box2D(bbox).getCorners()
     boxSkyCorners = wcs.pixelToSky(boxPixelCorners)
     return lsst.sphgeom.ConvexPolygonconvexHull([coord.getVector() for coord in boxSkyCorners])
def _findDetectorsListLSST(cameraPointList, detectorList, allow_multiple_chips=False):
    """!Find the detectors that cover a list of points specified by x and y coordinates in any system

    This is based one afw.camerGeom.camera.findDetectorsList.  It has been optimized for the LSST
    camera in the following way:

        - it accepts a limited list of detectors to check in advance (this list should be
          constructed by comparing the pupil coordinates in question and comparing to the
          pupil coordinates of the center of each detector)

       - it will stop looping through detectors one it has found one that is correct (the LSST
         camera does not allow an object to fall on more than one detector)

    @param[in] cameraPointList  a list of cameraPoints in PUPIL coordinates

    @param[in] detecorList is a list of lists.  Each row contains the detectors that should be searched
    for the correspdonding cameraPoint

    @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not
    this method will allow objects to be visible on more than one chip.  If it is 'False'
    and an object appears on more than one chip, only the first chip will appear in the list of
    chipNames but NO WARNING WILL BE EMITTED.  If it is 'True' and an object falls on more than one
    chip, a list of chipNames will appear for that object.

    @return outputNameList is a numpy array of the names of the detectors
    """

    # transform the points to the native coordinate system
    nativePointList = lsst_camera()._transformSingleSysArray(cameraPointList, PUPIL,
                                                             lsst_camera()._nativeCameraSys)

    # initialize output and some caching lists
    outputNameList = [None]*len(cameraPointList)
    chip_has_found = np.array([-1]*len(cameraPointList))
    checked_detectors = []

    # Figure out if any of these (RA, Dec) pairs could be
    # on more than one chip.  This is possible on the
    # wavefront sensors, since adjoining wavefront sensors
    # are kept one in focus, one out of focus.
    # See figure 2 of arXiv:1506.04839v2
    # (This might actually be a bug in obs_lsstSim
    # I opened DM-8075 on 25 October 2016 to investigate)
    could_be_multiple = [False]*len(cameraPointList)
    if allow_multiple_chips:
        for ipt in range(len(cameraPointList)):
            for det in detectorList[ipt]:
                if det.getType() == WAVEFRONT:
                    could_be_multiple[ipt] = True

    # loop over (RA, Dec) pairs
    for ipt, nativePoint in enumerate(nativePointList):
        if chip_has_found[ipt] < 0:  # i.e. if we have not yet found this (RA, Dec) pair
            for detector in detectorList[ipt]:

                # check that we have not already considered this detector
                if detector.getName() not in checked_detectors:
                    checked_detectors.append(detector.getName())

                    # in order to avoid constantly re-instantiating the same afwCameraGeom detector,
                    # we will now find all of the (RA, Dec) pairs that could be on the present
                    # chip and test them.
                    unfound_pts = np.where(chip_has_found < 0)[0]
                    if len(unfound_pts) == 0:
                        # we have already found all of the (RA, Dec) pairs
                        for ix, name in enumerate(outputNameList):
                            if isinstance(name, list):
                                outputNameList[ix] = str(name)
                        return np.array(outputNameList)

                    valid_pt_dexes = np.array([ii for ii in unfound_pts if detector in detectorList[ii]])
                    if len(valid_pt_dexes) > 0:
                        valid_pt_list = [nativePointList[ii] for ii in valid_pt_dexes]
                        coordMap = detector.getTransformMap()
                        cameraSys = detector.makeCameraSys(PIXELS)
                        detectorPointList = coordMap.transform(valid_pt_list, lsst_camera()._nativeCameraSys,
                                                               cameraSys)
                        box = afwGeom.Box2D(detector.getBBox())
                        for ix, pt in zip(valid_pt_dexes, detectorPointList):
                            if box.contains(pt):
                                if not could_be_multiple[ix]:
                                    # because this (RA, Dec) pair is not marked as could_be_multiple,
                                    # the fact that this (RA, Dec) pair is on the current chip
                                    # means this (RA, Dec) pair no longer needs to be considered.
                                    # You can set chip_has_found[ix] to unity.
                                    outputNameList[ix] = detector.getName()
                                    chip_has_found[ix] = 1
                                else:
                                    # Since this (RA, Dec) pair has been makred could_be_multiple,
                                    # finding this (RA, Dec) pair on the chip does not remove the
                                    # (RA, Dec) pair from contention.
                                    if outputNameList[ix] is None:
                                        outputNameList[ix] = detector.getName()
                                    elif isinstance(outputNameList[ix], list):
                                        outputNameList[ix].append(detector.getName())
                                    else:
                                        outputNameList[ix] = [outputNameList[ix], detector.getName()]

    # convert entries corresponding to multiple chips into strings
    # (i.e. [R:2,2 S:0,0, R:2,2 S:0,1] becomes `[R:2,2 S:0,0, R:2,2 S:0,1]`)
    for ix, name in enumerate(outputNameList):
        if isinstance(name, list):
            outputNameList[ix] = str(name)

    return np.array(outputNameList)
Exemplo n.º 11
0
        #Need RMS from fit: 2895 will replace this:
        rms = 0.0
        X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
        x0, y0 = diffMI.getXY0()
        modelValueArr = numpy.empty(len(Z))
        for i in range(len(X)):
            modelValueArr[i] = bkgdImage.get(int(X[i] - x0), int(Y[i] - y0))
        resids = Z - modelValueArr
        rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))

        if lsstDebug.Info(__name__).savefits:
            sciExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits')

        if lsstDebug.Info(__name__).savefig:
            bbox = afwGeom.Box2D(refExposure.getMaskedImage().getBBox())
            try:
                self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr,
                                resids)
            except Exception, e:
                self.log.warn('Debug plot not generated: %s' % (e))

        meanVar = afwMath.makeStatistics(diffMI.getVariance(),
                                         diffMI.getMask(), afwMath.MEANCLIP,
                                         self.sctrl).getValue()

        diffIm = diffMI.getImage()
        diffIm -= bkgdImage  #diffMI should now have a mean ~ 0
        del diffIm
        mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE,
                                     self.sctrl).getValue()
Exemplo n.º 12
0
    def testCountInputs(self):
        num = 3  # Number of images
        size = 10  # Size of images (pixels)
        shift = 4  # Shift to apply between images (pixels)
        value = 100.0  # Value to give objects
        offset = 12345  # x0,y0

        cdMatrix = (1.0e-5, 0.0, 0.0, 1.0e-5)
        crval = afwCoord.Coord(0.0 * afwGeom.degrees, 0.0 * afwGeom.degrees)

        positions = [
            afwGeom.Point2D(size // 2 + shift * (i - num // 2) + offset,
                            size // 2 + shift * (i - num // 2) + offset)
            for i in range(num)
        ]
        wcsList = [
            afwImage.makeWcs(crval, pos - afwGeom.Extent2D(offset, offset),
                             *cdMatrix) for pos in positions
        ]
        imageBox = afwGeom.Box2I(afwGeom.Point2I(0, 0),
                                 afwGeom.Extent2I(size, size))
        wcsRef = afwImage.makeWcs(crval, afwGeom.Point2D(offset, offset),
                                  *cdMatrix)

        exp = afwImage.ExposureF(size, size)
        exp.setXY0(afwGeom.Point2I(offset, offset))
        exp.setWcs(wcsRef)
        exp.setPsf(measAlg.DoubleGaussianPsf(5, 5, 1.0))
        exp.getInfo().setCoaddInputs(
            afwImage.CoaddInputs(afwTable.ExposureTable.makeMinimalSchema(),
                                 afwTable.ExposureTable.makeMinimalSchema()))
        ccds = exp.getInfo().getCoaddInputs().ccds
        for wcs in wcsList:
            record = ccds.addNew()
            record.setWcs(wcs)
            record.setBBox(imageBox)
            record.setValidPolygon(Polygon(afwGeom.Box2D(imageBox)))

        exp.getMaskedImage().getImage().set(0)
        exp.getMaskedImage().getMask().set(0)
        exp.getMaskedImage().getVariance().set(1.0)
        for pp in positions:
            x, y = map(int, pp)
            exp.getMaskedImage().getImage().set0(x, y, value)
            exp.getMaskedImage().getMask().set(x - offset, y - offset, value)

        measureSourcesConfig = measAlg.SourceMeasurementConfig()
        measureSourcesConfig.algorithms.names = [
            "centroid.naive", "countInputs"
        ]
        measureSourcesConfig.slots.centroid = "centroid.naive"
        measureSourcesConfig.slots.psfFlux = None
        measureSourcesConfig.slots.apFlux = None
        measureSourcesConfig.slots.modelFlux = None
        measureSourcesConfig.slots.instFlux = None
        measureSourcesConfig.slots.calibFlux = None
        measureSourcesConfig.slots.shape = None
        measureSourcesConfig.validate()
        schema = afwTable.SourceTable.makeMinimalSchema()
        ms = measureSourcesConfig.makeMeasureSources(schema)
        catalog = afwTable.SourceCatalog(schema)
        measureSourcesConfig.slots.setupTable(catalog.getTable())

        for pp in positions:
            foot = afwDetection.Footprint(afwGeom.Point2I(pp), 1.0)
            peak = foot.getPeaks().addNew()
            peak.setIx(int(pp[0]))
            peak.setIy(int(pp[1]))
            peak.setFx(pp[0])
            peak.setFy(pp[1])
            peak.setPeakValue(value)

            source = catalog.addNew()
            source.setFootprint(foot)

            ms.applyWithPeak(source, exp)

            number = sum(
                afwGeom.Box2D(imageBox).contains(
                    wcs.skyToPixel(wcsRef.pixelToSky(pp))) for wcs in wcsList)
            self.assertEqual(source.get("countInputs"), number)
Exemplo n.º 13
0
 def testMutators(self):
     box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True)
     box.grow(1)
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(3, 2), True))
     box.grow(geom.Extent2D(2, 3))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-5, -7), geom.Point2D(5, 5), True))
     box.shift(geom.Extent2D(3, 2))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-2, -5), geom.Point2D(8, 7), True))
     box.include(geom.Point2D(-4, 2))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-4, -5), geom.Point2D(8, 7), True))
     self.assertTrue(box.contains(geom.Point2D(-4, 2)))
     box.include(geom.Point2D(0, -6))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-4, -6), geom.Point2D(8, 7), True))
     box.include(geom.Box2D(geom.Point2D(0, 0), geom.Point2D(10, 11), True))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-4, -6), geom.Point2D(10, 11), True))
     box.clip(geom.Box2D(geom.Point2D(0, 0), geom.Point2D(11, 12), True))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(0, 0), geom.Point2D(10, 11), True))
     box.clip(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(5, 4), True))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(0, 0), geom.Point2D(5, 4), True))
Exemplo n.º 14
0
 def testRelations(self):
     box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True)
     self.assertTrue(box.contains(geom.Point2D(0, 0)))
     self.assertTrue(box.contains(geom.Point2D(-2, -3)))
     self.assertFalse(box.contains(geom.Point2D(2, -3)))
     self.assertFalse(box.contains(geom.Point2D(2, 1)))
     self.assertFalse(box.contains(geom.Point2D(-2, 1)))
     self.assertTrue(
         box.contains(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(1, 0))))
     self.assertTrue(box.contains(box))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 2))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(3, 1))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-3, -3), geom.Point2D(2, 1))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(2, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 2))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(3, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-3, -3), geom.Point2D(2, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(2, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(1, 0))))
     self.assertTrue(box.overlaps(box))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-5, -3), geom.Point2D(-3,
                                                                    1))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -6), geom.Point2D(2,
                                                                    -4))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(3, -3), geom.Point2D(4, 1))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-2, 2), geom.Point2D(2, 2))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -5), geom.Point2D(2,
                                                                    -3))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-4, -3), geom.Point2D(-2,
                                                                    1))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-2, 1), geom.Point2D(2, 3))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(2, -3), geom.Point2D(4, 1))))
    def run(self, butler, dataRefList):
        """!Make a skymap from the bounds of the given set of calexps.

        @param[in]  butler        data butler used to save the SkyMap
        @param[in]  dataRefList   dataRefs of calexps used to determine the size and pointing of the SkyMap
        @return     a pipeBase Struct containing:
                    - skyMap: the constructed SkyMap
        """
        self.log.info("Extracting bounding boxes of %d images" %
                      len(dataRefList))
        points = []
        for dataRef in dataRefList:
            if not dataRef.datasetExists("calexp"):
                self.log.warn("CalExp for %s does not exist: ignoring" %
                              (dataRef.dataId, ))
                continue
            md = dataRef.get("calexp_md", immediate=True)
            wcs = afwImage.makeWcs(md)
            # nb: don't need to worry about xy0 because Exposure saves Wcs with CRPIX shifted by (-x0, -y0).
            boxI = afwGeom.Box2I(
                afwGeom.Point2I(0, 0),
                afwGeom.Extent2I(md.get("NAXIS1"), md.get("NAXIS2")))
            boxD = afwGeom.Box2D(boxI)
            points.extend(
                tuple(wcs.pixelToSky(corner).getVector())
                for corner in boxD.getCorners())
        if len(points) == 0:
            raise RuntimeError(
                "No data found from which to compute convex hull")
        self.log.info("Computing spherical convex hull")
        polygon = lsst.geom.convexHull(points)
        if polygon is None:
            raise RuntimeError(
                "Failed to compute convex hull of the vertices of all calexp bounding boxes; "
                "they may not be hemispherical.")
        circle = polygon.getBoundingCircle()

        datasetName = self.config.coaddName + "Coadd_skyMap"

        skyMapConfig = DiscreteSkyMap.ConfigClass()
        if self.config.doAppend and butler.datasetExists(datasetName):
            oldSkyMap = butler.get(datasetName, immediate=True)
            if not isinstance(oldSkyMap.config, DiscreteSkyMap.ConfigClass):
                raise TypeError(
                    "Cannot append to existing non-discrete skymap")
            compareLog = []
            if not self.config.skyMap.compare(oldSkyMap.config,
                                              output=compareLog.append):
                raise ValueError(
                    "Cannot append to existing skymap - configurations differ:",
                    *compareLog)
            skyMapConfig.raList.extend(oldSkyMap.config.raList)
            skyMapConfig.decList.extend(oldSkyMap.config.decList)
            skyMapConfig.radiusList.extend(oldSkyMap.config.radiusList)
        skyMapConfig.update(**self.config.skyMap.toDict())
        skyMapConfig.raList.append(circle.center[0])
        skyMapConfig.decList.append(circle.center[1])
        skyMapConfig.radiusList.append(circle.radius + self.config.borderSize)
        skyMap = DiscreteSkyMap(skyMapConfig)

        for tractInfo in skyMap:
            wcs = tractInfo.getWcs()
            posBox = afwGeom.Box2D(tractInfo.getBBox())
            pixelPosList = (
                posBox.getMin(),
                afwGeom.Point2D(posBox.getMaxX(), posBox.getMinY()),
                posBox.getMax(),
                afwGeom.Point2D(posBox.getMinX(), posBox.getMaxY()),
            )
            skyPosList = [
                wcs.pixelToSky(pos).getPosition(afwGeom.degrees)
                for pos in pixelPosList
            ]
            posStrList = [
                "(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList
            ]
            self.log.info("tract %s has corners %s (RA, Dec deg) and %s x %s patches" % \
                (tractInfo.getId(), ", ".join(posStrList), \
                tractInfo.getNumPatches()[0], tractInfo.getNumPatches()[1]))
        if self.config.doWrite:
            butler.put(skyMap, datasetName)
        return pipeBase.Struct(skyMap=skyMap)
Exemplo n.º 16
0
    def testChebyshev1Function2D(self):
        errMsg = ("{}: {} != {} for x={}, xMin={}, xMax={}, xNorm={}, "
                  "yMin={}, yMax={}, yNorm={}, params={}; {}")
        maxOrder = 6
        deltaParam = 0.3
        ranges = ((-1, 1), (-1, 0), (0, 1), (-17, -2), (-65.3, 2.132))
        xRangeIter = itertools.cycle(ranges)
        yRangeIter = itertools.cycle(ranges)
        next(yRangeIter)  # make x and y ranges off from each other
        nPoints = 7  # number of points in x and y at which to test the functions

        for order in range(maxOrder + 1):
            xMin, xMax = next(xRangeIter)
            xMean = (xMin + xMax) / 2.0
            xDelta = (xMax - xMin) / float(nPoints - 1)

            yMin, yMax = next(yRangeIter)
            yMean = (yMin + yMax) / 2.0
            yDelta = (yMax - yMin) / float(nPoints - 1)

            xyRange = afwGeom.Box2D(afwGeom.Point2D(xMin, yMin),
                                    afwGeom.Point2D(xMax, yMax))

            f = afwMath.Chebyshev1Function2D(order, xyRange)
            numParams = f.getNParameters()
            params = nrange(numParams, deltaParam, deltaParam)
            f.setParameters(params)
            g = afwMath.Chebyshev1Function2D(params, xyRange)
            h = f.clone()

            self.assertEqual(f.getNParameters(), g.getNParameters())
            self.assertEqual(f.getNParameters(), h.getNParameters())

            self.assertEqual(f.getXYRange(), xyRange)
            self.assertEqual(f.getOrder(), order)

            self.assertEqual(g.getXYRange(), xyRange)
            self.assertEqual(g.getOrder(), order)

            # vary x in the inner loop to exercise the caching
            minYNorm = None
            maxYNorm = None
            for y in np.arange(yMin, yMax + yDelta / 2.0, yDelta):
                yNorm = 2.0 * (y - yMean) / float(yMax - yMin)
                if minYNorm is None or yNorm < minYNorm:
                    minYNorm = yNorm
                if maxYNorm is None or yNorm > maxYNorm:
                    maxYNorm = yNorm

                minXNorm = None
                maxXNorm = None
                for x in np.arange(xMin, xMax + xDelta / 2.0, xDelta):
                    xNorm = 2.0 * (x - xMean) / float(xMax - xMin)
                    if minXNorm is None or xNorm < minXNorm:
                        minXNorm = xNorm
                    if maxXNorm is None or xNorm > maxXNorm:
                        maxXNorm = xNorm

                        predVal = referenceChebyshev1Polynomial2(
                            xNorm, yNorm, params)

                        msg = errMsg.format(
                            type(f).__name__, f(x, y), predVal, x, xMin, xMax,
                            xNorm, yMin, yMax, yNorm, params,
                            "order constructor")
                        self.assertFloatsAlmostEqual(f(x, y),
                                                     predVal,
                                                     msg=msg,
                                                     atol=self.atol,
                                                     rtol=None)
                        msg = errMsg.format(
                            type(g).__name__, g(x, y), predVal, x, xMin, xMax,
                            xNorm, yMin, yMax, yNorm, params,
                            "params constructor")
                        self.assertFloatsAlmostEqual(g(x, y),
                                                     predVal,
                                                     msg=msg,
                                                     atol=self.atol,
                                                     rtol=None)
                        msg = errMsg.format(
                            type(h).__name__, h(x, y), predVal, x, xMin, xMax,
                            xNorm, yMin, yMax, yNorm, params, "order")
                        self.assertFloatsAlmostEqual(h(x, y),
                                                     predVal,
                                                     msg=msg,
                                                     atol=self.atol,
                                                     rtol=None)

                msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm)
                self.assertFloatsAlmostEqual(minXNorm,
                                             -1.,
                                             msg=msg,
                                             atol=self.atol)
                self.assertFloatsAlmostEqual(maxXNorm,
                                             1.,
                                             msg=msg,
                                             atol=self.atol)

            msg = self.normErr.format("y", yMin, yMax, minYNorm, maxYNorm)
            self.assertFloatsAlmostEqual(minYNorm,
                                         -1.,
                                         msg=msg,
                                         atol=self.atol)
            self.assertFloatsAlmostEqual(maxYNorm, 1., msg=msg, atol=self.atol)

        # test that the number of parameters is correct for the given order
        def numParamsFromOrder(order):
            return (order + 1) * (order + 2) // 2

        MaxOrder = 13
        for order in range(MaxOrder + 1):
            f = afwMath.Chebyshev1Function2D(order)
            predNParams = numParamsFromOrder(order)
            self.assertEqual(f.getNParameters(), predNParams)
            afwMath.Chebyshev1Function2D(np.zeros(predNParams, dtype=float))

        # test that the wrong number of parameters raises an exception
        validNumParams = set()
        for order in range(MaxOrder + 1):
            validNumParams.add(numParamsFromOrder(order))
        for numParams in range(numParamsFromOrder(MaxOrder)):
            if numParams in validNumParams:
                continue
            with self.assertRaises(pexExceptions.InvalidParameterError):
                afwMath.Chebyshev1Function2D(np.zeros(numParams, dtype=float))

        # test that changing parameters clears the cache
        # for simplicity use the xyRange that requires no normalization
        order = 3
        numParams = numParamsFromOrder(order)
        f = afwMath.Chebyshev1Function2D(order)
        xyRange = afwGeom.Box2D(afwGeom.Point2D(-1.0, -1.0),
                                afwGeom.Point2D(1.0, 1.0))
        x = 0.5
        y = -0.24
        for addValue in (0.0, 0.2):
            params = nrange(numParams, deltaParam + addValue, deltaParam)
            f.setParameters(params)
            predVal = referenceChebyshev1Polynomial2(x, y, params)
            msg = "%s != %s for x=%s, y=%s, params=%s" % (f(
                x, y), predVal, x, y, params)
            self.assertFloatsAlmostEqual(f(x, y),
                                         predVal,
                                         msg=msg,
                                         atol=self.atol,
                                         rtol=None)
Exemplo n.º 17
0
    def matchBackgrounds(self, refExposure, sciExposure):
        """
        Match science exposure's background level to that of reference exposure.

        Process creates a difference image of the reference exposure minus the science exposure, and then
        generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
        already has detections set. If detections have not been set/masked, sources will bias the
        background estimation.
        The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
        or by polynomial interpolation by the Approximate class. This model of difference image
        is added to the science exposure in memory.
        Fit diagnostics are also calculated and returned.

        @param[in] refExposure: reference exposure
        @param[in,out] sciExposure: science exposure; modified by changing the background level
            to match that of the reference exposure
        @returns a pipBase.Struct with fields:
            - backgroundModel: an afw.math.Approximate or an afw.math.Background.
            - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
            - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
              should be comparable to difference image's mean variance.
            - diffImVar: the mean variance of the difference image.
        """

        if lsstDebug.Info(__name__).savefits:
            refExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'refExposure.fits')
            sciExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'sciExposure.fits')

        # Check Configs for polynomials:
        if self.config.usePolynomial:
            x, y = sciExposure.getDimensions()
            shortSideLength = min(x, y)
            if shortSideLength < self.config.binSize:
                raise ValueError(
                    "%d = config.binSize > shorter dimension = %d" %
                    (self.config.binSize, shortSideLength))
            npoints = shortSideLength // self.config.binSize
            if shortSideLength % self.config.binSize != 0:
                npoints += 1

            if self.config.order > npoints - 1:
                raise ValueError("%d = config.order > npoints - 1 = %d" %
                                 (self.config.order, npoints - 1))

        # Check that exposures are same shape
        if (sciExposure.getDimensions() != refExposure.getDimensions()):
            wSci, hSci = sciExposure.getDimensions()
            wRef, hRef = refExposure.getDimensions()
            raise RuntimeError(
                "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)"
                % (wSci, hSci, wRef, hRef))

        statsFlag = getattr(afwMath, self.config.gridStatistic)
        self.sctrl.setNumSigmaClip(self.config.numSigmaClip)
        self.sctrl.setNumIter(self.config.numIter)

        im = refExposure.getMaskedImage()
        diffMI = im.Factory(im, True)
        diffMI -= sciExposure.getMaskedImage()

        width = diffMI.getWidth()
        height = diffMI.getHeight()
        nx = width // self.config.binSize
        if width % self.config.binSize != 0:
            nx += 1
        ny = height // self.config.binSize
        if height % self.config.binSize != 0:
            ny += 1

        bctrl = afwMath.BackgroundControl(nx, ny, self.sctrl, statsFlag)
        bctrl.setUndersampleStyle(self.config.undersampleStyle)
        bctrl.setInterpStyle(self.config.interpStyle)

        bkgd = afwMath.makeBackground(diffMI, bctrl)

        # Some config and input checks if config.usePolynomial:
        # 1) Check that order/bin size make sense:
        # 2) Change binsize or order if underconstrained.
        if self.config.usePolynomial:
            order = self.config.order
            bgX, bgY, bgZ, bgdZ = self._gridImage(diffMI, self.config.binSize,
                                                  statsFlag)
            minNumberGridPoints = min(len(set(bgX)), len(set(bgY)))
            if len(bgZ) == 0:
                raise ValueError("No overlap with reference. Nothing to match")
            elif minNumberGridPoints <= self.config.order:
                # must either lower order or raise number of bins or throw exception
                if self.config.undersampleStyle == "THROW_EXCEPTION":
                    raise ValueError(
                        "Image does not cover enough of ref image for order and binsize"
                    )
                elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
                    self.log.warn("Reducing order to %d" %
                                  (minNumberGridPoints - 1))
                    order = minNumberGridPoints - 1
                elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
                    newBinSize = (minNumberGridPoints * self.config.binSize
                                  ) // (self.config.order + 1)
                    bctrl.setNxSample(newBinSize)
                    bctrl.setNySample(newBinSize)
                    bkgd = afwMath.makeBackground(diffMI, bctrl)  # do over
                    self.log.warn("Decreasing binsize to %d" % (newBinSize))

            # If there is no variance in any image pixels, do not weight bins by inverse variance
            isUniformImageDiff = not numpy.any(
                bgdZ > self.config.gridStdevEpsilon)
            weightByInverseVariance = False if isUniformImageDiff else self.config.approxWeighting

        # Add offset to sciExposure
        try:
            if self.config.usePolynomial:
                actrl = afwMath.ApproximateControl(
                    afwMath.ApproximateControl.CHEBYSHEV, order, order,
                    weightByInverseVariance)
                undersampleStyle = getattr(afwMath,
                                           self.config.undersampleStyle)
                approx = bkgd.getApproximate(actrl, undersampleStyle)
                bkgdImage = approx.getImage()
            else:
                bkgdImage = bkgd.getImageF()
        except Exception as e:
            raise RuntimeError(
                "Background/Approximation failed to interp image %s: %s" %
                (self.debugDataIdString, e))

        sciMI = sciExposure.getMaskedImage()
        sciMI += bkgdImage
        del sciMI

        # Need RMS from fit: 2895 will replace this:
        rms = 0.0
        X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
        x0, y0 = diffMI.getXY0()
        modelValueArr = numpy.empty(len(Z))
        for i in range(len(X)):
            modelValueArr[i] = bkgdImage.get(int(X[i] - x0), int(Y[i] - y0))
        resids = Z - modelValueArr
        rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))

        if lsstDebug.Info(__name__).savefits:
            sciExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits')

        if lsstDebug.Info(__name__).savefig:
            bbox = afwGeom.Box2D(refExposure.getMaskedImage().getBBox())
            try:
                self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr,
                                resids)
            except Exception as e:
                self.log.warn('Debug plot not generated: %s' % (e))

        meanVar = afwMath.makeStatistics(diffMI.getVariance(),
                                         diffMI.getMask(), afwMath.MEANCLIP,
                                         self.sctrl).getValue()

        diffIm = diffMI.getImage()
        diffIm -= bkgdImage  # diffMI should now have a mean ~ 0
        del diffIm
        mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE,
                                     self.sctrl).getValue()

        outBkgd = approx if self.config.usePolynomial else bkgd

        return pipeBase.Struct(backgroundModel=outBkgd,
                               fitRMS=rms,
                               matchedMSE=mse,
                               diffImVar=meanVar)
Exemplo n.º 18
0
    def testChebyshev1Function2DTruncate(self):
        errMsg = (
            "{} != {} = {} for x={}, xMin={}, xMax={}, xNorm={},"
            " yMin={}, yMax={}, yNorm={}, truncParams={}; order constructor")

        maxOrder = 6
        deltaParam = 0.3
        ranges = ((-1, 1), (-17, -2), (-65.3, 2.132))
        xRangeIter = itertools.cycle(ranges)
        yRangeIter = itertools.cycle(ranges)
        next(yRangeIter)  # make x and y ranges off from each other
        nPoints = 7  # number of points in x and y at which to test the functions

        for order in range(maxOrder + 1):
            xMin, xMax = next(xRangeIter)
            xMean = (xMin + xMax) / 2.0
            xDelta = (xMax - xMin) / float(nPoints - 1)

            yMin, yMax = next(yRangeIter)
            yMean = (yMin + yMax) / 2.0
            yDelta = (yMax - yMin) / float(nPoints - 1)

            xyRange = afwGeom.Box2D(afwGeom.Point2D(xMin, yMin),
                                    afwGeom.Point2D(xMax, yMax))

            fullNParams = afwMath.Chebyshev1Function2D.nParametersFromOrder(
                order)
            fullParams = nrange(fullNParams, deltaParam, deltaParam)
            fullPoly = afwMath.Chebyshev1Function2D(fullParams, xyRange)

            for tooBigTruncOrder in range(order + 1, order + 3):
                with self.assertRaises(pexExceptions.InvalidParameterError):
                    fullPoly.truncate(tooBigTruncOrder)

            for truncOrder in range(order + 1):
                truncNParams = fullPoly.nParametersFromOrder(truncOrder)
                truncParams = fullParams[0:truncNParams]

                f = fullPoly.truncate(truncOrder)
                self.assertEqual(f.getNParameters(), truncNParams)

                g = afwMath.Chebyshev1Function2D(fullParams[0:truncNParams],
                                                 xyRange)

                self.assertEqual(f.getNParameters(), g.getNParameters())

                self.assertEqual(f.getOrder(), truncOrder)
                self.assertEqual(f.getXYRange(), xyRange)

                self.assertEqual(g.getOrder(), truncOrder)
                self.assertEqual(g.getXYRange(), xyRange)

                minXNorm = None
                maxXNorm = None
                for x in np.arange(xMin, xMax + xDelta / 2.0, xDelta):
                    xNorm = 2.0 * (x - xMean) / float(xMax - xMin)
                    if minXNorm is None or xNorm < minXNorm:
                        minXNorm = xNorm
                    if maxXNorm is None or xNorm > maxXNorm:
                        maxXNorm = xNorm

                    minYNorm = None
                    maxYNorm = None
                    for y in np.arange(yMin, yMax + yDelta / 2.0, yDelta):
                        yNorm = 2.0 * (y - yMean) / float(yMax - yMin)
                        if minYNorm is None or yNorm < minYNorm:
                            minYNorm = yNorm
                        if maxYNorm is None or yNorm > maxYNorm:
                            maxYNorm = yNorm

                            msg = errMsg.format(
                                type(f).__name__, f(x, y), g(x, y),
                                type(g).__name__, x, xMin, xMax, xNorm, yMin,
                                yMax, yNorm, truncParams)
                            self.assertFloatsAlmostEqual(f(x, y),
                                                         g(x, y),
                                                         msg=msg)

                    msg = self.normErr.format("y", yMin, yMax, minYNorm,
                                              maxYNorm)
                    self.assertFloatsAlmostEqual(minYNorm,
                                                 -1.0,
                                                 msg=msg,
                                                 atol=self.atol,
                                                 rtol=None)
                    self.assertFloatsAlmostEqual(maxYNorm,
                                                 1.0,
                                                 msg=msg,
                                                 atol=self.atol,
                                                 rtol=None)

                msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm)
                self.assertFloatsAlmostEqual(minXNorm,
                                             -1.0,
                                             msg=msg,
                                             atol=self.atol,
                                             rtol=None)
                self.assertFloatsAlmostEqual(maxXNorm,
                                             1.0,
                                             msg=msg,
                                             atol=self.atol,
                                             rtol=None)
Exemplo n.º 19
0
    def run(self, patchRef):
        """Select images for a region and report how many are in each tract and patch

        Also report quartiles of FWHM

        @param patchRef: data reference for coadd patch.
        @return: a pipeBase.Struct with fields:
        - exposureInfoList: a list of exposure info objects, as returned by the select subtask
        """
        skyMap = patchRef.get(self.config.coaddName + "Coadd_skyMap")

        tractId = patchRef.dataId["tract"]
        patchIndex = [int(val) for val in patchRef.dataId["patch"].split(",")]
        tractInfo = skyMap[tractId]
        wcs = tractInfo.getWcs()
        patchInfo = tractInfo.getPatchInfo(patchIndex)
        posBox = afwGeom.Box2D(patchInfo.getOuterBBox())
        coordList = [wcs.pixelToSky(pos) for pos in _getBox2DCorners(posBox)]

        skyPosList = [
            coord.getPosition(afwGeom.degrees) for coord in coordList
        ]
        skyPosStrList = [
            "(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList
        ]
        skyPosStr = ", ".join(skyPosStrList)
        print("PatchId=%s; corner RA/Dec (deg)=%s" %
              (patchRef.dataId, skyPosStr))

        exposureInfoList = self.select.runDataRef(
            dataRef=patchRef,
            coordList=coordList,
            makeDataRefList=False,
        ).exposureInfoList

        print("Found %d suitable exposures" % (len(exposureInfoList), ))
        if len(exposureInfoList) < 1:
            return

        fwhmList = [exposureInfo.fwhm for exposureInfo in exposureInfoList]
        fwhmList = numpy.array(fwhmList, dtype=float)
        print("FWHM Q1=%0.2f Q2=%0.2f Q3=%0.2f" % (
            numpy.percentile(fwhmList, 25.0),
            numpy.percentile(fwhmList, 50.0),
            numpy.percentile(fwhmList, 75.0),
        ))

        print("Image IDs:")
        if len(exposureInfoList) > 0:
            idKeys = sorted(exposureInfoList[0].dataId.keys())
            for exposureInfo in exposureInfoList:
                idStr = " ".join("%s=%s" % (key, exposureInfo.dataId[key])
                                 for key in idKeys)
                skyPosList = [
                    coord.getPosition(afwGeom.degrees)
                    for coord in exposureInfo.coordList
                ]
                skyPosStrList = [
                    "(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList
                ]
                skyPosStr = ", ".join(skyPosStrList)
                print("dataId=%s; corner RA/Dec (deg)=%s" % (idStr, skyPosStr))

        return pipeBase.Struct(exposureInfoList=exposureInfoList)
Exemplo n.º 20
0
    def testInputCounts(self, showPlot=False):
        # Generate a simulated coadd of four overlapping-but-offset CCDs.
        # Populate it with three sources.
        # Demonstrate that we can correctly recover the number of images which
        # contribute to each source.

        size = 20  # Size of images (pixels)
        value = 100.0  # Source flux

        ccdPositions = [
            afwGeom.Point2D(8, 0),
            afwGeom.Point2D(10, 10),
            afwGeom.Point2D(-8, -8),
            afwGeom.Point2D(-8, 8)
        ]

        # Represent sources by a tuple of position and expected number of
        # contributing CCDs (based on the size/positions given above).
        Source = namedtuple("Source", ["pos", "count"])
        sources = [
            Source(pos=afwGeom.Point2D(6, 6), count=2),
            Source(pos=afwGeom.Point2D(10, 10), count=3),
            Source(pos=afwGeom.Point2D(14, 14), count=1)
        ]

        # These lines are used in the creation of WCS information
        cdMatrix = (1.0e-5, 0.0, 0.0, 1.0e-5)
        crval = afwCoord.Coord(0.0 * afwGeom.degrees, 0.0 * afwGeom.degrees)

        # Construct the info needed to set the exposure object
        imageBox = afwGeom.Box2I(afwGeom.Point2I(0, 0),
                                 afwGeom.Extent2I(size, size))
        wcsRef = afwImage.makeWcs(crval, afwGeom.Point2D(0, 0), *cdMatrix)

        # Create the exposure object, and set it up to be the output of a coadd
        exp = afwImage.ExposureF(size, size)
        exp.setWcs(wcsRef)
        exp.getInfo().setCoaddInputs(
            afwImage.CoaddInputs(afwTable.ExposureTable.makeMinimalSchema(),
                                 afwTable.ExposureTable.makeMinimalSchema()))

        # Set the fake CCDs that "went into" making this coadd, using the
        # differing wcs objects created above.
        ccds = exp.getInfo().getCoaddInputs().ccds
        for pos in ccdPositions:
            record = ccds.addNew()
            record.setWcs(afwImage.makeWcs(crval, pos, *cdMatrix))
            record.setBBox(imageBox)
            record.setValidPolygon(Polygon(afwGeom.Box2D(imageBox)))

        # Configure a SingleFrameMeasurementTask to run InputCounts.
        measureSourcesConfig = measBase.SingleFrameMeasurementConfig()
        measureSourcesConfig.plugins.names = [
            "base_PeakCentroid", "base_InputCount"
        ]
        measureSourcesConfig.slots.centroid = "base_PeakCentroid"
        measureSourcesConfig.slots.psfFlux = None
        measureSourcesConfig.slots.apFlux = None
        measureSourcesConfig.slots.modelFlux = None
        measureSourcesConfig.slots.instFlux = None
        measureSourcesConfig.slots.calibFlux = None
        measureSourcesConfig.slots.shape = None
        measureSourcesConfig.validate()
        schema = afwTable.SourceTable.makeMinimalSchema()
        task = measBase.SingleFrameMeasurementTask(schema,
                                                   config=measureSourcesConfig)
        catalog = afwTable.SourceCatalog(schema)

        # Add simulated sources to the measurement catalog.
        for src in sources:
            spans = afwGeom.SpanSet.fromShape(1)
            spans = spans.shiftedBy(int(src.pos.getX()), int(src.pos.getY()))
            foot = afwDetection.Footprint(spans)
            peak = foot.getPeaks().addNew()
            peak.setFx(src.pos[0])
            peak.setFy(src.pos[1])
            peak.setPeakValue(value)
            catalog.addNew().setFootprint(foot)

        task.run(catalog, exp)

        for src, rec in zip(sources, catalog):
            self.assertEqual(rec.get("base_InputCount_value"), src.count)

        if display:
            ccdVennDiagram(exp)
Exemplo n.º 21
0
 def get(self, center_coord, width, height, filt, units):
     """Merge multiple patches from a SkyMap into a single image.
     This function takes advantage of the fact that all tracts in a SkyMap share
     the same WCS and should have identical pixels where they overlap.
     @center_coord: base coordinate of the box RA and Dec (minimum values, lower left corner)
     @width: in arcsec or pixel
     @height: in arcsec or pixel
     @filt: valid filter for the data set such as 'i', 'r', 'g'
     @units: 'pixel' or 'arcsecond' (defaults to 'pixel')
     """
     dest_tract_info = self._skymap.findTract(center_coord)
     dest_wcs = dest_tract_info.getWcs()
     # Determine target area.
     if units != "arcsec" and units != "arcsecond" and units != "pixel":
         units = "pixel"
     dest_bbox = self._bbox_for_coords(dest_wcs, center_coord, width,
                                       height, units)
     dest_corner_coords = [
         dest_wcs.pixelToSky(pixPos)
         for pixPos in afw_geom.Box2D(dest_bbox).getCorners()
     ]
     # Collect patches of the SkyMap that are in the target region.
     # Create source exposures from the patches within each tract
     # as all patches from a tract share a WCS.
     exposure_list = []
     tract_patch_list = self._skymap.findTractPatchList(dest_corner_coords)
     for j, tract_patch in enumerate(tract_patch_list):
         tract_info = tract_patch[0]
         patch_list = tract_patch[1]
         self._log.info("tract_info[{}]={}".format(j, tract_info))
         self._log.info("patch_list[{}]={}".format(j, patch_list))
         src_wcs = tract_info.getWcs()
         src_bbox = afw_geom.Box2I()
         for patch_info in patch_list:
             src_bbox.include(patch_info.getOuterBBox())
         src_exposure = afw_image.ExposureF(src_bbox,
                                            src_wcs)  # blank, so far
         exposure_list.append(src_exposure)
         # load srcExposures with patches
         tract_id = tract_info.getId()
         for patch_info in patch_list:
             patch_index = patch_info.getIndex()
             patch_index_str = ','.join(str(i) for i in patch_index)
             self._log.info("butler.get dataId=filter:{}, tract:{}, "
                            "patch:{}".format(filt, tract_id,
                                              patch_index_str))
             patch_exposure = self._butler.get("deepCoadd",
                                               dataId={
                                                   "filter": filt,
                                                   "tract": tract_id,
                                                   "patch": patch_index_str
                                               })
             src_view = afw_image.ExposureF(src_exposure,
                                            patch_exposure.getBBox())
             src_view_img = src_view.getMaskedImage()
             patch_img = patch_exposure.getMaskedImage()
             src_view_img[:] = patch_img
     # Copy the pixels from the source exposures to the destination exposures.
     dest_exposure_list = []
     for j, src_exposure in enumerate(exposure_list):
         src_image = src_exposure.getMaskedImage()
         src_wcs = src_exposure.getWcs()
         if j == 0:
             expo_bbox = dest_bbox  # dest_bbox only correct for first image
         else:
             # Determine the correct BBox (in pixels) for the current src_wcs
             ll_corner = afw_geom.Point2I(
                 src_wcs.skyToPixel(dest_corner_coords[0]))
             ur_corner = afw_geom.Point2I(
                 src_wcs.skyToPixel(dest_corner_coords[2]))
             # Handle negative values for in expo_bbox.
             if ll_corner.getX() < 0:
                 ll_corner.setX(0)
             if ll_corner.getY() < 0:
                 ll_corner.setY(0)
             if ur_corner.getX() < 0:
                 ur_corner.setX(0)
                 self._log.warn("getSkyMap negative X for ur_corner")
             if ur_corner.getY() < 0:
                 ur_corner.setY(0)
                 self._log.warn("getSkyMap negative Y for ur_corner")
             expo_bbox = afw_geom.Box2I(ll_corner, ur_corner)
         self._log.info("j={} expo_bbox={} sBBox={}".format(
             j, expo_bbox, src_exposure.getBBox()))
         dest_exposure = afw_image.ExposureF(expo_bbox, src_wcs)
         dest_img = dest_exposure.getMaskedImage()
         begin_x = expo_bbox.getBeginX() - src_image.getX0()
         end_x = expo_bbox.getEndX() - src_image.getX0()
         begin_y = expo_bbox.getBeginY() - src_image.getY0()
         end_y = expo_bbox.getEndY() - src_image.getY0()
         new_width = src_exposure.getBBox().getEndX() - expo_bbox.getBeginX(
         )
         new_height = src_exposure.getBBox().getEndY(
         ) - expo_bbox.getBeginY()
         # Do a final check to make sure that the we're not going past the end of src_image.
         src_img_len_x = src_image.getWidth()
         if end_x > src_img_len_x:
             new_width = src_img_len_x - begin_x
             end_x = src_img_len_x
         s_img_len_y = src_image.getHeight()
         if end_y > s_img_len_y:
             new_width = s_img_len_y - begin_y
             end_y = s_img_len_y
         self._log.debug("begin_x={} end_x={}".format(begin_x, end_x))
         self._log.debug(
             "new_width{} = sBBox.EndX{} - sBBox.BeginX{}".format(
                 new_width,
                 src_exposure.getBBox().getEndX(), expo_bbox.getBeginX()))
         self._log.debug("begin_y={} end_y={}".format(begin_y, end_y))
         self._log.debug(
             "new_height{} = sBBox.EndY{} - sBBox.BeginY{}".format(
                 new_height,
                 src_exposure.getBBox().getEndY(), expo_bbox.getBeginY()))
         dest_img[0:new_width, 0:new_height] = src_image[begin_x:end_x,
                                                         begin_y:end_y]
         dest_exposure_list.append(dest_exposure)
     # If there's only one exposure in the list (and there usually is) just return it.
     if len(dest_exposure_list) == 1:
         return dest_exposure_list[0]
     # Need to stitch together the multiple destination exposures.
     self._log.debug(
         "SkymapImage: stitching together multiple destExposures")
     warper_config = afw_math.WarperConfig()
     warper = afw_math.Warper.fromConfig(warper_config)
     stitched_exposure = self._stitch_exposures_good_pixel_copy(
         dest_wcs, dest_bbox, dest_exposure_list, warper)
     return stitched_exposure
Exemplo n.º 22
0
def ccdVennDiagram(exp, showImage=True, legendLocation='best'):
    '''
    Create a figure with the bounding boxes for each of the images which go into a coadd,
    over-plotting the given exposure object.
    @param[in] exp              (Exposure) The exposure object to plot, must be the product of a coadd
    Optional:
    @param[in] showImage        (Bool)     Plot image data in addition to it's bounding box, default True
    @param[in] legendLocation   (String)   Matplotlib legend location code, can be: 'best', 'upper right',
                                           'upper left', 'lower left', 'lower right', 'right', center left',
                                           'center right', 'lower center', 'upper center', 'center'
    '''
    # Create the figure object
    fig = plt.figure()
    # Use all the built in matplotib line style attributes to create a list of the possible styles
    linestyles = ['solid', 'dashed', 'dashdot', 'dotted']
    colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
    # Calculate the cartisian product of the styles, and randomize the order, to help each CCD get
    # it's own color
    pcomb = np.random.permutation(list(itertools.product(colors, linestyles)))
    # Filter out a black solid box, as that will be the style of the given exp object
    pcomb = pcomb[((pcomb[:, 0] == 'k') * (pcomb[:, 1] == 'solid')) is False]
    # Get the image properties
    origin = afwGeom.PointD(exp.getXY0())
    mainBox = exp.getBBox().getCorners()
    # Plot the exposure
    plt.gca().add_patch(
        patches.Rectangle((0, 0),
                          *list(mainBox[2] - mainBox[0]),
                          fill=False,
                          label="exposure"))
    # Grab all of the CCDs that went into creating the exposure
    ccds = exp.getInfo().getCoaddInputs().ccds
    # Loop over and plot the extents of each ccd
    for i, ccd in enumerate(ccds):
        ccdBox = afwGeom.Box2D(ccd.getBBox())
        ccdCorners = ccdBox.getCorners()
        coaddCorners = [
            exp.getWcs().skyToPixel(ccd.getWcs().pixelToSky(point)) +
            (afwGeom.PointD() - origin) for point in ccdCorners
        ]
        plt.gca().add_patch(
            patches.Rectangle(coaddCorners[0],
                              *list(coaddCorners[2] - coaddCorners[0]),
                              fill=False,
                              color=pcomb[i][0],
                              ls=pcomb[i][1],
                              label="CCD{}".format(i)))
    # If showImage is true, plot the data contained in exp as well as the boundrys
    if showImage:
        plt.imshow(exp.getMaskedImage().getArrays()[0],
                   cmap='Greys',
                   origin='lower')
        plt.colorbar()
    # Adjust plot parameters and plot
    plt.gca().relim()
    plt.gca().autoscale_view()
    ylim = plt.gca().get_ylim()
    xlim = plt.gca().get_xlim()
    plt.gca().set_ylim(1.5 * ylim[0], 1.5 * ylim[1])
    plt.gca().set_xlim(1.5 * xlim[0], 1.5 * xlim[1])
    plt.legend(loc=legendLocation)
    fig.canvas.draw()
    plt.show()
Exemplo n.º 23
0
def main(rootDir,
         tract,
         visits,
         ccds=None,
         ccdKey='ccd',
         showPatch=False,
         saveFile=None,
         showCcds=False):
    butler = dafPersist.Butler(rootDir)
    camera = butler.get("camera")

    # draw the CCDs
    ras, decs = [], []
    bboxesPlotted = []
    for i_v, visit in enumerate(visits):
        print("%r visit=%r" % (i_v, visit))
        for ccd in camera:
            bbox = ccd.getBBox()
            ccdId = int(ccd.getSerial())

            if (ccds is None
                    or ccdId in ccds) and ccd.getType() is cameraGeom.SCIENCE:
                dataId = {'visit': visit, ccdKey: ccdId}
                try:
                    md = butler.get("calexp_md", dataId)
                    wcs = afwImage.makeWcs(md)

                    ra, dec = bboxToRaDec(bbox, wcs)
                    ras += ra
                    decs += dec
                    color = ('r', 'b', 'c', 'g', 'm')[i_v % 5]
                    pyplot.fill(ra,
                                dec,
                                fill=True,
                                alpha=0.2,
                                color=color,
                                edgecolor=color)

                    # add CCD serial numbers
                    if showCcds:
                        minPoint = afwGeom.Point2D(min(ra), min(dec))
                        maxPoint = afwGeom.Point2D(max(ra), max(dec))
                        # Use doubles in Box2D to check overlap
                        bboxDouble = afwGeom.Box2D(minPoint, maxPoint)
                        overlaps = [
                            not bboxDouble.overlaps(otherBbox)
                            for otherBbox in bboxesPlotted
                        ]
                        if all(overlaps):
                            pyplot.text(percent(ra),
                                        percent(dec),
                                        str(ccdId),
                                        fontsize=6,
                                        horizontalalignment='center',
                                        verticalalignment='center',
                                        color=color)
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        alpha=0.5,
                                        color=color,
                                        edgecolor=color)
                        bboxesPlotted.append(bboxDouble)
                except:
                    pass

    buff = 0.1
    xlim = max(ras) + buff, min(ras) - buff
    ylim = min(decs) - buff, max(decs) + buff

    # draw the skymap
    if showPatch:
        skymap = butler.get('deepCoadd_skyMap', {'tract': 0})
        for tract in skymap:
            for patch in tract:
                ra, dec = bboxToRaDec(patch.getInnerBBox(), tract.getWcs())
                pyplot.fill(ra,
                            dec,
                            fill=False,
                            edgecolor='k',
                            lw=1,
                            linestyle='dashed')
                if xlim[1] < percent(ra) < xlim[0] and ylim[0] < percent(
                        dec) < ylim[1]:
                    pyplot.text(percent(ra),
                                percent(dec, 0.9),
                                str(patch.getIndex()),
                                fontsize=6,
                                horizontalalignment='center',
                                verticalalignment='top')

    # add labels and save
    ax = pyplot.gca()
    ax.set_xlabel("R.A. (deg)")
    ax.set_ylabel("Decl. (deg)")
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    fig = pyplot.gcf()
    if saveFile is not None:
        fig.savefig(saveFile)
    else:
        fig.show()
Exemplo n.º 24
0
    def run(self, exposure, sensorRef, templateIdList=None):
        """!Retrieve and mosaic a template coadd exposure that overlaps the exposure

        \param[in] exposure -- an exposure for which to generate an overlapping template
        \param[in] sensorRef -- a Butler data reference that can be used to obtain coadd data
        \param[in] templateIdList -- list of data ids (unused)

        \return a pipeBase.Struct
         - exposure: a template coadd exposure assembled out of patches
         - sources:  None for this subtask
        """
        skyMap = sensorRef.get(datasetType=self.config.coaddName +
                               "Coadd_skyMap")
        expWcs = exposure.getWcs()
        expBoxD = afwGeom.Box2D(exposure.getBBox())
        expBoxD.grow(self.config.templateBorderSize)
        ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())
        tractInfo = skyMap.findTract(ctrSkyPos)
        self.log.info("Using skyMap tract %s" % (tractInfo.getId(), ))
        skyCorners = [
            expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners()
        ]
        patchList = tractInfo.findPatchList(skyCorners)

        if not patchList:
            raise RuntimeError("No suitable tract found")
        self.log.info("Assembling %s coadd patches" % (len(patchList), ))

        # compute coadd bbox
        coaddWcs = tractInfo.getWcs()
        coaddBBox = afwGeom.Box2D()
        for skyPos in skyCorners:
            coaddBBox.include(coaddWcs.skyToPixel(skyPos))
        coaddBBox = afwGeom.Box2I(coaddBBox)
        self.log.info("exposure dimensions=%s; coadd dimensions=%s" %
                      (exposure.getDimensions(), coaddBBox.getDimensions()))

        # assemble coadd exposure from subregions of patches
        coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
        coaddExposure.getMaskedImage().set(np.nan, afwImage.Mask\
                                           .getPlaneBitMask("NO_DATA"), np.nan)
        nPatchesFound = 0
        coaddFilter = None
        coaddPsf = None
        for patchInfo in patchList:
            patchSubBBox = patchInfo.getOuterBBox()
            patchSubBBox.clip(coaddBBox)
            patchArgDict = dict(
                datasetType=self.config.coaddName + "Coadd_sub",
                bbox=patchSubBBox,
                tract=tractInfo.getId(),
                patch="%s,%s" %
                (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
            )
            if patchSubBBox.isEmpty():
                self.log.info(
                    "skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels"
                    % patchArgDict)
                continue
            if not sensorRef.datasetExists(**patchArgDict):
                self.log.warn(
                    "%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist"
                    % patchArgDict)
                continue

            nPatchesFound += 1
            self.log.info("Reading patch %s" % patchArgDict)
            coaddPatch = sensorRef.get(**patchArgDict)
            coaddExposure.getMaskedImage().assign(coaddPatch.getMaskedImage(),
                                                  coaddPatch.getBBox())
            if coaddFilter is None:
                coaddFilter = coaddPatch.getFilter()

            # Retrieve the PSF for this coadd tract, if not already retrieved
            if coaddPsf is None and coaddPatch.hasPsf():
                coaddPsf = coaddPatch.getPsf()

        if nPatchesFound == 0:
            raise RuntimeError("No patches found!")

        if coaddPsf is None:
            raise RuntimeError("No coadd Psf found!")

        coaddExposure.setPsf(coaddPsf)
        coaddExposure.setFilter(coaddFilter)
        return pipeBase.Struct(exposure=coaddExposure, sources=None)
Exemplo n.º 25
0
def plot_skymap_tract(skyMap, tract=0, title=None, ax=None, nvisits_tab=None):
    """
    Plot a tract from a skyMap.
    
    Parameters
    ----------
    skyMap: lsst.skyMap.SkyMap
        The SkyMap object containing the tract and patch information.
    tract: int [0]
        The tract id of the desired tract to plot.
    title: str [None]
        Title of the tract plot.  If None, the use `tract <id>`.
    ax: matplotlib.axes._subplots.AxesSubplot [None]
        The subplot object to contain the tract plot.  If None, then make a new one.

    Returns
    -------
    matplotlib.axes._subplots.AxesSubplot: The subplot containing the tract plot.
    """
    if title is None:
        title = 'tract {}'.format(tract)
    tractInfo = skyMap[tract]
    tractBox = afwGeom.Box2D(tractInfo.getBBox())
    tractPosList = tractBox.getCorners()
    wcs = tractInfo.getWcs()
    xNum, yNum = tractInfo.getNumPatches()

    if ax is None:
        fig = plt.figure(figsize=(12, 8))
        ax = fig.add_subplot(111)

    tract_center = wcs.pixelToSky(tractBox.getCenter())\
                      .getPosition(afwGeom.degrees)
    ax.text(tract_center[0],
            tract_center[1],
            '%d' % tract,
            size=16,
            ha="center",
            va="center",
            color='blue')
    for x in range(xNum):
        for y in range(yNum):
            patchInfo = tractInfo.getPatchInfo([x, y])
            patchBox = afwGeom.Box2D(patchInfo.getOuterBBox())
            pixelPatchList = patchBox.getCorners()
            path = make_patch(pixelPatchList, wcs)
            patch = patches.PathPatch(path, alpha=0.1, lw=1)
            ax.add_patch(patch)
            center = wcs.pixelToSky(patchBox.getCenter())\
                        .getPosition(afwGeom.degrees)
            if nvisits_tab is not None:
                nvisits = nvisits_tab[(nvisits_tab.tract == tract)
                                      & (nvisits_tab.patch_x == x) &
                                      (nvisits_tab.patch_y == y)].nvisits
                ax.text(center[0],
                        center[1],
                        '%d' % nvisits,
                        size=6,
                        ha="center",
                        va="center")
            else:
                ax.text(center[0],
                        center[1],
                        '%d,%d' % (x, y),
                        size=6,
                        ha="center",
                        va="center")

    skyPosList = [
        wcs.pixelToSky(pos).getPosition(afwGeom.degrees)
        for pos in tractPosList
    ]
    ax.set_xlim(
        max(coord[0] for coord in skyPosList) + 1,
        min(coord[0] for coord in skyPosList) - 1)
    ax.set_ylim(
        min(coord[1] for coord in skyPosList) - 1,
        max(coord[1] for coord in skyPosList) + 1)
    ax.grid(ls=':', color='gray')
    ax.set_xlabel("RA (deg.)")
    ax.set_ylabel("Dec (deg.)")
    ax.set_title(title)
    return ax
Exemplo n.º 26
0
def plotKernelCoefficients(spatialKernel,
                           kernelCellSet,
                           showBadCandidates=False,
                           keepPlots=True):
    """Plot the individual kernel candidate and the spatial kernel solution coefficients.

    Parameters
    ----------

    spatialKernel : `lsst.afw.math.LinearCombinationKernel`
        The spatial spatialKernel solution model which is a spatially varying linear combination
        of the spatialKernel basis functions.
        Typically returned by `lsst.ip.diffim.SpatialKernelSolution.getSolutionPair()`.

    kernelCellSet : `lsst.afw.math.SpatialCellSet`
        The spatial cells that was used for solution for the spatialKernel. They contain the
        local solutions of the AL kernel for the selected sources.

    showBadCandidates : `bool`, optional
        If True, plot the coefficient values for kernel candidates where the solution was marked
        bad by the numerical algorithm. Defaults to False.

    keepPlots: `bool`, optional
        If True, sets ``plt.show()`` to be called before the task terminates, so that the plots
        can be explored interactively. Defaults to True.

    Notes
    -----
    This function produces 3 figures per image subtraction operation.
    * A grid plot of the local solutions. Each grid cell corresponds to a proportional area in
      the image. In each cell, local kernel solution coefficients are plotted of kernel candidates (color)
      that fall into this area as a function of the kernel basis function number.
    * A grid plot of the spatial solution. Each grid cell corresponds to a proportional area in
      the image. In each cell, the spatial solution coefficients are evaluated for the center of the cell.
    * Histogram of the local solution coefficients. Red line marks the spatial solution value at
      center of the image.

    This function is called if ``lsst.ip.diffim.psfMatch.plotKernelCoefficients==True`` in lsstDebug. This
    function was implemented as part of DM-17825.
    """
    try:
        import matplotlib.pyplot as plt
    except ImportError as e:
        print("Unable to import matplotlib: %s" % e)
        return

    # Image dimensions
    imgBBox = kernelCellSet.getBBox()
    x0 = imgBBox.getBeginX()
    y0 = imgBBox.getBeginY()
    wImage = imgBBox.getWidth()
    hImage = imgBBox.getHeight()
    imgCenterX = imgBBox.getCenterX()
    imgCenterY = imgBBox.getCenterY()

    # Plot the local solutions
    # ----

    # Grid size
    nX = 8
    nY = 8
    wCell = wImage / nX
    hCell = hImage / nY

    fig = plt.figure()
    fig.suptitle("Kernel candidate parameters on an image grid")
    arrAx = fig.subplots(nrows=nY,
                         ncols=nX,
                         sharex=True,
                         sharey=True,
                         gridspec_kw=dict(wspace=0, hspace=0))

    # Bottom left panel is for bottom left part of the image
    arrAx = arrAx[::-1, :]

    allParams = []
    for cell in kernelCellSet.getCellList():
        cellBBox = afwGeom.Box2D(cell.getBBox())
        # Determine which panel this spatial cell belongs to
        iX = int((cellBBox.getCenterX() - x0) // wCell)
        iY = int((cellBBox.getCenterY() - y0) // hCell)

        for cand in cell.begin(False):
            try:
                kernel = cand.getKernel(cand.ORIG)
            except Exception:
                continue

            if not showBadCandidates and cand.isBad():
                continue

            nKernelParams = kernel.getNKernelParameters()
            kernelParams = np.array(kernel.getKernelParameters())
            allParams.append(kernelParams)

            if cand.isBad():
                color = 'red'
            else:
                color = None
            arrAx[iY, iX].plot(np.arange(nKernelParams),
                               kernelParams,
                               '.-',
                               color=color,
                               drawstyle='steps-mid',
                               linewidth=0.1)
    for ax in arrAx.ravel():
        ax.grid(True, axis='y')

    # Plot histogram of the local parameters and the global solution at the image center
    # ----

    spatialFuncs = spatialKernel.getSpatialFunctionList()
    nKernelParams = spatialKernel.getNKernelParameters()
    nX = 8
    fig = plt.figure()
    fig.suptitle(
        "Hist. of parameters marked with spatial solution at img center")
    arrAx = fig.subplots(nrows=int(nKernelParams // nX) + 1, ncols=nX)
    arrAx = arrAx[::-1, :]
    allParams = np.array(allParams)
    for k in range(nKernelParams):
        ax = arrAx.ravel()[k]
        ax.hist(allParams[:, k], bins=20, edgecolor='black')
        ax.set_xlabel('P{}'.format(k))
        valueParam = spatialFuncs[k](imgCenterX, imgCenterY)
        ax.axvline(x=valueParam, color='red')
        ax.text(0.1,
                0.9,
                '{:.1f}'.format(valueParam),
                transform=ax.transAxes,
                backgroundcolor='lightsteelblue')

    # Plot grid of the spatial solution
    # ----

    nX = 8
    nY = 8
    wCell = wImage / nX
    hCell = hImage / nY
    x0 += wCell / 2
    y0 += hCell / 2

    fig = plt.figure()
    fig.suptitle("Spatial solution of kernel parameters on an image grid")
    arrAx = fig.subplots(nrows=nY,
                         ncols=nX,
                         sharex=True,
                         sharey=True,
                         gridspec_kw=dict(wspace=0, hspace=0))
    arrAx = arrAx[::-1, :]
    kernelParams = np.zeros(nKernelParams, dtype=float)

    for iX in range(nX):
        for iY in range(nY):
            x = x0 + iX * wCell
            y = y0 + iY * hCell
            # Evaluate the spatial solution functions for this x,y location
            kernelParams = [f(x, y) for f in spatialFuncs]
            arrAx[iY, iX].plot(np.arange(nKernelParams),
                               kernelParams,
                               '.-',
                               drawstyle='steps-mid')
            arrAx[iY, iX].grid(True, axis='y')

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

        import atexit
        atexit.register(show)
        keptPlots = True
Exemplo n.º 27
0
def main(rootDir,
         tracts,
         visits,
         ccds=None,
         ccdKey='ccd',
         showPatch=False,
         saveFile=None,
         showCcds=False):
    butler = dafPersist.Butler(rootDir)
    camera = butler.get("camera")

    # draw the CCDs
    ras, decs = [], []
    bboxesPlotted = []
    print('Number of visits = ', len(visits))
    cmap = get_cmap(len(visits))
    for i_v, visit in enumerate(visits):
        print("%r visit=%r" % (i_v + 1, visit))
        inLegend = False
        color = cmap(i_v)
        for ccd in camera:
            bbox = ccd.getBBox()
            ccdId = int(ccd.getId())

            if (ccds is None
                    or ccdId in ccds) and ccd.getType() == cameraGeom.SCIENCE:
                dataId = {'visit': visit, ccdKey: ccdId}
                try:
                    md = butler.get("calexp_md", dataId)
                    wcs = afwGeom.makeSkyWcs(md)

                    ra, dec = bboxToRaDec(bbox, wcs)
                    ras += ra
                    decs += dec
                    if not inLegend:
                        pyplot.fill(ra,
                                    dec,
                                    fill=True,
                                    alpha=0.2,
                                    color=color,
                                    edgecolor=color,
                                    label=str(visit))
                        inLegend = True
                    else:
                        pyplot.fill(ra,
                                    dec,
                                    fill=True,
                                    alpha=0.2,
                                    color=color,
                                    edgecolor=color)

                    # add CCD serial numbers
                    if showCcds:
                        minPoint = afwGeom.Point2D(min(ra), min(dec))
                        maxPoint = afwGeom.Point2D(max(ra), max(dec))
                        # Use doubles in Box2D to check overlap
                        bboxDouble = afwGeom.Box2D(minPoint, maxPoint)
                        overlaps = [
                            not bboxDouble.overlaps(otherBbox)
                            for otherBbox in bboxesPlotted
                        ]
                        if all(overlaps):
                            pyplot.text(percent(ra),
                                        percent(dec),
                                        str(ccdId),
                                        fontsize=6,
                                        horizontalalignment='center',
                                        verticalalignment='center',
                                        color=color)
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        alpha=0.5,
                                        color=color,
                                        edgecolor=color)
                        bboxesPlotted.append(bboxDouble)
                except Exception:
                    pass

    buff = 0.1
    xlim = max(ras) + buff, min(ras) - buff
    ylim = min(decs) - buff, max(decs) + buff

    skymap = butler.get("deepCoadd_skyMap")
    # draw the skymap
    if showPatch:
        alpha0 = 1.0
        for i_t, tract in enumerate(tracts):
            alpha = max(0.1, alpha0 - i_t * 1.0 / len(tracts))
            inLegend = False
            for tractInfo in skymap:
                if tractInfo.getId() == tract:
                    for patch in tractInfo:
                        ra, dec = bboxToRaDec(patch.getInnerBBox(),
                                              tractInfo.getWcs())
                        if not inLegend:
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        edgecolor='k',
                                        lw=1,
                                        linestyle='dashed',
                                        alpha=alpha,
                                        label=str(tract))
                            inLegend = True
                        else:
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        edgecolor='k',
                                        lw=1,
                                        linestyle='dashed',
                                        alpha=alpha)
                        if xlim[1] < percent(ra) < xlim[0] and ylim[
                                0] < percent(dec) < ylim[1]:
                            pyplot.text(percent(ra),
                                        percent(dec),
                                        ((str(patch.getIndex()))[1:-1].replace(
                                            " ", "")),
                                        fontsize=6,
                                        horizontalalignment='center',
                                        verticalalignment='center',
                                        alpha=alpha)

    # add labels and save
    ax = pyplot.gca()
    ax.set_xlabel("R.A. (deg)")
    ax.set_ylabel("Decl. (deg)")
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    ax.legend(loc='center left',
              bbox_to_anchor=(1.0, 0.5),
              fancybox=True,
              shadow=True,
              fontsize=6)
    fig = pyplot.gcf()
    if saveFile is not None:
        fig.savefig(saveFile)
    else:
        fig.show()
Exemplo n.º 28
0
    def runDataRef(self,
                   dataRef,
                   coordList,
                   makeDataRefList=True,
                   selectDataList=[]):
        """Select images in the selectDataList that overlap the patch

        We use the "convexHull" function in the geom package to define
        polygons on the celestial sphere, and test the polygon of the
        patch for overlap with the polygon of the image.

        We use "convexHull" instead of generating a SphericalConvexPolygon
        directly because the standard for the inputs to SphericalConvexPolygon
        are pretty high and we don't want to be responsible for reaching them.
        If "convexHull" is found to be too slow, we can revise this.

        @param dataRef: Data reference for coadd/tempExp (with tract, patch)
        @param coordList: List of Coord specifying boundary of patch
        @param makeDataRefList: Construct a list of data references?
        @param selectDataList: List of SelectStruct, to consider for selection
        """
        from lsst.geom import convexHull

        dataRefList = []
        exposureInfoList = []

        patchVertices = [coord.getVector() for coord in coordList]
        patchPoly = convexHull(patchVertices)

        for data in selectDataList:
            dataRef = data.dataRef
            imageWcs = data.wcs
            nx, ny = data.dims

            imageBox = afwGeom.Box2D(afwGeom.Point2D(0, 0),
                                     afwGeom.Extent2D(nx, ny))
            try:
                imageCorners = [
                    imageWcs.pixelToSky(pix) for pix in imageBox.getCorners()
                ]
            except (pexExceptions.DomainError,
                    pexExceptions.RuntimeError) as e:
                # Protecting ourselves from awful Wcs solutions in input images
                self.log.debug(
                    "WCS error in testing calexp %s (%s): deselecting",
                    dataRef.dataId, e)
                continue

            imagePoly = convexHull(
                [coord.getVector() for coord in imageCorners])
            if imagePoly is None:
                self.log.debug(
                    "Unable to create polygon from image %s: deselecting",
                    dataRef.dataId)
                continue
            if patchPoly.intersects(
                    imagePoly
            ):  # "intersects" also covers "contains" or "is contained by"
                self.log.info("Selecting calexp %s" % dataRef.dataId)
                dataRefList.append(dataRef)
                exposureInfoList.append(
                    BaseExposureInfo(dataRef.dataId, imageCorners))

        return pipeBase.Struct(
            dataRefList=dataRefList if makeDataRefList else None,
            exposureInfoList=exposureInfoList,
        )
Exemplo n.º 29
0
for tractOverlap in (0.0, 0.33, 1.0, 3.5):
    print("tractOverlap = %s degrees" % (tractOverlap,))
    config = DodecaSkyMap.ConfigClass()
    config.tractOverlap = tractOverlap
    skyMap = DodecaSkyMap(config)
    totNumPix = 0
    print("Tract  Ctr RA  Ctr Dec    Rows        Cols       # Pix   Width  Height")
    print(" ID     (deg)   (deg)     (pix)       (pix)              (deg)  (deg)")
    for tractInfo in skyMap:
        bbox = tractInfo.getBBox()
        dimensions = bbox.getDimensions()
        numPix = dimensions[0] * dimensions[1]
        totNumPix += numPix
        wcs = tractInfo.getWcs()
        posBBox = afwGeom.Box2D(bbox)
        ctrPixPos = posBBox.getCenter()
        ctrCoord = wcs.pixelToSky(ctrPixPos)
        ctrSkyPosDeg = ctrCoord.getPosition(afwGeom.degrees)
        leftCoord = wcs.pixelToSky(afwGeom.Point2D(posBBox.getMinX(), ctrPixPos[1]))
        rightCoord = wcs.pixelToSky(afwGeom.Point2D(posBBox.getMaxX(), ctrPixPos[1]))
        topCoord = wcs.pixelToSky(afwGeom.Point2D(ctrPixPos[0], posBBox.getMinY()))
        bottomCoord = wcs.pixelToSky(afwGeom.Point2D(ctrPixPos[0], posBBox.getMaxY()))
        xSpan = leftCoord.separation(rightCoord).asDegrees()
        ySpan = bottomCoord.separation(topCoord).asDegrees()
        print("%3d   %7.1f %7.1f %10.2e  %10.2e %10.1e %6.1f %6.1f" %
              (tractInfo.getId(), ctrSkyPosDeg[0], ctrSkyPosDeg[1],
               dimensions[0], dimensions[1], numPix, xSpan, ySpan))

    pixelScaleRad = afwGeom.Angle(skyMap.config.pixelScale, afwGeom.arcseconds).asRadians()
    nomPixelAreaRad2 = pixelScaleRad**2  # nominal area of a pixel in rad^2
Exemplo n.º 30
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,
        )