Exemple #1
0
    def __init__(self, config=None):
        BaseSkyMap.__init__(self, config)
        self._dodecahedron = detail.Dodecahedron(
            withFacesOnPoles=self.config.withTractsOnPoles)

        tractOverlap = geom.Angle(self.config.tractOverlap, geom.degrees)

        for id in range(12):
            tractVec = self._dodecahedron.getFaceCtr(id)
            tractCoord = detail.coordFromVec(tractVec, defRA=geom.Angle(0))
            tractRA = tractCoord.getLongitude()
            vertexVecList = self._dodecahedron.getVertices(id)

            # make initial WCS; don't worry about crPixPos because TractInfo will shift it as required
            wcs = self._wcsFactory.makeWcs(crPixPos=geom.Point2D(0, 0),
                                           crValCoord=tractCoord)

            self._tractInfoList.append(
                TractInfo(
                    id=id,
                    tractBuilder=self._tractBuilder,
                    ctrCoord=tractCoord,
                    vertexCoordList=[
                        detail.coordFromVec(vec, defRA=tractRA)
                        for vec in vertexVecList
                    ],
                    tractOverlap=tractOverlap,
                    wcs=wcs,
                ))
def make_afw_coords(coord_list):
    """
    Convert list of ra and dec to lsst.afw.coord.IcrsCoord.

    Parameters
    ----------
    coord_list : list of tuples or tuple
        ra and dec in degrees.

    Returns
    -------
    afw_coords : list of lsst.afw.coord.IcrsCoord
    """
    if type(coord_list[0]) in (float, int, np.float64):
        ra, dec = coord_list
        afw_coords = geom.SpherePoint(geom.Angle(ra, geom.degrees),
                                      geom.Angle(dec, geom.degrees))
    else:
        afw_coords = [
            geom.SpherePoint(geom.Angle(ra, geom.degrees),
                             geom.Angle(dec, geom.degrees))
            for ra, dec in coord_list
        ]

    return afw_coords
Exemple #3
0
def make_stamps(n_stamps=3, use_archive=False):
    stampSize = 25
    # create dummy stamp stamps
    stampImages = [
        afwImage.maskedImage.MaskedImageF(stampSize, stampSize)
        for _ in range(n_stamps)
    ]
    for stampIm in stampImages:
        stampImArray = stampIm.image.array
        stampImArray += np.random.rand(stampSize, stampSize)
        stampMaskArray = stampIm.mask.array
        stampMaskArray += 10
        stampVarArray = stampIm.variance.array
        stampVarArray += 1000.
    ras = np.random.rand(n_stamps) * 360.
    decs = np.random.rand(n_stamps) * 180 - 90
    archive_elements = [
        tF.makeTransform(geom.AffineTransform(np.random.rand(2)))
        if use_archive else None for _ in range(n_stamps)
    ]
    stamp_list = [
        stamps.Stamp(stamp_im=stampIm,
                     position=geom.SpherePoint(geom.Angle(ra, geom.degrees),
                                               geom.Angle(dec, geom.degrees)),
                     archive_element=ae) for stampIm, ra, dec, ae in zip(
                         stampImages, ras, decs, archive_elements)
    ]
    metadata = PropertyList()
    metadata['RA_DEG'] = ras
    metadata['DEC_DEG'] = decs

    return stamps.Stamps(stamp_list, metadata=metadata, use_archive=True)
Exemple #4
0
def make_stamps(n_stamps=3):
    stampSize = 25
    # create dummy stamp stamps
    stampImages = [
        afwImage.maskedImage.MaskedImageF(stampSize, stampSize)
        for _ in range(n_stamps)
    ]
    for stampIm in stampImages:
        stampImArray = stampIm.image.array
        stampImArray += np.random.rand(stampSize, stampSize)
        stampMaskArray = stampIm.mask.array
        stampMaskArray += 10
        stampVarArray = stampIm.variance.array
        stampVarArray += 1000.
    ras = np.random.rand(n_stamps) * 360.
    decs = np.random.rand(n_stamps) * 180 - 90
    stamp_list = [
        stamps.Stamp(stamp_im=stampIm,
                     position=geom.SpherePoint(geom.Angle(ra, geom.degrees),
                                               geom.Angle(dec, geom.degrees)))
        for stampIm, ra, dec in zip(stampImages, ras, decs)
    ]
    metadata = PropertyList()
    metadata['RA_DEG'] = ras
    metadata['DEC_DEG'] = decs

    return stamps.Stamps(stamp_list, metadata=metadata)
Exemple #5
0
    def __init__(self, config=None):
        BaseSkyMap.__init__(self, config)

        decRange = tuple(geom.Angle(dr, geom.degrees) for dr in self.config.decRange)
        midDec = (decRange[0] + decRange[1]) / 2.0
        tractWidthRA = geom.Angle(360.0 / self.config.numTracts, geom.degrees)
        tractOverlap = geom.Angle(self.config.tractOverlap, geom.degrees)

        for id in range(self.config.numTracts):
            begRA = tractWidthRA * id
            endRA = begRA + tractWidthRA
            vertexCoordList = (
                geom.SpherePoint(begRA, decRange[0]),
                geom.SpherePoint(endRA, decRange[0]),
                geom.SpherePoint(endRA, decRange[1]),
                geom.SpherePoint(begRA, decRange[1]),
            )

            midRA = begRA + tractWidthRA / 2.0
            ctrCoord = geom.SpherePoint(midRA, midDec)

            # CRVal must have Dec=0 for symmetry about the equator
            crValCoord = geom.SpherePoint(midRA, geom.Angle(0.0))

            # make initial WCS; don't worry about crPixPos because TractInfo will shift it as required
            wcs = self._wcsFactory.makeWcs(crPixPos=geom.Point2D(0, 0), crValCoord=crValCoord)

            self._tractInfoList.append(TractInfo(
                id=id,
                tractBuilder=self._tractBuilder,
                ctrCoord=ctrCoord,
                vertexCoordList=vertexCoordList,
                tractOverlap=tractOverlap,
                wcs=wcs,
            ))
Exemple #6
0
def averageRaDec(ra, dec):
    """Calculate average RA, Dec from input lists using spherical geometry.

    Parameters
    ----------
    ra : `list` [`float`]
        RA in [radians]
    dec : `list` [`float`]
        Dec in [radians]

    Returns
    -------
    float, float
       meanRa, meanDec -- Tuple of average RA, Dec [radians]
    """
    assert (len(ra) == len(dec))

    angleRa = [geom.Angle(r, geom.radians) for r in ra]
    angleDec = [geom.Angle(d, geom.radians) for d in dec]
    coords = [
        geom.SpherePoint(ar, ad, geom.radians)
        for (ar, ad) in zip(angleRa, angleDec)
    ]

    meanRa, meanDec = geom.averageSpherePoint(coords)

    return meanRa.asRadians(), meanDec.asRadians()
Exemple #7
0
 def orientation(self):
     """Return the cameraGeom.Orientation() object defined by the
     configuration values.
     """
     return Orientation(self.offset, self.refPos,
                        geom.Angle(self.yawDeg, geom.degrees),
                        geom.Angle(self.pitchDeg, geom.degrees),
                        geom.Angle(self.rollDeg, geom.degrees))
Exemple #8
0
def vizierLocationForTarget(exp, target, doMotionCorrection):
    """Get the target location from Vizier optionally correction motion.

    Parameters
    ----------
    target : `str`
        The name of the target, e.g. 'HD 55852'

    Returns
    -------
    targetLocation : `lsst.geom.SpherePoint` or `None`
        Location of the target object, optionally corrected for
        proper motion and parallax.

    Raises
    ------
    ValueError
        If object not found in Hipparcos2 via Vizier.
        This is quite common, even for bright objects.
    """
    # do not import at the module level - tests crash due to a race
    # condition with directory creation
    from astroquery.vizier import Vizier

    result = Vizier.query_object(
        target)  # result is an empty table list for an unknown target
    try:
        star = result['I/311/hip2']
    except TypeError:  # if 'I/311/hip2' not in result (result doesn't allow easy checking without a try)
        raise ValueError

    epoch = "J1991.25"
    coord = SkyCoord(
        ra=star[0]['RArad'] * u.Unit(star['RArad'].unit),
        dec=star[0]['DErad'] * u.Unit(star['DErad'].unit),
        obstime=epoch,
        pm_ra_cosdec=star[0]['pmRA'] *
        u.Unit(star['pmRA'].unit),  # NB contains cosdec already
        pm_dec=star[0]['pmDE'] * u.Unit(star['pmDE'].unit),
        distance=Distance(parallax=star[0]['Plx'] * u.Unit(star['Plx'].unit)))

    if doMotionCorrection:
        expDate = exp.getInfo().getVisitInfo().getDate()
        obsTime = astropy.time.Time(expDate.get(expDate.EPOCH),
                                    format='jyear',
                                    scale='tai')
        newCoord = coord.apply_space_motion(new_obstime=obsTime)
    else:
        newCoord = coord

    raRad, decRad = newCoord.ra.rad, newCoord.dec.rad
    ra = geom.Angle(raRad)
    dec = geom.Angle(decRad)
    targetLocation = geom.SpherePoint(ra, dec)
    return targetLocation
def makeCamera(cameraFile):
    """An imaging camera (e.g. the LSST 3Gpix camera)

    Parameters
    ----------
    cameraFile : `str`
        Camera description YAML file.

    Returns
    -------
    camera : `lsst.afw.cameraGeom.Camera`
        The desired Camera
    """

    with open(cameraFile) as fd:
        cameraParams = yaml.load(fd, Loader=yaml.CLoader)

    cameraName = cameraParams["name"]

    #
    # Handle distortion models.
    #
    plateScale = geom.Angle(cameraParams["plateScale"], geom.arcseconds)
    nativeSys = cameraGeom.CameraSys(cameraParams["transforms"].pop("nativeSys"))
    transforms = makeTransformDict(nativeSys, cameraParams["transforms"], plateScale)

    ccdParams = cameraParams["CCDs"]
    detectorConfigList = makeDetectorConfigList(ccdParams)

    amplifierDict = {}
    for ccdName, ccdValues in ccdParams.items():
        amplifierDict[ccdName] = makeAmplifierList(ccdValues)

    return makeCameraFromCatalogs(cameraName, detectorConfigList, nativeSys, transforms, amplifierDict)
    def addCatalog(self, src, filter, visit, ccd, calib, footprints):
        """Add objects from a catalog to the existing MultiMatch

        @param[in]  srcCat      An SourceCatalog of objects to be added.
        @param[in]  filt        The filter of the catalog
        @param[in]  visit       The visit number
        @param[in]  ccd         The ccd number
        @param[in]  footprints  A list of footprints that have been transformed to the
                                WCS of the coadd patch.
        """

        if self.multi_matches is None:
            # The data id for multiMatch does not take strings so we need to convert filter to a string
            self.multi_matches = afwTable.MultiMatch(
                src.schema, {
                    'visit': np.int32,
                    'ccd': np.int32,
                    'filter': np.int32
                },
                radius=geom.Angle(self.config.tolerance / 3600., geom.degrees))
        for s, foot in zip(src, footprints):
            s.setFootprint(foot)
        self.multi_matches.add(
            src, {
                'visit': visit,
                'ccd': ccd,
                'filter': self.config.filters.index(filter)
            })
        self.calibDict[visit][ccd] = calib
Exemple #11
0
    def testFindTract(self):
        """Test the SkyMap.findTract method
        """
        for numTracts in (2, 4):
            config = EquatSkyMap.ConfigClass()
            config.numTracts = numTracts
            skyMap = EquatSkyMap(config)
            decRange = skyMap.config.decRange
            decList = (
                (decRange[0] * 0.999) + (decRange[1] * 0.901),
                (decRange[0] * 0.500) + (decRange[1] * 0.500),
                (decRange[0] * 0.091) + (decRange[1] * 0.999),
            )
            for tractInfo0 in skyMap:
                tractId0 = tractInfo0.getId()
                ctrCoord0 = tractInfo0.getCtrCoord()

                for tractInfo1 in self.getNeighborTracts(skyMap, tractId0):

                    tractId1 = tractInfo1.getId()
                    ctrCoord1 = tractInfo1.getCtrCoord()

                    for deltaFrac in (-0.001, 0.001):
                        v0 = ctrCoord0.getVector() * (0.5 + deltaFrac)
                        v1 = ctrCoord1.getVector() * (0.5 - deltaFrac)
                        testVec = v0 + v1
                        testRa = geom.SpherePoint(testVec).getRa()

                        if deltaFrac > 0.0:
                            expectedTractId = tractId0
                        else:
                            expectedTractId = tractId1

                        for testDecDeg in decList:
                            testDec = geom.Angle(testDecDeg, geom.degrees)
                            testCoord = geom.SpherePoint(testRa, testDec)

                            nearestTractInfo = skyMap.findTract(testCoord)
                            nearestTractId = nearestTractInfo.getId()

                            self.assertEqual(nearestTractId, expectedTractId)

                            patchInfo = nearestTractInfo.findPatch(testCoord)
                            pixelInd = geom.Point2I(nearestTractInfo.getWcs().skyToPixel(testCoord))
                            self.assertTrue(patchInfo.getInnerBBox().contains(pixelInd))

                # find a point outside the tract and make sure it fails
                tractInfo = tractInfo0
                wcs = tractInfo.getWcs()
                bbox = geom.Box2D(tractInfo.getBBox())
                outerPixPosList = [
                    bbox.getMin() - geom.Extent2D(1, 1),
                    geom.Point2D(bbox.getMaxX(), bbox.getMinY()) - geom.Extent2D(1, 1),
                    bbox.getMax() + geom.Extent2D(1, 1),
                    geom.Point2D(bbox.getMinX(), bbox.getMaxY()) + geom.Extent2D(1, 1),
                ]
                for outerPixPos in outerPixPosList:
                    testCoord = wcs.pixelToSky(outerPixPos)
                    self.assertRaises(LookupError, tractInfo.findPatch, testCoord)
Exemple #12
0
def simbadLocationForTarget(target):
    """Get the target location from Simbad.

    Parameters
    ----------
    target : `str`
        The name of the target, e.g. 'HD 55852'

    Returns
    -------
    targetLocation : `lsst.geom.SpherePoint`
        Nominal location of the target object, uncorrected for
        proper motion and parallax.

    Raises
    ------
    ValueError
        If object not found, or if multiple entries for the object are found.
    """
    # do not import at the module level - tests crash due to a race
    # condition with directory creation
    from astroquery.simbad import Simbad

    obj = Simbad.query_object(target)
    if not obj:
        raise ValueError(f"Found failed to find {target} in simbad!")
    if len(obj) != 1:
        raise ValueError(f"Found {len(obj)} simbad entries for {target}!")

    raStr = obj[0]['RA']
    decStr = obj[0]['DEC']
    skyLocation = SkyCoord(raStr,
                           decStr,
                           unit=(u.hourangle, u.degree),
                           frame='icrs')
    raRad, decRad = skyLocation.ra.rad, skyLocation.dec.rad
    ra = geom.Angle(raRad)
    dec = geom.Angle(decRad)
    targetLocation = geom.SpherePoint(ra, dec)
    return targetLocation
Exemple #13
0
 def _bbox_for_coords(self, wcs, center_coord, width, height, units):
     """Returns a Box2I object representing the bounding box in pixels
     of the target region.
     @wcs: WCS object for the target region.
     @center_coord: ICRS RA and Dec coordinate for the center of the target
                    region.
     @width: Width of the target region with units indicated by 'units'
             below.
     @height: Height of the target region with units indicated by 'units'
             below.
     @units: Units for height and width. 'pixel' or 'arcsecond'
     """
     if units == "arcsec":
         # center_coord center, RA and Dec with width and height in
         # arcseconds
         width_half_a = geom.Angle((width / 2.0), geom.arcseconds)
         height_half_a = geom.Angle((height / 2.0), geom.arcseconds)
         min_ra = center_coord.getLongitude() - width_half_a
         min_dec = center_coord.getLatitude() - height_half_a
         max_ra = center_coord.getLongitude() + width_half_a
         max_dec = center_coord.getLatitude() + height_half_a
         ll_coord = geom.SpherePoint(min_ra, min_dec, geom.degrees)
         ll_coord_pix = wcs.skyToPixel(ll_coord)
         ur_coord = geom.SpherePoint(max_ra, max_dec, geom.degrees)
         ur_coord_pix = wcs.skyToPixel(ur_coord)
         p2i_min = geom.Point2I(ll_coord_pix)
         p2i_max = geom.Point2I(ur_coord_pix)
         bbox = geom.Box2I(p2i_min, p2i_max)
     elif units == "pixel":
         # center_coord center, RA and Dec with width and height in pixels
         ctr_coord_pix = wcs.skyToPixel(center_coord)
         min_ra_pix = int(ctr_coord_pix.getX() - width // 2)
         min_dec_pix = int(ctr_coord_pix.getY() - height // 2)
         p2i = geom.Point2I(min_ra_pix, min_dec_pix)
         bbox = geom.Box2I(p2i, geom.Extent2I(width, height))
     else:
         raise Exception("invalid units {}".format(units))
     return bbox
Exemple #14
0
    def __new__(cls):
        plateScale = geom.Angle(20, geom.arcseconds)  # plate scale, in angle on sky/mm
        # Radial distortion is modeled as a radial polynomial that converts from focal plane (in mm)
        # to field angle (in radians). Thus the coefficients are:
        # C0: always 0, for continuity at the center of the focal plane; units are rad
        # C1: 1/plateScale; units are rad/mm
        # C2: usually 0; units are rad/mm^2
        # C3: radial distortion; units are rad/mm^3
        radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScale.asRadians()
        fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeff)
        focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted()

        camera = cameraGeom.Camera.Builder("test")
        cls._makeDetectors(camera, focalPlaneToFieldAngle)
        camera.setTransformFromFocalPlaneTo(cameraGeom.FIELD_ANGLE, focalPlaneToFieldAngle)
        return camera.finish()
Exemple #15
0
    def getOrientation(self, source):
        """Calculate the orientation of dipole; vector from negative to positive lobe

        Parameters
        ----------
        source : `lsst.afw.table.SourceRecord`
            The source that will be examined"""

        negCenX = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_x")
        negCenY = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_y")
        posCenX = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_x")
        posCenY = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_y")
        if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX)
                or np.isinf(posCenY)):
            return None

        dx, dy = posCenX - negCenX, posCenY - negCenY
        angle = geom.Angle(np.arctan2(dx, dy), geom.radians)
        return angle
    def __call__(self, centerCoord, band, radius=2.1):
        """
        Return the reference catalog entries within the desired
        sky cone.

        Paramters
        ---------
        centerCoord: lsst.geom.SpherePoint
            Center of the sky cone to use to select objects.
        band: str
            Band of visit, one of 'ugrizy'.
        radius: float [2.1]
            Radius in degrees of the sky cone.

        Returns
        -------
        lsst.afw.table.SimpleCatalog
        """
        radius = lsst_geom.Angle(radius, lsst_geom.degrees)
        return self.ref_task.loadSkyCircle(centerCoord, radius, band).refCat
Exemple #17
0
 def run(self, source_catalogs, photo_calibs, astrom_calibs, vIds, wcs, box,
         apply_external_wcs):
     self.log.info("Running catalog matching")
     radius = geom.Angle(self.radius, geom.arcseconds)
     srcvis, matched = match_catalogs(source_catalogs,
                                      photo_calibs,
                                      astrom_calibs,
                                      vIds,
                                      radius,
                                      apply_external_wcs,
                                      logger=self.log)
     # Trim the output to the patch bounding box
     out_matched = type(matched)(matched.schema)
     self.log.info(f"{len(matched)} sources in matched catalog.")
     for record in matched:
         if box.contains(wcs.skyToPixel(record.getCoord())):
             out_matched.append(record)
     self.log.info(
         f"{len(out_matched)} sources when trimmed to {self.level} boundaries."
     )
     return pipeBase.Struct(outputCatalog=out_matched)
Exemple #18
0
    def _minimumBoundingBox(self, wcs):
        """Calculate the minimum bounding box for the tract, given the WCS.

        The bounding box is created in the frame of the supplied WCS,
        so that it's OK if the coordinates are negative.

        We compute the bounding box that holds all the vertices and the
        desired overlap.
        """
        minBBoxD = geom.Box2D()
        halfOverlap = self._tractOverlap / 2.0
        for vertexCoord in self._vertexCoordList:
            if self._tractOverlap == 0:
                minBBoxD.include(wcs.skyToPixel(vertexCoord))
            else:
                numAngles = 24
                angleIncr = geom.Angle(360.0, geom.degrees) / float(numAngles)
                for i in range(numAngles):
                    offAngle = angleIncr * i
                    offCoord = vertexCoord.offset(offAngle, halfOverlap)
                    pixPos = wcs.skyToPixel(offCoord)
                    minBBoxD.include(pixPos)
        return minBBoxD
Exemple #19
0
def createWcs(x, y, mapper, order=4, cOffset=1.0):
    # Here cOffset reflects the differences between FITS coords (LLC =
    # 1,1) and LSST coords (LLC = 0,0).  That is, when creating a Wcs
    # from scratch, we need to evaluate our WCS at coordinate 0,0 to
    # create CRVAL, but set CRPIX to 1,1.

    ra_rad, dec_rad = mapper.xyToRaDec(x, y)

    # Minimial table for sky coordinates
    catTable = afwTable.SimpleTable.make(afwTable.SimpleTable.makeMinimalSchema())

    # Minimial table + centroids for focal plane coordintes
    srcSchema = afwTable.SourceTable.makeMinimalSchema()
    centroidKey = afwTable.Point2DKey.addFields(srcSchema, "centroid", "centroid", "pixel")

    srcTable = afwTable.SourceTable.make(srcSchema)
    srcTable.defineCentroid("centroid")

    matches = []
    for i in range(len(x)):
        src = srcTable.makeRecord()
        src.set(centroidKey.getX(), x[i])
        src.set(centroidKey.getY(), y[i])

        cat = catTable.makeRecord()
        cat.set(catTable.getCoordKey().getRa(), geom.Angle(ra_rad[i], geom.radians))
        cat.set(catTable.getCoordKey().getDec(), geom.Angle(dec_rad[i], geom.radians))

        mat = afwTable.ReferenceMatch(cat, src, 0.0)
        matches.append(mat)

    # Need to make linear Wcs around which to expand solution

    # CRPIX1  = Column Pixel Coordinate of Ref. Pixel
    # CRPIX2  = Row Pixel Coordinate of Ref. Pixel
    crpix = geom.Point2D(x[0] + cOffset, y[0] + cOffset)

    # CRVAL1  = RA at Reference Pixel
    # CRVAL2  = DEC at Reference Pixel
    crval = geom.SpherePoint(ra_rad[0], dec_rad[0], geom.radians)

    # CD1_1   = RA  degrees per column pixel
    # CD1_2   = RA  degrees per row pixel
    # CD2_1   = DEC degrees per column pixel
    # CD2_2   = DEC degrees per row pixel
    LLl = mapper.xyToRaDec(0., 0.)
    ULl = mapper.xyToRaDec(0., 1.)
    LRl = mapper.xyToRaDec(1., 0.)

    LLc = geom.SpherePoint(LLl[0], LLl[1], geom.radians)
    ULc = geom.SpherePoint(ULl[0], ULl[1], geom.radians)
    LRc = geom.SpherePoint(LRl[0], LRl[1], geom.radians)

    cdN_1 = LLc.getTangentPlaneOffset(LRc)
    cdN_2 = LLc.getTangentPlaneOffset(ULc)
    cd1_1, cd2_1 = cdN_1[0].asDegrees(), cdN_1[1].asDegrees()
    cd1_2, cd2_2 = cdN_2[0].asDegrees(), cdN_2[1].asDegrees()

    cdMatrix = np.array([cd1_1, cd2_1, cd1_2, cd2_2], dtype=float)
    cdMatrix.shape = (2, 2)

    linearWcs = makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix)
    wcs = sip.makeCreateWcsWithSip(matches, linearWcs, order).getNewWcs()

    return wcs
Exemple #20
0
    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 = geom.Box2D(bbox)
        ctrPixPos = posBBox.getCenter()
        ctrCoord = wcs.pixelToSky(ctrPixPos)
        ctrSkyPosDeg = ctrCoord.getPosition(geom.degrees)
        leftCoord = wcs.pixelToSky(geom.Point2D(posBBox.getMinX(), ctrPixPos[1]))
        rightCoord = wcs.pixelToSky(geom.Point2D(posBBox.getMaxX(), ctrPixPos[1]))
        topCoord = wcs.pixelToSky(geom.Point2D(ctrPixPos[0], posBBox.getMinY()))
        bottomCoord = wcs.pixelToSky(geom.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 = geom.Angle(skyMap.config.pixelScale, geom.arcseconds).asRadians()
    nomPixelAreaRad2 = pixelScaleRad**2  # nominal area of a pixel in rad^2
    numPixToTileSphere = 4 * math.pi / nomPixelAreaRad2
    print("total pixels = %.1e" % (totNumPix,))
    print("pixels to tile sphere = %.1e" % (numPixToTileSphere,))
    print("extra storage (tot pix/pix to tile) = %.1f\n" % (totNumPix / numPixToTileSphere,))
    def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources,
                 minMatchedPairs, match_tolerance, sourceFluxField, verbose):
        """Implementation of matching sources to position reference objects

        Unlike matchObjectsToSources, this method does not check if the sources
        are suitable.

        Parameters
        ----------
        refCat : `lsst.afw.table.SimpleCatalog`
            catalog of position reference objects that overlap an exposure
        sourceCat : `lsst.afw.table.SourceCatalog`
            catalog of sources found on the exposure
        wcs : `lsst.afw.geom.SkyWcs`
            estimated WCS of exposure
        refFluxField : `str`
            field of refCat to use for flux
        numUsableSources : `int`
            number of usable sources (sources with known centroid that are not
            near the edge, but may be saturated)
        minMatchedPairs : `int`
            minimum number of matches
        match_tolerance : `lsst.meas.astrom.MatchTolerancePessimistic`
            a MatchTolerance object containing variables specifying matcher
            tolerances and state from possible previous runs.
        sourceFluxField : `str`
            Name of the flux field in the source catalog.
        verbose : `bool`
            Set true to print diagnostic information to std::cout

        Returns
        -------
        result :
            Results struct with components:

            - ``matches`` : a list the matches found
              (`list` of `lsst.afw.table.ReferenceMatch`).
            - ``match_tolerance`` : MatchTolerance containing updated values from
              this fit iteration (`lsst.meas.astrom.MatchTolerancePessimistic`)
        """

        # Load the source and reference catalog as spherical points
        # in numpy array. We do this rather than relying on internal
        # lsst C objects for simplicity and because we require
        # objects contiguous in memory. We need to do these slightly
        # differently for the reference and source cats as they are
        # different catalog objects with different fields.
        src_array = np.empty((len(sourceCat), 4), dtype=np.float64)
        for src_idx, srcObj in enumerate(sourceCat):
            coord = wcs.pixelToSky(srcObj.getCentroid())
            theta = np.pi / 2 - coord.getLatitude().asRadians()
            phi = coord.getLongitude().asRadians()
            flux = srcObj[sourceFluxField]
            src_array[src_idx, :] = \
                self._latlong_flux_to_xyz_mag(theta, phi, flux)

        if match_tolerance.PPMbObj is None or \
           match_tolerance.autoMaxMatchDist is None:
            # The reference catalog is fixed per AstrometryTask so we only
            # create the data needed if this is the first step in the match
            # fit cycle.
            ref_array = np.empty((len(refCat), 4), dtype=np.float64)
            for ref_idx, refObj in enumerate(refCat):
                theta = np.pi / 2 - refObj.getDec().asRadians()
                phi = refObj.getRa().asRadians()
                flux = refObj[refFluxField]
                ref_array[ref_idx, :] = \
                    self._latlong_flux_to_xyz_mag(theta, phi, flux)
            # Create our matcher object.
            match_tolerance.PPMbObj = PessimisticPatternMatcherB(
                ref_array[:, :3], self.log)
            self.log.debug("Computing source statistics...")
            maxMatchDistArcSecSrc = self._get_pair_pattern_statistics(
                src_array)
            self.log.debug("Computing reference statistics...")
            maxMatchDistArcSecRef = self._get_pair_pattern_statistics(
                ref_array)
            maxMatchDistArcSec = np.max((
                self.config.minMatchDistPixels *
                wcs.getPixelScale().asArcseconds(),
                np.min((maxMatchDistArcSecSrc,
                        maxMatchDistArcSecRef))))
            match_tolerance.autoMaxMatchDist = geom.Angle(
                maxMatchDistArcSec, geom.arcseconds)

        # Set configurable defaults when we encounter None type or set
        # state based on previous run of AstrometryTask._matchAndFitWcs.
        if match_tolerance.maxShift is None:
            maxShiftArcseconds = (self.config.maxOffsetPix *
                                  wcs.getPixelScale().asArcseconds())
        else:
            # We don't want to clamp down too hard on the allowed shift so
            # we test that the smallest we ever allow is the pixel scale.
            maxShiftArcseconds = np.max(
                (match_tolerance.maxShift.asArcseconds(),
                 self.config.minMatchDistPixels *
                 wcs.getPixelScale().asArcseconds()))

        # If our tolerances are not set from a previous run, estimate a
        # starting tolerance guess from the statistics of patterns we can
        # create on both the source and reference catalog. We use the smaller
        # of the two.
        if match_tolerance.maxMatchDist is None:
            match_tolerance.maxMatchDist = match_tolerance.autoMaxMatchDist
        else:
            maxMatchDistArcSec = np.max(
                (self.config.minMatchDistPixels *
                 wcs.getPixelScale().asArcseconds(),
                 np.min((match_tolerance.maxMatchDist.asArcseconds(),
                         match_tolerance.autoMaxMatchDist.asArcseconds()))))

        # Make sure the data we are considering is dense enough to require
        # the consensus mode of the matcher. If not default to Optimistic
        # pattern matcher behavior. We enforce pessimistic mode if the
        # reference cat is sufficiently large, avoiding false positives.
        numConsensus = self.config.numPatternConsensus
        if len(refCat) < self.config.numRefRequireConsensus:
            minObjectsForConsensus = \
                self.config.numBrightStars + \
                self.config.numPointsForShapeAttempt
            if len(refCat) < minObjectsForConsensus or \
               len(sourceCat) < minObjectsForConsensus:
                numConsensus = 1

        self.log.debug("Current tol maxDist: %.4f arcsec" %
                       maxMatchDistArcSec)
        self.log.debug("Current shift: %.4f arcsec" %
                       maxShiftArcseconds)

        match_found = False
        # Start the iteration over our tolerances.
        for soften_dist in range(self.config.matcherIterations):
            if soften_dist == 0 and \
               match_tolerance.lastMatchedPattern is not None:
                # If we are on the first, most stringent tolerance,
                # and have already found a match, the matcher should behave
                # like an optimistic pattern matcher. Exiting at the first
                # match.
                run_n_consent = 1
            else:
                # If we fail or this is the first match attempt, set the
                # pattern consensus to the specified config value.
                run_n_consent = numConsensus
            # We double the match dist tolerance each round and add 1 to the
            # to the number of candidate spokes to check.
            matcher_struct = match_tolerance.PPMbObj.match(
                source_array=src_array,
                n_check=self.config.numPointsForShapeAttempt,
                n_match=self.config.numPointsForShape,
                n_agree=run_n_consent,
                max_n_patterns=self.config.numBrightStars,
                max_shift=maxShiftArcseconds,
                max_rotation=self.config.maxRotationDeg,
                max_dist=maxMatchDistArcSec * 2. ** soften_dist,
                min_matches=minMatchedPairs,
                pattern_skip_array=np.array(
                    match_tolerance.failedPatternList)
            )

            if soften_dist == 0 and \
               len(matcher_struct.match_ids) == 0 and \
               match_tolerance.lastMatchedPattern is not None:
                # If we found a pattern on a previous match-fit iteration and
                # can't find an optimistic match on our first try with the
                # tolerances as found in the previous match-fit,
                # the match we found in the last iteration was likely bad. We
                # append the bad match's index to the a list of
                # patterns/matches to skip on subsequent iterations.
                match_tolerance.failedPatternList.append(
                    match_tolerance.lastMatchedPattern)
                match_tolerance.lastMatchedPattern = None
                maxShiftArcseconds = \
                    self.config.maxOffsetPix * wcs.getPixelScale().asArcseconds()
            elif len(matcher_struct.match_ids) > 0:
                # Match found, save a bit a state regarding this pattern
                # in the match tolerance class object and exit.
                match_tolerance.maxShift = \
                    matcher_struct.shift * geom.arcseconds
                match_tolerance.lastMatchedPattern = \
                    matcher_struct.pattern_idx
                match_found = True
                break

        # If we didn't find a match, exit early.
        if not match_found:
            return pipeBase.Struct(
                matches=[],
                match_tolerance=match_tolerance,
            )

        # The matcher returns all the nearest neighbors that agree between
        # the reference and source catalog. For the current astrometric solver
        # we need to remove as many false positives as possible before sending
        # the matches off to the solver. The low value of 100 and high value of
        # 2 are the low number of sigma and high respectively. The exact values
        # were found after testing on data of various reference/source
        # densities and astrometric distortion quality, specifically the
        # visits:  HSC (3358), DECam (406285, 410827),
        # CFHT (793169, 896070, 980526).
        distances_arcsec = np.degrees(matcher_struct.distances_rad) * 3600
        dist_cut_arcsec = np.max(
            (np.degrees(matcher_struct.max_dist_rad) * 3600,
             self.config.minMatchDistPixels * wcs.getPixelScale().asArcseconds()))

        # A match has been found, return our list of matches and
        # return.
        matches = []
        for match_id_pair, dist_arcsec in zip(matcher_struct.match_ids,
                                              distances_arcsec):
            if dist_arcsec < dist_cut_arcsec:
                match = afwTable.ReferenceMatch()
                match.first = refCat[int(match_id_pair[1])]
                match.second = sourceCat[int(match_id_pair[0])]
                # We compute the true distance along and sphere. This isn't
                # used in the WCS fitter however it is used in the unittest
                # to confirm the matches computed.
                match.distance = match.first.getCoord().separation(
                    match.second.getCoord()).asArcseconds()
                matches.append(match)

        return pipeBase.Struct(
            matches=matches,
            match_tolerance=match_tolerance,
        )
Exemple #22
0
 def test_getObservatory(self):
     observatory = Observatory(geom.Angle(-70.749417, geom.degrees),
                               geom.Angle(-30.244633, geom.degrees), 2663)
     self.assertEqual(observatory, self.visit_info.getObservatory())
Exemple #23
0
 def test_getBoresightRotAngle(self):
     # Note test eimage header has ROTANG=236.983652.  boresightRotAngle is -ROTANG.
     angle = geom.Angle(-236.983652, geom.degrees)
     self.assertAnglesAlmostEqual(angle, self.visit_info.getBoresightRotAngle())
Exemple #24
0
    def runDataRef(self, sensorRef):
        """Process one CCD
        """
        dataid = sensorRef.dataId
        self.log.info("Processing %s" % (dataid))

        wcs = self.butler.get('calexp_wcs', dataid)
        calib = self.butler.get("calexp_photoCalib", dataid)

        Flags = [
            "base_PixelFlags_flag_saturated", "base_PixelFlags_flag_cr",
            "base_PixelFlags_flag_interpolated",
            self.config.fluxType + "_flag", "base_SdssCentroid_flag",
            "base_SdssCentroid_flag_almostNoSecondDerivative",
            "base_SdssCentroid_flag_edge",
            "base_SdssCentroid_flag_noSecondDerivative",
            "base_SdssCentroid_flag_notAtMaximum",
            "base_SdssCentroid_flag_resetToPeak", "base_SdssShape_flag",
            "base_ClassificationExtendedness_flag"
        ]

        src = self.butler.get('src', dataid)
        # Apply photoCalib to src catalog
        srcCal = calib.calibrateCatalog(src).asAstropy()

        # get filter name associated to this visit
        for dataRef in self.butler.subset('src', visit=dataid['visit']):
            if dataRef.datasetExists():
                fullId = dataRef.dataId
            else:
                continue
        filt = fullId['filter']

        # select sources
        cut = np.ones_like(srcCal['id'], dtype=bool)
        for flag in Flags:
            cut &= srcCal[flag] == False
        cut &= srcCal[self.config.fluxType + '_instFlux'] > 0
        cut &= srcCal[self.config.fluxType + '_instFlux'] / srcCal[
            self.config.fluxType + '_instFluxErr'] > 5
        cut &= srcCal[self.config.fluxType + '_mag'] < self.config.magCut

        cat = srcCal[cut]['id', 'coord_ra', 'coord_dec',
                          self.config.fluxType + '_mag',
                          self.config.fluxType + '_magErr']

        #define a reference filter (not critical for what we are doing)
        f = 'lsst_' + filt + '_smeared'

        # Find the approximate celestial coordinates of the sensor's center
        centerPixel = geom.Point2D(2000., 2000.)
        centerCoord = wcs.pixelToSky(centerPixel)

        # Retrieve reference object within a 0.5 deg radius circle around the sensor's center
        radius = geom.Angle(0.5, geom.degrees)
        ref = self.refTask.loadSkyCircle(centerCoord, radius,
                                         f).refCat.copy(deep=True).asAstropy()

        # create SkyCoord catalogs for astropy matching
        cRef = SkyCoord(ra=ref['coord_ra'], dec=ref['coord_dec'])
        cSrc = SkyCoord(ra=cat['coord_ra'], dec=cat['coord_dec'])

        # match catalogs
        idx, d2d, d3d = cSrc.match_to_catalog_sky(cRef)

        # get median distance between matched sources and references
        median = np.median(d2d.milliarcsecond)
        self.log.info("Median astrometric scatter %.2f mas" % (median))

        if median > self.config.rejectCut:
            self.log.error(
                "Median astrometric scatter is too large %.2f mas astrometric fit probably failed"
                % (median))

        return median
Exemple #25
0
def summarizeVisit(butler, *, exp=None, extendedSummary=False, **kwargs):
    from astroquery.simbad import Simbad
    import astropy.units as u
    from astropy.time import Time
    from astropy.coordinates import SkyCoord, EarthLocation, AltAz

    def _airMassFromrRawMd(md):
        auxTelLocation = EarthLocation(lat=-30.244639 * u.deg,
                                       lon=-70.749417 * u.deg,
                                       height=2663 * u.m)
        time = Time(md['DATE-OBS'])
        skyLocation = SkyCoord(md['RASTART'], md['DECSTART'], unit=u.deg)
        altAz = AltAz(obstime=time, location=auxTelLocation)
        observationAltAz = skyLocation.transform_to(altAz)
        return observationAltAz.secz.value

    items = ["OBJECT", "expTime", "FILTER", "imageType"]
    obj, expTime, filterCompound, imageType = butler.queryMetadata(
        'raw', items, **kwargs)[0]
    filt, grating = filterCompound.split('~')
    rawMd = butler.get('raw_md', **kwargs)
    airmass = _airMassFromrRawMd(rawMd)

    print(f"Object name: {obj}")
    print(f"expTime:     {expTime}s")
    print(f"imageType:   {imageType}")
    print(f"Filter:      {filt}")
    print(f"Grating:     {grating}")
    print(f"Airmass:     {airmass:.3f}")

    if imageType not in ['BIAS', 'FLAT', 'DARK']:
        simbadObj = Simbad.query_object(obj)
        if simbadObj is None:
            print(f"Failed to find {obj} in Simbad.")
        else:
            assert (len(simbadObj.as_array()) == 1)
            raStr = simbadObj[0]['RA']
            decStr = simbadObj[0]['DEC']
            skyLocation = SkyCoord(raStr,
                                   decStr,
                                   unit=(u.hourangle, u.degree),
                                   frame='icrs')
            raRad, decRad = skyLocation.ra.rad, skyLocation.dec.rad
            print(f"obj RA  (str):   {raStr}")
            print(f"obj DEC (str):   {decStr}")
            print(f"obj RA  (rad):   {raRad:5f}")
            print(f"obj DEC (rad):   {decRad:5f}")
            print(f"obj RA  (deg):   {raRad*180/math.pi:5f}")
            print(f"obj DEC (deg):   {decRad*180/math.pi:5f}")

            if exp is not None:  # calc source coords from exp wcs
                ra = geom.Angle(raRad)
                dec = geom.Angle(decRad)
                targetLocation = geom.SpherePoint(ra, dec)
                pixCoord = exp.getWcs().skyToPixel(targetLocation)
                print(exp.getWcs())
                print(f"Source location: {pixCoord} using exp provided")
            else:  # try to find one, but not too hard
                datasetTypes = ['calexp', 'quickLookExp', 'postISRCCD']
                for datasetType in datasetTypes:
                    wcs = None
                    typeUsed = None
                    try:
                        wcs = butler.get(datasetType + '_wcs', **kwargs)
                        typeUsed = datasetType
                        break
                    except butlerExcept.NoResults:
                        pass
                if wcs is not None:
                    ra = geom.Angle(raRad)
                    dec = geom.Angle(decRad)
                    targetLocation = geom.SpherePoint(ra, dec)
                    pixCoord = wcs.skyToPixel(targetLocation)
                    print(wcs)
                    print(f"Source location: {pixCoord} using {typeUsed}")

    if extendedSummary:
        print('\n--- Extended Summary ---')
        ranIsr = False
        if exp is None:
            print("Running isr to compute image stats...")

            # catch all the ISR chat
            # logRedirection1 = LogRedirect(1, open(os.devnull, 'w'))
            # logRedirection2 = LogRedirect(2, open(os.devnull, 'w'))
            # import ipdb as pdb; pdb.set_trace()
            from lsst.ip.isr.isrTask import IsrTask
            isrConfig = IsrTask.ConfigClass()
            isrConfig.doLinearize = False
            isrConfig.doBias = False
            isrConfig.doFlat = False
            isrConfig.doDark = False
            isrConfig.doFringe = False
            isrConfig.doDefect = False
            isrConfig.doWrite = False
            isrTask = IsrTask(config=isrConfig)
            dataRef = butler.dataRef('raw', **kwargs)
            exp = isrTask.runDataRef(dataRef).exposure
            wcs = exp.getWcs()
            ranIsr = True
            # logRedirection1.finish()  # end re-direct
            # logRedirection2.finish()  # end re-direct

            print(wcs)
        if simbadObj and ranIsr:
            ra = geom.Angle(raRad)
            dec = geom.Angle(decRad)
            targetLocation = geom.SpherePoint(ra, dec)
            pixCoord = wcs.skyToPixel(targetLocation)
            print(
                f"Source location: {pixCoord} using postISR just-reconstructed wcs"
            )

        print(
            f'\nImage stats from {"just-constructed" if ranIsr else "provided"} exp:\n'
        )
        print(f'Image mean:   {np.mean(exp.image.array):.2f}')
        print(f'Image median: {np.median(exp.image.array):.2f}')
        print(f'Image min:    {np.min(exp.image.array):.2f}')
        print(f'Image max:    {np.max(exp.image.array):.2f}')
        # TODO: quartiles/percentiles here
        # number of masked pixels, saturated pixels

        print()
        print(f'BAD pixels:      {countPixels(exp.maskedImage, "BAD")}')
        print(f'SAT pixels:      {countPixels(exp.maskedImage, "SAT")}')
        print(f'CR pixels:       {countPixels(exp.maskedImage, "CR")}')
        print(f'INTRP pixels:    {countPixels(exp.maskedImage, "INTRP")}')
        print(f'DETECTED pixels: {countPixels(exp.maskedImage, "DETECTED")}')

        # detector = exp.getDetector()
        visitInfo = exp.getInfo().getVisitInfo()
        rotAngle = visitInfo.getBoresightRotAngle()
        boresight = visitInfo.getBoresightRaDec()
        md = butler.get('raw_md', **kwargs)

        print("\n From VisitInfo:")
        print(f"boresight: {boresight}")
        print(f"rotAngle:  {rotAngle}")
        print(f"  →        {rotAngle.asDegrees():.4f} deg")

        print("\n From raw_md:")
        print(f"ROTPA:     {md['ROTPA']} deg")
        print(f"  →        {(md['ROTPA']*math.pi/180):.6f} rad")
Exemple #26
0
def build_matched_dataset(repo,
                          dataIds,
                          matchRadius=None,
                          brightSnrMin=None,
                          brightSnrMax=None,
                          faintSnrMin=None,
                          faintSnrMax=None,
                          doApplyExternalPhotoCalib=False,
                          externalPhotoCalibName=None,
                          doApplyExternalSkyWcs=False,
                          externalSkyWcsName=None,
                          skipTEx=False,
                          skipNonSrd=False):
    """Construct a container for matched star catalogs from multple visits, with filtering,
    summary statistics, and modelling.

    `lsst.verify.Blob` instances are serializable to JSON.

    Parameters
    ----------
    repo : `str` or `lsst.daf.persistence.Butler`
        A Butler instance or a repository URL that can be used to construct
        one.
    dataIds : `list` of `dict`
        List of `butler` data IDs of Image catalogs to compare to reference.
        The `calexp` cpixel image is needed for the photometric calibration.
    matchRadius :  `lsst.geom.Angle`, optional
        Radius for matching. Default is 1 arcsecond.
    brightSnrMin : `float`, optional
        Minimum median SNR for a source to be considered bright; passed to `filterSources`.
    brightSnrMax : `float`, optional
        Maximum median SNR for a source to be considered bright; passed to `filterSources`.
    faintSnrMin : `float`, optional
        Minimum median SNR for a source to be considered faint; passed to `filterSources`.
    faintSnrMax : `float`, optional
        Maximum median SNR for a source to be considered faint; passed to `filterSources`.
    doApplyExternalPhotoCalib : bool, optional
        Apply external photoCalib to calibrate fluxes.
    externalPhotoCalibName : str, optional
        Type of external `PhotoCalib` to apply.  Currently supported are jointcal,
        fgcm, and fgcm_tract.  Must be set if "doApplyExternalPhotoCalib" is True.
    doApplyExternalSkyWcs : bool, optional
        Apply external wcs to calibrate positions.
    externalSkyWcsName : str, optional:
        Type of external `wcs` to apply.  Currently supported is jointcal.
        Must be set if "doApplyExternalSkyWcs" is True.
    skipTEx : `bool`, optional
        Skip TEx calculations (useful for older catalogs that don't have
        PsfShape measurements).
    skipNonSrd : `bool`, optional
        Skip any metrics not defined in the LSST SRD; default False.

    Attributes of returned Blob
    ----------
    filterName : `str`
        Name of filter used for all observations.
    mag : `astropy.units.Quantity`
        Mean PSF magnitudes of stars over multiple visits (magnitudes).
    magerr : `astropy.units.Quantity`
        Median 1-sigma uncertainty of PSF magnitudes over multiple visits
        (magnitudes).
    magrms : `astropy.units.Quantity`
        RMS of PSF magnitudes over multiple visits (magnitudes).
    snr : `astropy.units.Quantity`
        Median signal-to-noise ratio of PSF magnitudes over multiple visits
        (dimensionless).
    dist : `astropy.units.Quantity`
        RMS of sky coordinates of stars over multiple visits (milliarcseconds).

        *Not serialized.*
    matchesFaint : `afw.table.GroupView`
        Faint matches containing only objects that have:

        1. A PSF Flux measurement with sufficient S/N.
        2. A finite (non-nan) PSF magnitude. This separate check is largely
           to reject failed zeropoints.
        3. No flags set for bad, cosmic ray, edge or saturated.
        4. Extendedness consistent with a point source.

        *Not serialized.*
    matchesBright : `afw.table.GroupView`
        Bright matches matching a higher S/N threshold than matchesFaint.

        *Not serialized.*
    magKey
        Key for `"base_PsfFlux_mag"` in the `matchesFaint` and `matchesBright`
        catalog tables.

        *Not serialized.*

    Raises
    ------
    RuntimeError:
        Raised if "doApplyExternalPhotoCalib" is True and "externalPhotoCalibName"
        is None, or if "doApplyExternalSkyWcs" is True and "externalSkyWcsName" is
        None.
    """
    if doApplyExternalPhotoCalib and externalPhotoCalibName is None:
        raise RuntimeError(
            "Must set externalPhotoCalibName if doApplyExternalPhotoCalib is True."
        )
    if doApplyExternalSkyWcs and externalSkyWcsName is None:
        raise RuntimeError(
            "Must set externalSkyWcsName if doApplyExternalSkyWcs is True.")

    blob = Blob('MatchedMultiVisitDataset')

    if not matchRadius:
        matchRadius = geom.Angle(1, geom.arcseconds)

    # Extract single filter
    blob['filterName'] = Datum(quantity=set([dId['filter']
                                             for dId in dataIds]).pop(),
                               description='Filter name')

    # Record important configuration
    blob['doApplyExternalPhotoCalib'] = Datum(
        quantity=doApplyExternalPhotoCalib,
        description=('Whether external photometric '
                     'calibrations were used.'))
    blob['externalPhotoCalibName'] = Datum(
        quantity=externalPhotoCalibName,
        description='Name of external PhotoCalib dataset used.')
    blob['doApplyExternalSkyWcs'] = Datum(
        quantity=doApplyExternalSkyWcs,
        description='Whether external wcs calibrations were used.')
    blob['externalSkyWcsName'] = Datum(
        quantity=externalSkyWcsName,
        description='Name of external wcs dataset used.')

    # Match catalogs across visits
    blob._catalog, blob._matchedCatalog = \
        _loadAndMatchCatalogs(repo, dataIds, matchRadius,
                              doApplyExternalPhotoCalib=doApplyExternalPhotoCalib,
                              externalPhotoCalibName=externalPhotoCalibName,
                              doApplyExternalSkyWcs=doApplyExternalSkyWcs,
                              externalSkyWcsName=externalSkyWcsName,
                              skipTEx=skipTEx, skipNonSrd=skipNonSrd)

    blob.magKey = blob._matchedCatalog.schema.find("base_PsfFlux_mag").key
    # Reduce catalogs into summary statistics.
    # These are the serializable attributes of this class.
    filterResult = filterSources(
        blob._matchedCatalog,
        brightSnrMin=brightSnrMin,
        brightSnrMax=brightSnrMax,
        faintSnrMin=faintSnrMin,
        faintSnrMax=faintSnrMax,
    )
    blob['brightSnrMin'] = Datum(
        quantity=filterResult.brightSnrMin * u.Unit(''),
        label='Bright SNR Min',
        description='Minimum median SNR for a source to be considered bright')
    blob['brightSnrMax'] = Datum(
        quantity=filterResult.brightSnrMax * u.Unit(''),
        label='Bright SNR Max',
        description='Maximum median SNR for a source to be considered bright')
    summarizeSources(blob, filterResult)
    return blob
def point_source_matches(dataref,
                         ref_cat0,
                         max_offset=0.1,
                         src_columns=(),
                         ref_columns=(),
                         flux_type='base_PsfFlux'):
    """
    Match point sources between a reference catalog and the dataref
    pointing to a src catalog.

    Parameters
    ----------
    dataref: lsst.daf.persistence.butlerSubset.ButlerDataref
        Dataref pointing to the desired sensor-visit.
    ref_cat0: lsst.afw.table.SimpleCatalog
        The reference catalog.
    max_offset: float [0.1]
        Maximum offset for positional matching in arcseconds.
    src_columns: list-like [()]
        Columns from the src catalog to save in the output dataframe.
    ref_columns: list-like [()]
        Columns from the reference catalog to save in the output dataframe.
        The column names will have 'ref_' prepended.
    flux_type: str ['base_PsfFlux']
        Flux type for point sources.

    Returns
    -------
    pandas.DataFrame
    """
    flux_col = f'{flux_type}_instFlux'
    src0 = dataref.get('src')
    band = dataref.dataId['filter']

    # Apply point source selections to the source catalog.
    ext = src0.get('base_ClassificationExtendedness_value')
    model_flag = src0.get(f'{flux_type}_flag')
    model_flux = src0.get(flux_col)
    num_children = src0.get('deblend_nChild')
    src = src0.subset((ext == 0) & (model_flag == False) & (model_flux > 0)
                      & (num_children == 0))

    # Match RA, Dec with the reference catalog stars.
    ref_cat = ref_cat0.subset((ref_cat0.get('resolved') == 0))
    radius = lsst_geom.Angle(max_offset, lsst_geom.arcseconds)
    matches = afw_table.matchRaDec(ref_cat, src, radius)
    num_matches = len(matches)

    offsets = np.zeros(num_matches, dtype=np.float)
    ref_ras = np.zeros(num_matches, dtype=np.float)
    ref_decs = np.zeros(num_matches, dtype=np.float)
    ref_mags = np.zeros(num_matches, dtype=np.float)
    src_mags = np.zeros(num_matches, dtype=np.float)
    ref_data = defaultdict(list)
    src_data = defaultdict(list)
    calib = dataref.get('calexp_photoCalib')
    for i, match in enumerate(matches):
        offsets[i] = np.degrees(match.distance) * 3600 * 1000.
        ref_mags[i] = match.first[f'lsst_{band}']
        ref_ras[i] = match.first['coord_ra']
        ref_decs[i] = match.first['coord_dec']
        src_mags[i] = calib.instFluxToMagnitude(match.second[flux_col])
        for ref_col in ref_columns:
            ref_data[f'ref_{ref_col}'].append(match.first[ref_col])
        for src_col in src_columns:
            src_data[src_col].append(match.second[src_col])
    data = dict(offset=offsets,
                ref_mag=ref_mags,
                src_mag=src_mags,
                ref_ra=ref_ras,
                ref_dec=ref_decs)
    data.update(src_data)
    data.update(ref_data)
    return pd.DataFrame(data=data)
Exemple #28
0
    def run(self, in_table, calexp):
        """Compute and return the cutouts.

        Parameters
        ----------
        in_table : `astropy.QTable`
            A table containing at least the following columns: position, xspan
            and yspan.  The position should be an `astropy.SkyCoord`.  The
            xspan and yspan are the extent of the cutout in x and y in pixels.
        calexp : `lsst.afw.image.ExposureF`
            The calibrated exposure from which to extract cutouts

        Returns
        -------
        output : `lsst.pipe.base.Struct`
            A struct containing:

            * cutouts: an `lsst.meas.algorithms.Stamps` object
                       that wraps a list of masked images of the cutouts and a
                       `PropertyList` containing the metadata to be persisted
                       with the cutouts.  The exposure metadata is preserved and,
                       in addition, arrays holding the RA and Dec of each stamp
                       in degrees are added to the metadata.  Note: the origin
                       of the output stamps is `lsst.afw.image.PARENT`.
            * skipped_positions: a `list` of `lsst.geom.SpherePoint` objects for
                                 stamps that were skiped for being off the image
                                 or partially off the image

        Raises
        ------
        ValueError
            If the input catalog doesn't have the required columns,
            a ValueError is raised
        """
        cols = in_table.colnames
        if 'position' not in cols or 'xspan' not in cols or 'yspan' not in cols:
            raise ValueError(
                'Required column missing from the input table.  '
                'Required columns are "position", "xspan", and "yspan".  '
                f'The column names are: {in_table.colnames}')
        max_idx = self.config.max_cutouts
        cutout_list = []
        wcs = calexp.getWcs()
        mim = calexp.getMaskedImage()
        ras = []
        decs = []
        skipped_positions = []
        for rec in in_table[:max_idx]:
            ra = rec['position'].ra.degree
            dec = rec['position'].dec.degree
            ras.append(ra)
            decs.append(dec)
            pt = geom.SpherePoint(geom.Angle(ra, geom.degrees),
                                  geom.Angle(dec, geom.degrees))
            pix = wcs.skyToPixel(pt)
            xspan = rec['xspan'].value
            yspan = rec['yspan'].value
            # Clamp to LL corner of the LL pixel and draw extent from there
            box = geom.Box2I(
                geom.Point2I(int(pix.x - xspan / 2), int(pix.y - yspan / 2)),
                geom.Extent2I(xspan, yspan))
            if not mim.getBBox().contains(box):
                if not self.config.skip_bad:
                    raise ValueError(
                        f'Cutout bounding box is not completely contained in the image: {box}'
                    )
                else:
                    skipped_positions.append(pt)
                    continue
            sub = mim.Factory(mim, box)
            stamp = Stamp(stamp_im=sub, position=pt)
            cutout_list.append(stamp)
        metadata = calexp.getMetadata()
        metadata['RA_DEG'] = ras
        metadata['DEC_DEG'] = decs
        return pipeBase.Struct(cutouts=Stamps(cutout_list, metadata=metadata),
                               skipped_positions=skipped_positions)
Exemple #29
0
    patchBorder=100,
    ctrCoord=geom.SpherePoint(
        52.02312138728324 * geom.degrees,
        -45.37190082644628 * geom.degrees,
    ),
    vertexCoordList=[
        geom.SpherePoint(53.14552868797877 * geom.degrees,
                         -46.14411015280491 * geom.degrees),
        geom.SpherePoint(50.900633932279646 * geom.degrees,
                         -46.144109368203 * geom.degrees),
        geom.SpherePoint(50.9310808815193 * geom.degrees,
                         -44.58893982329817 * geom.degrees),
        geom.SpherePoint(53.11508391181962 * geom.degrees,
                         -44.58894056643462 * geom.degrees),
    ],
    tractOverlap=geom.Angle(0.000290888, geom.radians),
    wcs=lsst.afw.geom.skyWcs.SkyWcs.readString(
        '1 SkyWcs 0  Begin FrameSet\n#   Title = "ICRS coordinates; gnomonic projection"\n#   Naxes = 2\n#   Domain = "SKY"\n#   Epoch = 2000\n#   Lbl1 = "Right ascension"\n#   Lbl2 = "Declination"\n#   System = "ICRS"\n#   Uni1 = "hh:mm:ss.s"\n#   Uni2 = "ddd:mm:ss"\n#   Dir1 = 0\n#   Bot2 = -1.5707963267948966\n#   Top2 = 1.5707963267948966\n IsA Frame\n    Nframe = 3\n#   Base = 1\n    Currnt = 3\n    Lnk2 = 1\n    Lnk3 = 2\n    Frm1 =\n       Begin Frame\n#         Title = "2-d coordinate system"\n          Naxes = 2\n          Domain = "PIXELS"\n#         Lbl1 = "Axis 1"\n#         Lbl2 = "Axis 2"\n          Ax1 =\n             Begin Axis\n             End Axis\n          Ax2 =\n             Begin Axis\n             End Axis\n       End Frame\n    Frm2 =\n       Begin Frame\n          Title = "FITS Intermediate World Coordinates"\n          Naxes = 2\n          Domain = "IWC"\n#         Lbl1 = "Axis 1"\n#         Lbl2 = "Axis 2"\n#         Uni1 = "deg"\n#         Uni2 = "deg"\n          Ax1 =\n             Begin Axis\n                Unit = "deg"\n             End Axis\n          Ax2 =\n             Begin Axis\n                Unit = "deg"\n             End Axis\n       End Frame\n    Frm3 =\n       Begin SkyFrame\n          Ident = " "\n       IsA Object\n#         Title = "ICRS coordinates; gnomonic projection"\n          Naxes = 2\n#         Domain = "SKY"\n#         Epoch = 2000\n#         Lbl1 = "Right ascension"\n#         Lbl2 = "Declination"\n          System = "ICRS"\n          AlSys = "ICRS"\n#         Uni1 = "hh:mm:ss.s"\n#         Uni2 = "ddd:mm:ss"\n#         Dir1 = 0\n#         Bot2 = -1.5707963267948966\n#         Top2 = 1.5707963267948966\n          Ax1 =\n             Begin SkyAxis\n             End SkyAxis\n          Ax2 =\n             Begin SkyAxis\n             End SkyAxis\n       IsA Frame\n          Proj = "gnomonic"\n#         SkyTol = 0.001\n          SRefIs = "Ignored"\n          SRef1 = 0.9079747553727725\n          SRef2 = -0.79188905730982384\n       End SkyFrame\n    Map2 =\n       Begin WinMap\n          Nin = 2\n          IsSimp = 1\n       IsA Mapping\n          Sft1 = 0.77772222222222231\n          Scl1 = -5.5555555555586444e-05\n          Sft2 = -0.77772222222222231\n          Scl2 = 5.5555555555586444e-05\n       End WinMap\n    Map3 =\n       Begin CmpMap\n          Nin = 2\n       IsA Mapping\n          InvA = 1\n          MapA =\n             Begin CmpMap\n                Nin = 2\n                Invert = 1\n             IsA Mapping\n                InvA = 1\n                MapA =\n                   Begin SphMap\n                      Nin = 3\n                      Nout = 2\n                      Invert = 0\n                   IsA Mapping\n                      UntRd = 1\n                      PlrLg = 0.90797475537277261\n                   End SphMap\n                MapB =\n                   Begin CmpMap\n                      Nin = 3\n                      Nout = 2\n                   IsA Mapping\n                      InvA = 1\n                      MapA =\n                         Begin MatrixMap\n                            Nin = 3\n                            Invert = 0\n                         IsA Mapping\n                            M0 = -0.43792860044783516\n                            M1 = -0.78825913613806919\n                            M2 = 0.43228008883669983\n                            M3 = -0.56098952976750427\n                            M4 = 0.61534342792855501\n                            M5 = 0.5537537477944231\n                            M6 = -0.70250216255968545\n                            M7 = 0\n                            M8 = -0.71168160830456006\n                            IM0 = -0.43792860044783516\n                            IM1 = -0.56098952976750427\n                            IM2 = -0.70250216255968545\n                            IM3 = -0.78825913613806919\n                            IM4 = 0.61534342792855501\n                            IM5 = 0\n                            IM6 = 0.43228008883669983\n                            IM7 = 0.5537537477944231\n                            IM8 = -0.71168160830456006\n                            Form = "Full"\n                         End MatrixMap\n                      MapB =\n                         Begin CmpMap\n                            Nin = 3\n                            Nout = 2\n                         IsA Mapping\n                            MapA =\n                               Begin SphMap\n                                  Nin = 3\n                                  Nout = 2\n                                  Invert = 1\n                               IsA Mapping\n                                  UntRd = 1\n                                  PlrLg = 0\n                               End SphMap\n                            MapB =\n                               Begin CmpMap\n                                  Nin = 2\n                               IsA Mapping\n                                  MapA =\n                                     Begin WcsMap\n                                        Nin = 2\n                                        Invert = 1\n                                     IsA Mapping\n                                        Type = "TAN"\n                                     End WcsMap\n                                  MapB =\n                                     Begin ZoomMap\n                                        Nin = 2\n                                        Invert = 0\n                                     IsA Mapping\n                                        Zoom = 57.295779513082323\n                                     End ZoomMap\n                               End CmpMap\n                         End CmpMap\n                   End CmpMap\n             End CmpMap\n          MapB =\n             Begin UnitMap\n                Nin = 2\n                IsSimp = 1\n             IsA Mapping\n             End UnitMap\n       End CmpMap\n End FrameSet\n'  # noqa
    ),
)


def test_make_hexgrid_for_tract_rng():
    grid1 = make_hexgrid_for_tract(TRACTINFO,
                                   rng=np.random.RandomState(seed=100))
    grid2 = make_hexgrid_for_tract(TRACTINFO,
                                   rng=np.random.RandomState(seed=100))
    grid3 = make_hexgrid_for_tract(TRACTINFO,
                                   rng=np.random.RandomState(seed=10))

    for key in grid1.dtype.names: