Пример #1
0
    def std_raw(self, item, dataId):
        exposure = super(LsstSimMapper, self).std_raw(item, dataId)
        md = exposure.getMetadata()
        if md.exists("VERSION") and md.getInt("VERSION") < 16952:
            # Precess crval of WCS from date of observation to J2000
            epoch = exposure.getInfo().getVisitInfo().getDate().get(
                dafBase.DateTime.EPOCH)
            if math.isnan(epoch):
                raise RuntimeError("Date not found in raw exposure %s" %
                                   (dataId, ))
            wcs = exposure.getWcs()
            origin = wcs.getSkyOrigin()
            refCoord = afwCoord.Fk5Coord(origin.getLongitude(),
                                         origin.getLatitude(), epoch)
            newRefCoord = refCoord.precess(2000.)
            crval = afwGeom.PointD()
            crval.setX(newRefCoord.getRa().asDegrees())
            crval.setY(newRefCoord.getDec().asDegrees())
            wcs = afwImage.Wcs(crval, wcs.getPixelOrigin(), wcs.getCDMatrix())
            exposure.setWcs(wcs)

        return exposure
Пример #2
0
    def testGalactic(self):
        """Verify Galactic coordinate transforms"""

        # Try converting Sag-A to galactic and make sure we get the right answer
        # Sagittarius A (very nearly the galactic center)
        sagAKnownEqu = afwCoord.Fk5Coord("17:45:40.04","-29:00:28.1")
        sagAKnownGal = afwCoord.GalacticCoord(359.94432 * afwGeom.degrees, -0.04619 * afwGeom.degrees)
        
        sagAGal = sagAKnownEqu.toGalactic()
        s = ("Galactic (Sag-A):  (transformed) %.5f %.5f   (known) %.5f %.5f\n" %
             (sagAGal.getL().asDegrees(), sagAGal.getB().asDegrees(),
              sagAKnownGal.getL().asDegrees(), sagAKnownGal.getB().asDegrees()))
        print s
        
        # verify ... to 4 places, the accuracy of the galactic pole in Fk5
        self.assertAlmostEqual(sagAGal.getL().asDegrees(), sagAKnownGal.getL().asDegrees(), 4)
        self.assertAlmostEqual(sagAGal.getB().asDegrees(), sagAKnownGal.getB().asDegrees(), 4)

        # make sure it transforms back ... to machine precision
        self.assertAlmostEqual(sagAGal.toFk5().getRa().asDegrees(),
                               sagAKnownEqu.getRa().asDegrees(), 12)
        self.assertAlmostEqual(sagAGal.toFk5().getDec().asDegrees(),
                               sagAKnownEqu.getDec().asDegrees(), 12)
Пример #3
0
    def testEcliptic(self):
        """Verify Ecliptic Coordinate Transforms""" 
       
        # Pollux
        alpha, delta = "07:45:18.946", "28:01:34.26"
        # known ecliptic coords (example from Meeus, Astro algorithms, pg 95)
        lamb, beta = 113.215629, 6.684170

        # Try converting pollux Ra,Dec to ecliptic and check that we get the right answer
        polluxEqu = afwCoord.Fk5Coord(alpha, delta)
        polluxEcl = polluxEqu.toEcliptic()
        s = ("Ecliptic (Pollux): ",
             polluxEcl.getLambda().asDegrees(), polluxEcl.getBeta().asDegrees(), lamb, beta)
        print(s)

        # verify to precision of known values
        self.assertAlmostEqual(polluxEcl.getLambda().asDegrees(), lamb, 6)
        self.assertAlmostEqual(polluxEcl.getBeta().asDegrees(), beta, 6)

        # make sure it transforms back (machine precision)
        self.assertAlmostEqual(polluxEcl.toFk5().getRa().asDegrees(),
                               polluxEqu.getRa().asDegrees(), 13)
        self.assertAlmostEqual(polluxEcl.toFk5().getDec().asDegrees(),
                               polluxEqu.getDec().asDegrees(), 13)
Пример #4
0
    def testNames(self):
        """Test the names of the Coords (Useful with Point2D form)"""

        # verify that each coordinate type can tell you what its components are
        # called.
        radec1, known1 = afwCoord.Coord(self.ra, self.dec).getCoordNames(), \
            ["RA", "Dec"]
        radec3, known3 = afwCoord.Fk5Coord(self.ra, self.dec).getCoordNames(), \
            ["RA", "Dec"]
        radec4, known4 = \
            afwCoord.IcrsCoord(self.ra, self.dec).getCoordNames(), \
            ["RA", "Dec"]
        lb, known5 = \
            afwCoord.GalacticCoord(self.ra, self.dec).getCoordNames(), \
            ["L", "B"]
        lambet, known6 = \
            afwCoord.EclipticCoord(self.ra, self.dec).getCoordNames(), \
            ["Lambda", "Beta"]
        observatory = afwCoord.Observatory(0 * afwGeom.degrees,
                                           0 * afwGeom.degrees, 0)
        altaz = afwCoord.TopocentricCoord(self.ra, self.dec, 2000.0,
                                          observatory).getCoordNames()
        known7 = ["Az", "Alt"]

        pairs = [
            [radec1, known1],
            [radec3, known3],
            [radec4, known4],
            [lb, known5],
            [lambet, known6],
            [altaz, known7],
        ]

        for pair, known in (pairs):
            self.assertEqual(pair[0], known[0])
            self.assertEqual(pair[1], known[1])
Пример #5
0
    def testPrecess(self):
        """Test precession calculations in different coordinate systems"""

        # Try precessing in the various coordinate systems, and check the results.
        
        ### Fk5 ###
        
        # example 21.b Meeus, pg 135, for Alpha Persei ... with proper motion
        alpha0, delta0 = "2:44:11.986", "49:13:42.48"
        # proper motions per year
        dAlphaS, dDeltaAS = 0.03425, -0.0895
        # Angle/yr
        dAlpha, dDelta = (dAlphaS*15.) * afwGeom.arcseconds, (dDeltaAS) * afwGeom.arcseconds

        # get for 2028, Nov 13.19
        epoch = dafBase.DateTime(2028, 11, 13, 4, 33, 36,
                                 dafBase.DateTime.TAI).get(dafBase.DateTime.EPOCH)

        # the known final answer
        # - actually 41.547214, 49.348483 (suspect precision error in Meeus)
        alphaKnown, deltaKnown = 41.547236, 49.348488

        alphaPer0 = afwCoord.Fk5Coord(alpha0, delta0)
        alpha1 = alphaPer0.getRa() + dAlpha*(epoch - 2000.0)
        delta1 = alphaPer0.getDec() + dDelta*(epoch - 2000.0)

        alphaPer = afwCoord.Fk5Coord(alpha1, delta1).precess(epoch)

        print("Precession (Alpha-Per): %.6f %.6f   (known) %.6f %.6f" % (alphaPer.getRa().asDegrees(),
                                                                         alphaPer.getDec().asDegrees(),
                                                                         alphaKnown, deltaKnown))
        # precision 6 (with 1 digit fudged in the 'known' answers)
        self.assertAlmostEqual(alphaPer.getRa().asDegrees(), alphaKnown, 6)
        self.assertAlmostEqual(alphaPer.getDec().asDegrees(), deltaKnown, 6)

        # verify that toFk5(epoch) also works as precess
        alphaPer2 = afwCoord.Fk5Coord(alpha1, delta1).toFk5(epoch)
        self.assertEqual(alphaPer[0], alphaPer2[0])
        self.assertEqual(alphaPer[1], alphaPer2[1])

        # verify that convert(FK5, epoch) also works as precess
        alphaPer3 = afwCoord.Fk5Coord(alpha1, delta1).convert(afwCoord.FK5, epoch)
        self.assertEqual(alphaPer[0], alphaPer3[0])
        self.assertEqual(alphaPer[1], alphaPer3[1])
        
        ### Galactic ###
        
        # make sure Galactic throws an exception. As there's no epoch, there's no precess() method.
        gal = afwCoord.GalacticCoord(self.l * afwGeom.degrees, self.b * afwGeom.degrees)
        epochNew = 2010.0
        self.assertRaises(AttributeError, lambda: gal.precess(epochNew))

        
        ### Icrs ###

        # make sure Icrs throws an exception. As there's no epoch, there's no precess() method.
        icrs = afwCoord.IcrsCoord(self.l * afwGeom.degrees, self.b * afwGeom.degrees)
        epochNew = 2010.0
        self.assertRaises(AttributeError, lambda: icrs.precess(epochNew))

        
        ### Ecliptic ###
        
        # test for ecliptic with venus (example from meeus, pg 137)
        lamb2000, beta2000 = 149.48194, 1.76549
        
        # known values for -214, June 30.0
        # they're actually 118.704, 1.615, but I suspect discrepancy is a rounding error in Meeus
        # -- we use double precision, he carries 7 places only.

        # originally 214BC, but that broke the DateTime
        # It did work previously, so for the short term, I've taken the answer it
        #  returns for 1920, and used that as the 'known answer' for future tests.
        
        #year = -214 
        #lamb214bc, beta214bc = 118.704, 1.606
        year = 1920
        lamb214bc, beta214bc = 148.37119237032144, 1.7610036104147864
        
        venus2000  = afwCoord.EclipticCoord(lamb2000 * afwGeom.degrees, beta2000 * afwGeom.degrees, 2000.0)
        ep = dafBase.DateTime(year, 6, 30, 0, 0, 0,
                               dafBase.DateTime.TAI).get(dafBase.DateTime.EPOCH)
        venus214bc = venus2000.precess(ep)
        print("Precession (Ecliptic, Venus): %.4f %.4f  (known) %.4f %.4f" %
             (venus214bc.getLambda().asDegrees(), venus214bc.getBeta().asDegrees(),
              lamb214bc, beta214bc))
        
        # 3 places precision (accuracy of our controls)
        self.assertAlmostEqual(venus214bc.getLambda().asDegrees(), lamb214bc, 3)
        self.assertAlmostEqual(venus214bc.getBeta().asDegrees(), beta214bc, 3)

        # verify that toEcliptic(ep) does the same as precess(ep)
        venus214bc2 = venus2000.toEcliptic(ep)
        self.assertEqual(venus214bc[0], venus214bc2[0])
        self.assertEqual(venus214bc[1], venus214bc2[1])

        # verify that convert(ECLIPTIC, ep) is the same as precess(ep)
        venus214bc3 = venus2000.convert(afwCoord.ECLIPTIC, ep)
        self.assertEqual(venus214bc[0], venus214bc3[0])
        self.assertEqual(venus214bc[1], venus214bc3[1])
Пример #6
0
    def readFits(fileName, hdu=0, flags=0):
        """Read a ds9 region file, returning a ObjectMaskCatalog object

        This method is called "readFits" to fool the butler. The corresponding mapper entry looks like
        brightObjectMask: {
            template:      "deepCoadd/BrightObjectMasks/%(tract)d/BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg"
            python:        "lsst.obs.subaru.objectMasks.ObjectMaskCatalog"
            persistable:   "PurePythonClass"
            storage:       "FitsCatalogStorage"
        }
        and this is the only way I know to get it to read a random file type, in this case a ds9 region file.

        This method expects to find files named as BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg
        The files should be structured as follows:

        # Description of catalogue as a comment
        # CATALOG: catalog-id-string
        # TRACT: 0
        # PATCH: 5,4
        # FILTER: HSC-I

        wcs; fk5

        circle(RA, DEC, RADIUS)           # ID: 1, mag: 12.34
        box(RA, DEC, XSIZE, YSIZE, THETA) # ID: 2, mag: 23.45
        ...

        The ", mag: XX.YY" is optional

        The commented lines must be present, with the relevant fields such as tract patch and filter filled
        in. The coordinate system must be listed as above. Each patch is specified as a box or circle, with
        RA, DEC, and dimensions specified in decimal degrees (with or without an explicit "d").

        Only (axis-aligned) boxes and circles are currently supported as region definitions.
        """

        log = Log.getLogger("ObjectMaskCatalog")

        brightObjects = ObjectMaskCatalog()
        checkedWcsIsFk5 = False
        NaN = float("NaN")*afwGeom.degrees

        nFormatError = 0                      # number of format errors seen
        with open(fileName) as fd:
            for lineNo, line in enumerate(fd.readlines(), 1):
                line = line.rstrip()

                if re.search(r"^\s*#", line):
                    #
                    # Parse any line of the form "# key : value" and put them into the metadata.
                    #
                    # The medatdata values must be defined as outlined in the above docstring
                    #
                    # The value of these three keys will be checked,
                    # so get them right!
                    #
                    mat = re.search(r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line)
                    if mat:
                        key, value = mat.group(1).lower(), mat.group(2)
                        if key == "tract":
                            value = int(value)

                        brightObjects.table.getMetadata().set(key, value)

                line = re.sub(r"^\s*#.*", "", line)
                if not line:
                    continue

                if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE):
                    checkedWcsIsFk5 = True
                    continue

                # This regular expression parses the regions file for each region to be masked,
                # with the format as specified in the above docstring.
                mat = re.search(r"^\s*(box|circle)"
                                "(?:\s+|\s*\(\s*)"   # open paren or space
                                     "(\d+(?:\.\d*)?([d]*))" "(?:\s+|\s*,\s*)"
                                "([+-]?\d+(?:\.\d*)?)([d]*)" "(?:\s+|\s*,\s*)"
                                "([+-]?\d+(?:\.\d*)?)([d]*)" "(?:\s+|\s*,\s*)?"
                                "(?:([+-]?\d+(?:\.\d*)?)([d]*)"
                                    "\s*,\s*"
                                   "([+-]?\d+(?:\.\d*)?)([d]*)"
                                ")?"
                                "(?:\s*|\s*\)\s*)"   # close paren or space
                                "\s*#\s*ID:\s*(\d+)" # start comment
                                "(?:\s*,\s*mag:\s*(\d+\.\d*))?"
                                "\s*$", line)
                if mat:
                    _type, ra, raUnit, dec, decUnit, \
                        param1, param1Unit, param2, param2Unit, param3, param3Unit, \
                        _id, mag = mat.groups()

                    _id = int(_id)
                    if mag is None:
                        mag = NaN
                    else:
                        mag = float(mag)

                    ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo)
                    dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo)

                    radius = NaN
                    width = NaN
                    height = NaN
                    angle = NaN

                    if _type == "box":
                        width = convertToAngle(param1, param1Unit, "width", fileName, lineNo)
                        height = convertToAngle(param2, param2Unit, "height", fileName, lineNo)
                        angle = convertToAngle(param3, param3Unit, "angle", fileName, lineNo)

                        if angle != 0.0:
                            log.warn("Rotated boxes are not supported: \"%s\" at %s:%d" % (
                                line, fileName, lineNo))
                            nFormatError += 1
                    elif _type == "circle":
                        radius = convertToAngle(param1, param1Unit, "radius", fileName, lineNo)

                        if not (param2 is None and param3 is None):
                            log.warn("Extra parameters for circle: \"%s\" at %s:%d" % (
                                line, fileName, lineNo))
                            nFormatError += 1

                    rec = brightObjects.addNew()
                    # N.b. rec["coord"] = Coord is not supported, so we have to use the setter
                    rec["type"] = _type
                    rec["id"] = _id
                    rec["mag"] = mag
                    rec.setCoord(afwCoord.Fk5Coord(ra, dec))

                    rec["angle"] = angle
                    rec["height"] = height
                    rec["width"] = width
                    rec["radius"] = radius
                else:
                    log.warn("Unexpected line \"%s\" at %s:%d" % (line, fileName, lineNo))
                    nFormatError += 1

        if nFormatError > 0:
            raise RuntimeError("Saw %d formatting errors in %s" % (nFormatError, fileName))

        if not checkedWcsIsFk5:
            raise RuntimeError("Expected to see a line specifying an fk5 wcs in %s" % fileName)

        # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy
        brightObjects._catalog = brightObjects._catalog.copy(True)

        return brightObjects
Пример #7
0
def main(visit,
         rerun,
         out,
         ccd="0..103",
         root=None,
         point=None,
         minchips=1,
         showLabels=False,
         boresightOnly=False,
         edge=0.9):

    pcen, rad, pcenVisit = None, None, None
    if point:
        pcenArgs = map(float, point.split(":"))
        if len(pcenArgs) == 2:
            pcenVisit, rad = pcenArgs
        elif len(pcenArgs) == 3:
            cx, cy, rad = pcenArgs
            pcen = afwCoord.Fk5Coord(afwGeom.Point2D(cx, cy))
        else:
            raise ValueError(
                "Unable to parse center 'point', must be ra:dec:rad or visit:rad"
            )

    print visit, rerun
    butler = hscUtil.getButler(rerun, root=root)

    visitWidths = []
    visitHeights = []

    dataIds = visitCcdToDataId(visit, ccd)
    corners = {}
    boresights = {}
    for dataId in dataIds:
        visit, ccd = dataId['visit'], dataId['ccd']

        if boresightOnly and visit in boresights:
            continue

        print "Getting ", visit, ccd
        try:
            dataRef = hscButler.getDataRef(butler, dataId)
        except Exception, e:
            print "getDataRef() failed for ", visit, ccd, str(e)
            continue

        calfile = dataRef.get("calexp_filename", immediate=True)[0]
        noCal = False
        if not os.path.exists(calfile):
            print calfile + " missing."
            noCal = True
        else:
            # get the calexp
            calmd = dataRef.get("calexp_md", immediate=True)
            wcs = afwImage.makeWcs(calmd)
            w, h = calmd.get("NAXIS1"), calmd.get("NAXIS2")

            boresights[dataId['visit']] = [
                calmd.get("RA2000"), calmd.get("DEC2000")
            ]

            # get the corners in RA,Dec
            ll = wcs.pixelToSky(afwGeom.Point2D(1, 1)).toFk5()
            lr = wcs.pixelToSky(afwGeom.Point2D(w, 1)).toFk5()
            ul = wcs.pixelToSky(afwGeom.Point2D(1, h)).toFk5()
            ur = wcs.pixelToSky(afwGeom.Point2D(w, h)).toFk5()

        if noCal:

            rawfile = dataRef.get("raw_filename", immediate=True)[0]
            if not os.path.exists(rawfile):
                print rawfile + " missing too.  continuing."
                continue
            else:
                rawmd = dataRef.get("raw_md", immediate=True)
                raS, decS = rawmd.get("RA2000"), rawmd.get("DEC2000")
                boresights[dataId['visit']] = [raS, decS]
                #ra, dec = c.getRa().asDegrees(), c.getDec().asDegrees()
                crv1, crv2 = map(float,
                                 [rawmd.get("CRVAL1"),
                                  rawmd.get("CRVAL2")])
                crp1, crp2 = map(float,
                                 [rawmd.get("CRPIX1"),
                                  rawmd.get("CRPIX2")])

                useDelt = True
                try:
                    cdelt1, cdelt2 = map(
                        float, [rawmd.get("CDELT1"),
                                rawmd.get("CDELT1")])
                except:
                    useDelt = False
                if not useDelt:
                    cd11, cd12, cd21, cd22 = map(float, [
                        rawmd.get("CD1_1"),
                        rawmd.get("CD1_2"),
                        rawmd.get("CD2_1"),
                        rawmd.get("CD2_2")
                    ])
                    cdelt1 = 3600 * (cd11 + cd12) / 206265
                    cdelt2 = 3600 * (cd21 + cd22) / 206265

                    #cdelt1, cdelt2 = 1.0, 1.0

                nx, ny = map(int, [rawmd.get("NAXIS1"), rawmd.get("NAXIS2")])

                def trans(x, y):
                    ra = crv1 + (crp1 - x) * cdelt1
                    dec = crv2 + (crp2 - y) * cdelt2
                    return ra, dec

                ll = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(1, 1)))
                lr = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(nx, 1)))
                ul = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(1, ny)))
                ur = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(nx, ny)))

        if pcenVisit and not pcen:
            if int(pcenVisit) == int(visit):
                cx, cy = boresights[pcenVisit]
                pcen = afwCoord.Fk5Coord(cx, cy)

        if visit not in corners:
            corners[visit] = []

        corners[visit].append([ll, lr, ur, ul, ccd])
Пример #8
0
        corners[visit].append([ll, lr, ur, ul, ccd])

    ccdWidths = []
    ccdHeights = []
    for visit, cornerList in corners.items():

        for bbox in cornerList:
            ll, lr, ul, ur, ccd = bbox

            keep = True
            if pcen:

                # if only doing boresights, don't bother checking the corners
                if boresightOnly:
                    cx, cy = boresights[visit]
                    sep = pcen.angularSeparation(afwCoord.Fk5Coord(cx, cy))
                    if sep.asDegrees() > rad:
                        keep = False
                else:
                    for c in ll, lr, ul, ur:
                        sep = pcen.angularSeparation(c)
                        if sep.asDegrees() > rad:
                            keep = False

            if keep:
                ccdWidths.append(ll.getRa().asDegrees() -
                                 lr.getRa().asDegrees())
                ccdHeights.append(ll.getDec().asDegrees() -
                                  ul.getDec().asDegrees())
            else:
                if boresights:
Пример #9
0
    def readFits(fileName, hdu=0, flags=0):
        """Read a ds9 region file, returning a ObjectMaskCatalog object

        This method is called "readFits" to fool the butler. The corresponding mapper entry looks like
        brightObjectMask: {
            template:      "deepCoadd/BrightObjectMasks/%(tract)d/BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg"
            python:        "lsst.obs.subaru.objectMasks.ObjectMaskCatalog"
            persistable:   "PurePythonClass"
            storage:       "FitsCatalogStorage"
        }
        and this is the only way I know to get it to read a random file type, in this case a ds9 region file

        This method expects to find files named as BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg
        The files should be structured as follows:

        # Description of catalogue as a comment
        # CATALOG: catalog-id-string
        # TRACT: 0
        # PATCH: 5,4
        # FILTER: HSC-I

        wcs; fk5

        circle(RA, DEC, RADIUS) # ID: 1

        The commented lines must be present, with the relevant fields such as tract patch and filter filled
        in. The coordinate system must be listed as above. Each patch is specified as a circle, with an RA,
        DEC, and Radius specified in decimal degrees. Only circles are supported as region definitions
        currently.
        """

        log = pexLog.getDefaultLog().createChildLog("ObjectMaskCatalog")

        brightObjects = ObjectMaskCatalog()
        checkedWcsIsFk5 = False

        with open(fileName) as fd:
            for lineNo, line in enumerate(fd.readlines(), 1):
                line = line.rstrip()

                if re.search(r"^\s*#", line):
                    #
                    # Parse any line of the form "# key : value" and put them into the metadata.
                    #
                    # The medatdata values must be defined as outlined in the above docstring
                    #
                    # The value of these three keys will be checked,
                    # so get them right!
                    #
                    mat = re.search(
                        r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line)
                    if mat:
                        key, value = mat.group(1).lower(), mat.group(2)
                        if key == "tract":
                            value = int(value)

                        brightObjects.table.getMetadata().set(key, value)

                line = re.sub(r"^\s*#.*", "", line)
                if not line:
                    continue

                if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE):
                    checkedWcsIsFk5 = True
                    continue

                # This regular expression parses the regions file for each region to be masked,
                # with the format as specified in the above docstring.
                mat = re.search(
                    r"^\s*circle(?:\s+|\s*\(\s*)"
                    "(\d+(?:\.\d*)([d]*))"
                    "(?:\s+|\s*,\s*)"
                    "([+-]?\d+(?:\.\d*)([d]*))"
                    "(?:\s+|\s*,\s*)"
                    "(\d+(?:\.\d*))([d'\"]*)"
                    "(?:\s*|\s*\)\s*)"
                    "\s*#\s*ID:\s*(\d+)"
                    "\s*$", line)
                if mat:
                    ra, raUnit, dec, decUnit, radius, radiusUnit, _id = mat.groups(
                    )

                    _id = int(_id)
                    ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo)
                    dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo)
                    radius = convertToAngle(radius, radiusUnit, "radius",
                                            fileName, lineNo)

                    rec = brightObjects.addNew()
                    # N.b. rec["coord"] = Coord is not supported, so we have to use the setter
                    rec["id"] = _id
                    rec.setCoord(afwCoord.Fk5Coord(ra, dec))
                    rec["radius"] = radius
                else:
                    log.warn("Unexpected line \"%s\" at %s:%d" %
                             (line, fileName, lineNo))

        if not checkedWcsIsFk5:
            raise RuntimeError("Expected to see a line specifying an fk5 wcs")

        # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy
        brightObjects._catalog = brightObjects._catalog.copy(True)

        return brightObjects