Esempio n. 1
0
 def _checkNeighbors(self, qs, root, x, y, tolerance):
     i = qs.id(root, x, y)
     neighbors = qs.getNeighbors(i)
     centers = [qs.getCenter(n) for n in neighbors]
     c = qs.getCenter(i)
     for v in centers:
         self.assertTrue(geom.cartesianAngularSep(c, v) < tolerance)
Esempio n. 2
0
 def _checkNeighbors(self, qs, root, x, y, tolerance):
     i = qs.id(root, x, y)
     neighbors = qs.getNeighbors(i)
     centers = [qs.getCenter(n) for n in neighbors]
     c = qs.getCenter(i)
     for v in centers:
         self.assertTrue(geom.cartesianAngularSep(c, v) < tolerance)
Esempio n. 3
0
 def getGeometry(self, pixelId, fiducial=False):
     """Returns a spherical convex polygon corresponding to the fiducial
     or padded boundaries of the sky-pixel with the specified id.
     """
     root, ix, iy = self.coords(pixelId)
     xl = 2.0 * float(ix) / float(self.resolution) - 1.0
     xr = 2.0 * float(ix + 1) / float(self.resolution) - 1.0
     yb = 2.0 * float(iy) / float(self.resolution) - 1.0
     yt = 2.0 * float(iy + 1) / float(self.resolution) - 1.0
     c, x, y = self.center[root], self.x[root], self.y[root]
     v = list(
         map(geom.normalize,
             [(c[0] + xl * x[0] + yb * y[0], c[1] + xl * x[1] + yb * y[1],
               c[2] + xl * x[2] + yb * y[2]),
              (c[0] + xr * x[0] + yb * y[0], c[1] + xr * x[1] + yb * y[1],
               c[2] + xr * x[2] + yb * y[2]),
              (c[0] + xr * x[0] + yt * y[0], c[1] + xr * x[1] + yt * y[1],
               c[2] + xr * x[2] + yt * y[2]),
              (c[0] + xl * x[0] + yt * y[0], c[1] + xl * x[1] + yt * y[1],
               c[2] + xl * x[2] + yt * y[2])]))
     if not fiducial and self.padding > 0.0:
         # Determine angles by which edge planes must be rotated outwards
         sp = math.sin(self.padding)
         theta = [
             0.5 * geom.cartesianAngularSep(x[0], x[1])
             for x in ((v[0], v[3]), (v[1], v[0]), (v[2], v[1]), (v[3],
                                                                  v[2]))
         ]
         sina = [sp / math.cos(math.radians(x)) for x in theta]
         cosa = [math.sqrt(1.0 - x * x) for x in sina]
         # find plane equations of fiducial pixel boundaries
         xlp = self.xplane[root][ix][0]
         ybp = self.yplane[root][iy][0]
         xrp = self.xplane[root][ix + 1][0]
         ytp = self.yplane[root][iy + 1][0]
         # rotate edge planes outwards
         xlp = self.xrot[root](xlp, -sina[0], cosa[0])
         ybp = self.yrot[root](ybp, -sina[1], cosa[1])
         xrp = self.xrot[root](xrp, sina[2], cosa[2])
         ytp = self.yrot[root](ytp, sina[3], cosa[3])
         # intersect rotated planes to find vertices of padded sky-pixel
         v = list(
             map(geom.normalize, [
                 geom.cross(xlp, ybp),
                 geom.cross(xrp, ybp),
                 geom.cross(xrp, ytp),
                 geom.cross(xlp, ytp)
             ]))
     return geom.SphericalConvexPolygon(v)
Esempio n. 4
0
 def getGeometry(self, pixelId, fiducial=False):
     """Returns a spherical convex polygon corresponding to the fiducial
     or padded boundaries of the sky-pixel with the specified id.
     """
     root, ix, iy = self.coords(pixelId)
     xl = 2.0 * float(ix) / float(self.resolution) - 1.0
     xr = 2.0 * float(ix + 1) / float(self.resolution) - 1.0
     yb = 2.0 * float(iy) / float(self.resolution) - 1.0
     yt = 2.0 * float(iy + 1) / float(self.resolution) - 1.0
     c, x, y = self.center[root], self.x[root], self.y[root]
     v = map(geom.normalize, [(c[0] + xl * x[0] + yb * y[0],
                               c[1] + xl * x[1] + yb * y[1],
                               c[2] + xl * x[2] + yb * y[2]),
                              (c[0] + xr * x[0] + yb * y[0],
                               c[1] + xr * x[1] + yb * y[1],
                               c[2] + xr * x[2] + yb * y[2]),
                              (c[0] + xr * x[0] + yt * y[0],
                               c[1] + xr * x[1] + yt * y[1],
                               c[2] + xr * x[2] + yt * y[2]),
                              (c[0] + xl * x[0] + yt * y[0],
                               c[1] + xl * x[1] + yt * y[1],
                               c[2] + xl * x[2] + yt * y[2])])
     if not fiducial and self.padding > 0.0:
         # Determine angles by which edge planes must be rotated outwards
         sp = math.sin(self.padding)
         theta = map(lambda x: 0.5 * geom.cartesianAngularSep(x[0], x[1]),
                     ((v[0],v[3]), (v[1],v[0]), (v[2],v[1]), (v[3],v[2])))
         sina = map(lambda x: sp / math.cos(math.radians(x)), theta)
         cosa = map(lambda x: math.sqrt(1.0 - x * x), sina)
         # find plane equations of fiducial pixel boundaries
         xlp = self.xplane[root][ix][0]
         ybp = self.yplane[root][iy][0]
         xrp = self.xplane[root][ix + 1][0]
         ytp = self.yplane[root][iy + 1][0]
         # rotate edge planes outwards
         xlp = self.xrot[root](xlp, -sina[0], cosa[0])
         ybp = self.yrot[root](ybp, -sina[1], cosa[1])
         xrp = self.xrot[root](xrp, sina[2], cosa[2])
         ytp = self.yrot[root](ytp, sina[3], cosa[3])
         # intersect rotated planes to find vertices of padded sky-pixel
         v = map(geom.normalize, [geom.cross(xlp, ybp),
                                  geom.cross(xrp, ybp),
                                  geom.cross(xrp, ytp),
                                  geom.cross(xlp, ytp)])
     return geom.SphericalConvexPolygon(v)
Esempio n. 5
0
    def __init__(self, resolution, paddingRad):
        """Creates a new quad-sphere sky pixelisation.

        resolution: the number of pixels along the x and y axes of each root
                    pixel.

        paddingRad: the angular separation (rad) by which fiducial sky-pixels
                    are to be padded.
        """
        if not isinstance(resolution, numbers.Integral):
            raise TypeError(
                'Quad-sphere resolution parameter must be an integer')
        if resolution < 3 or resolution > 16384:
            raise RuntimeError(
                'Quad-sphere resolution must be in range [3, 16384]')
        if not isinstance(paddingRad, float):
            raise TypeError('Quad-sphere pixel padding angle must be a float')
        if paddingRad < 0.0 or paddingRad >= math.pi * 0.25:
            raise RuntimeError(
                'Quad-sphere pixel padding radius must be in range [0, 45) deg'
            )
        R = resolution
        self.resolution = R
        self.padding = paddingRad
        x, y, z = (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)
        nx, ny, nz = (-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)
        # center of each root pixel
        self.center = [z, x, y, nx, ny, nz]
        # x basis vector of each root pixel
        self.x = [ny, y, nx, ny, x, ny]
        # y basis vector of each root pixel
        self.y = [x, z, z, z, z, nx]
        # functions for rotating vectors in direction of increasing x (per root pixel)
        self.xrot = [_rotX, _rotZ, _rotZ, _rotZ, _rotZ, _rotNX]
        # functions for rotating vectors in direction of increasing y (per root pixel)
        self.yrot = [_rotY, _rotNY, _rotX, _rotY, _rotNX, _rotY]
        # Compute fiducial and splitting x/y planes for each root pixel
        self.xplane = []
        self.yplane = []
        sp = math.sin(self.padding)
        for root in range(6):
            xplanes = []
            yplanes = []
            c, x, y = self.center[root], self.x[root], self.y[root]
            for i in range(R + 1):
                xfp = self._fiducialXPlane(root, i)
                yfp = self._fiducialYPlane(root, i)
                f = 2.0 * float(i) / float(R) - 1.0
                thetaX = math.radians(0.5 * geom.cartesianAngularSep(
                    (c[0] + f * x[0] + y[0], c[1] + f * x[1] + y[1],
                     c[2] + f * x[2] + y[2]),
                    (c[0] + f * x[0] - y[0], c[1] + f * x[1] - y[1],
                     c[2] + f * x[2] - y[2])))
                thetaY = math.radians(0.5 * geom.cartesianAngularSep(
                    (c[0] + x[0] + f * y[0], c[1] + x[1] + f * y[1],
                     c[2] + x[2] + f * y[2]),
                    (c[0] - x[0] + f * y[0], c[1] - x[1] + f * y[1],
                     c[2] - x[2] + f * y[2])))
                sinrx = sp / math.cos(thetaX)
                sinry = sp / math.cos(thetaY)
                cosrx = math.sqrt(1.0 - sinrx * sinrx)
                cosry = math.sqrt(1.0 - sinry * sinry)
                if i == 0:
                    xlp = None
                    ybp = None
                else:
                    xlp = self.xrot[root](xfp, sinrx, cosrx)
                    ybp = self.yrot[root](yfp, sinry, cosry)
                    xlp = (-xlp[0], -xlp[1], -xlp[2])
                    ybp = (-ybp[0], -ybp[1], -ybp[2])
                if i == R:
                    xrp = None
                    ytp = None
                else:
                    xrp = self.xrot[root](xfp, -sinrx, cosrx)
                    ytp = self.yrot[root](yfp, -sinry, cosry)
                xplanes.append((xfp, xlp, xrp))
                yplanes.append((yfp, ybp, ytp))
            self.xplane.append(xplanes)
            self.yplane.append(yplanes)
        # Corner pixel neighbors.
        self.cornerNeighbors = (
            # root pixel 0
            (
                # x, y = 0, 0
                (self.id(0, 1, 0), self.id(0, 1, 1), self.id(0, 0, 1),
                 self.id(2, R - 1, R - 1), self.id(2, R - 2, R - 1),
                 self.id(3, 0, R - 1), self.id(3, 1, R - 1)),
                # x, y = 0, R-1
                (self.id(0, 1, R - 1), self.id(0, 1,
                                               R - 2), self.id(0, 0, R - 2),
                 self.id(1, R - 1, R - 1), self.id(1, R - 2, R - 1),
                 self.id(2, 0, R - 1), self.id(2, 1, R - 1)),
                # x, y = R-1, 0
                (self.id(0, R - 2, 0), self.id(0, R - 2,
                                               1), self.id(0, R - 1, 1),
                 self.id(3, R - 2, R - 1), self.id(3, R - 1, R - 1),
                 self.id(4, 0, R - 1), self.id(4, 1, R - 1)),
                # x, y = R-1, R-1
                (self.id(0, R - 2, R - 1), self.id(0, R - 2, R - 2),
                 self.id(0, R - 1,
                         R - 2), self.id(1, 0, R - 1), self.id(1, 1, R - 1),
                 self.id(4, R - 2, R - 1), self.id(4, R - 1, R - 1))),
            # root pixel 1
            (
                # x, y = 0, 0
                (self.id(1, 1, 0), self.id(1, 1, 1), self.id(1, 0, 1),
                 self.id(4, R - 1, 0), self.id(4, R - 1, 1),
                 self.id(5, R - 2, 0), self.id(5, R - 1, 0)),
                # x, y = 0, R-1
                (self.id(1, 1, R - 1), self.id(1, 1,
                                               R - 2), self.id(1, 0, R - 2),
                 self.id(4, R - 1, R - 2), self.id(4, R - 1, R - 1),
                 self.id(0, R - 2, R - 1), self.id(0, R - 1, R - 1)),
                # x, y = R-1, 0
                (self.id(1, R - 2, 0), self.id(1, R - 2, 1),
                 self.id(1, R - 1, 1), self.id(2, 0, 0), self.id(2, 0, 1),
                 self.id(5, 0, 0), self.id(5, 1, 0)),
                # x, y = R-1, R-1
                (self.id(1, R - 2, R - 1), self.id(1, R - 2, R - 2),
                 self.id(1, R - 1, R - 2), self.id(2, 0, R - 2),
                 self.id(2, 0, R - 1), self.id(0, 0,
                                               R - 1), self.id(0, 1, R - 1))),
            # root pixel 2
            (
                # x, y = 0, 0
                (self.id(2, 1, 0), self.id(2, 1, 1), self.id(2, 0, 1),
                 self.id(1, R - 1,
                         0), self.id(1, R - 1,
                                     1), self.id(5, 0, 0), self.id(5, 0, 1)),
                # x, y = 0, R-1
                (self.id(2, 1, R - 1), self.id(2, 1,
                                               R - 2), self.id(2, 0, R - 2),
                 self.id(1, R - 1, R - 2), self.id(1, R - 1, R - 1),
                 self.id(0, 0, R - 2), self.id(0, 0, R - 1)),
                # x, y = R-1, 0
                (self.id(2, R - 2, 0), self.id(2, R - 2, 1),
                 self.id(2, R - 1, 1), self.id(3, 0, 0), self.id(3, 0, 1),
                 self.id(5, 0, R - 2), self.id(5, 0, R - 1)),
                # x, y = R-1, R-1
                (self.id(2, R - 2, R - 1), self.id(2, R - 2, R - 2),
                 self.id(2, R - 1, R - 2), self.id(3, 0, R - 2),
                 self.id(3, 0, R - 1), self.id(0, 0, 0), self.id(0, 0, 1))),
            # root pixel 3
            (
                # x, y = 0, 0
                (self.id(3, 1, 0), self.id(3, 1, 1), self.id(3, 0, 1),
                 self.id(2, R - 1, 0), self.id(2, R - 1, 1),
                 self.id(5, 0, R - 1), self.id(5, 1, R - 1)),
                # x, y = 0, R-1
                (self.id(3, 1, R - 1), self.id(3, 1, R - 2),
                 self.id(3, 0, R - 2), self.id(2, R - 1, R - 2),
                 self.id(2, R - 1, R - 1), self.id(0, 0, 0), self.id(0, 1, 0)),
                # x, y = R-1, 0
                (self.id(3, R - 2, 0), self.id(3, R - 2, 1),
                 self.id(3, R - 1, 1), self.id(4, 0, 0), self.id(4, 0, 1),
                 self.id(5, R - 2, R - 1), self.id(5, R - 1, R - 1)),
                # x, y = R-1, R-1
                (self.id(3, R - 2, R - 1), self.id(3, R - 2, R - 2),
                 self.id(3, R - 1, R - 2), self.id(4, 0, R - 2),
                 self.id(4, 0, R - 1), self.id(0, R - 2,
                                               0), self.id(0, R - 1, 0))),
            # root pixel 4
            (
                # x, y = 0, 0
                (self.id(4, 1, 0), self.id(4, 1, 1), self.id(4, 0, 1),
                 self.id(3, R - 1, 0), self.id(3, R - 1, 1),
                 self.id(5, R - 1, R - 2), self.id(5, R - 1, R - 1)),
                # x, y = 0, R-1
                (self.id(4, 1, R - 1), self.id(4, 1, R - 2),
                 self.id(4, 0, R - 2), self.id(3, R - 1, R - 2),
                 self.id(3, R - 1, R - 1), self.id(0, R - 1,
                                                   0), self.id(0, R - 1, 1)),
                # x, y = R-1, 0
                (self.id(4, R - 2, 0), self.id(4, R - 2, 1),
                 self.id(4, R - 1, 1), self.id(1, 0, 0), self.id(1, 0, 1),
                 self.id(5, R - 1, 0), self.id(5, R - 1, 1)),
                # x, y = R-1, R-1
                (self.id(4, R - 2, R - 1), self.id(4, R - 2, R - 2),
                 self.id(4, R - 1,
                         R - 2), self.id(1, 0, R - 2), self.id(1, 0, R - 1),
                 self.id(0, R - 1, R - 2), self.id(0, R - 1, R - 1))),
            # root pixel 5
            (
                # x, y = 0, 0
                (self.id(5, 1, 0), self.id(5, 1, 1), self.id(5, 0, 1),
                 self.id(1, R - 2,
                         0), self.id(1, R - 1,
                                     0), self.id(2, 0, 0), self.id(2, 1, 0)),
                # x, y = 0, R-1
                (self.id(5, 1, R - 1), self.id(5, 1, R - 2),
                 self.id(5, 0, R - 2), self.id(2, R - 2, 0),
                 self.id(2, R - 1, 0), self.id(3, 0, 0), self.id(3, 1, 0)),
                # x, y = R-1, 0
                (self.id(5, R - 2, 0), self.id(5, R - 2, 1),
                 self.id(5, R - 1, 1), self.id(1, 0, 0), self.id(1, 1, 0),
                 self.id(4, R - 2, 0), self.id(4, R - 1, 0)),
                # x, y = R-1, R-1
                (self.id(5, R - 2, R - 1), self.id(5, R - 2, R - 2),
                 self.id(5, R - 1, R - 2), self.id(3, R - 2, 0),
                 self.id(3, R - 1, 0), self.id(4, 0, 0), self.id(4, 1, 0))),
        )
Esempio n. 6
0
def getAllSipWcs(cursor, qsp, kind):
    """Constructs a Wcs object from each entry in the Science_Ccd_Exposure
    table. SIP distortion parameters are read in from
    Science_Ccd_Exposure_Metadata. Returns a 3-tuple with the following
    contents:

    - a mapping from sky-tiles to a list of (WCS, filter) tuples for
      overlapping science CCDs
    - a list of all (WCS, filter) tuples read in
    - a bounding circle (lsst.geom.SphericalCircle) for all science CCDs.
    """
    wcsMap = {}
    wcsList = []
    offset = 0
    centers = []
    radius = 0.0
    n = 0
    blocksize = 1000
    # An alternative would be to create WCSes from FITS metadata or FITS
    # files themselves, but these aren't always available.
    while True:
        cursor.execute("""SELECT scienceCcdExposureId, filterId,
                      raDeSys, equinox, ctype1, ctype2,
                      crpix1, crpix2, crval1, crval2,
                      cd1_1, cd1_2, cd2_1, cd2_2
               FROM Science_Ccd_Exposure LIMIT %d, %d
            """ % (offset, blocksize))
        rows = cursor.fetchall()
        if len(rows) == 0:
            break
        # For each CCD in the results, build up a metadata PropertySet
        # including SIP distortion terms and use it to obtain a WCS.
        for row in rows:
            ps = dafBase.PropertySet()
            ps.setString("RADESYS", row[2])
            ps.setDouble("EQUINOX", row[3])
            ps.setString("CTYPE1", row[4])
            ps.setString("CTYPE2", row[5])
            ps.setString("CUNIT1", "deg")
            ps.setString("CUNIT2", "deg")
            ps.setDouble("CRPIX1", row[6])
            ps.setDouble("CRPIX2", row[7])
            ps.setDouble("CRVAL1", row[8])
            ps.setDouble("CRVAL2", row[9])
            ps.setDouble("CD1_1", row[10])
            ps.setDouble("CD1_2", row[11])
            ps.setDouble("CD2_1", row[12])
            ps.setDouble("CD2_2", row[13])
            if row[4].endswith("-SIP") and row[5].endswith("-SIP"):
                cursor.execute("""
                    SELECT metadataKey, intValue
                    FROM Science_Ccd_Exposure_Metadata
                    WHERE scienceCcdExposureId = %d AND
                          intValue IS NOT NULL AND
                          metadataKey RLIKE '^[AB]P?_ORDER$'
                    """ % row[0])
                for k, v in cursor.fetchall():
                    ps.setInt(k, v)
                cursor.execute("""
                    SELECT metadataKey, doubleValue
                    FROM Science_Ccd_Exposure_Metadata
                    WHERE scienceCcdExposureId = %d AND
                          doubleValue IS NOT NULL AND
                          metadataKey RLIKE '^[AB]P?_[0-9]+_[0-9]+$'
                    """ % row[0])
                for k, v in cursor.fetchall():
                    ps.setDouble(k, v)
            wcs = afwImage.makeWcs(ps)
            wcsList.append([wcs, row[1]])
            if kind == 'imsim':
                poly = qs.imageToPolygon(wcs, 4072.0, 4000.0, 0.0)
            else:
                poly = qs.imageToPolygon(wcs, 2048.0, 4610.0, 0.0)
            bc = poly.getBoundingCircle()
            radius = max(radius, bc.getRadius())
            centers.append(geom.cartesianUnitVector(bc.getCenter()))
            pix = qsp.intersect(poly)
            for skyTile in pix:
                if skyTile in wcsMap:
                    wcsMap[skyTile].append([wcs, row[1]])
                else:
                    wcsMap[skyTile] = [[wcs, row[1]]]
        n = offset + len(rows)
        print "... %d" % n
        offset += blocksize
    x, y, z = 0.0, 0.0, 0.0
    for v in centers:
        x += v[0]
        y += v[1]
        z += v[2]
    cen = geom.normalize((x, y, z))
    r = max(geom.cartesianAngularSep(cen, v) for v in centers) + 2.0 * radius
    return wcsMap, wcsList, geom.SphericalCircle(cen, r)
Esempio n. 7
0
    def __init__(self, resolution, paddingRad):
        """Creates a new quad-sphere sky pixelisation.

        resolution: the number of pixels along the x and y axes of each root
                    pixel.

        paddingRad: the angular separation (rad) by which fiducial sky-pixels
                    are to be padded.
        """
        if not isinstance(resolution, (int, long)):
            raise TypeError(
                'Quad-sphere resolution parameter must be an int or long')
        if resolution < 3 or resolution > 16384:
            raise RuntimeError(
                'Quad-sphere resolution must be in range [3, 16384]')
        if not isinstance(paddingRad, float):
            raise TypeError(
                'Quad-sphere pixel padding angle must be a float')
        if paddingRad < 0.0 or paddingRad >= math.pi * 0.25:
            raise RuntimeError(
                'Quad-sphere pixel padding radius must be in range [0, 45) deg')
        R = resolution
        self.resolution = R
        self.padding = paddingRad
        x, y, z = (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)
        nx, ny, nz = (-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)
        # center of each root pixel
        self.center = [z, x, y, nx, ny, nz]
        # x basis vector of each root pixel
        self.x = [ny, y, nx, ny, x, ny]
        # y basis vector of each root pixel
        self.y = [x, z, z, z, z, nx]
        # functions for rotating vectors in direction of increasing x (per root pixel)
        self.xrot = [_rotX, _rotZ, _rotZ, _rotZ, _rotZ, _rotNX]
        # functions for rotating vectors in direction of increasing y (per root pixel)
        self.yrot = [_rotY, _rotNY, _rotX, _rotY, _rotNX, _rotY]
        # Compute fiducial and splitting x/y planes for each root pixel
        self.xplane = []
        self.yplane = []
        sp = math.sin(self.padding)
        for root in xrange(6):
            xplanes = []
            yplanes = []
            c, x, y = self.center[root], self.x[root], self.y[root]
            for i in xrange(R + 1):
                xfp = self._fiducialXPlane(root, i)
                yfp = self._fiducialYPlane(root, i)
                f = 2.0 * float(i) / float(R) - 1.0
                thetaX = math.radians(0.5 * geom.cartesianAngularSep(
                    (c[0] + f * x[0] + y[0],
                     c[1] + f * x[1] + y[1],
                     c[2] + f * x[2] + y[2]),
                    (c[0] + f * x[0] - y[0],
                     c[1] + f * x[1] - y[1],
                     c[2] + f * x[2] - y[2])))
                thetaY = math.radians(0.5 * geom.cartesianAngularSep(
                    (c[0] + x[0] + f * y[0],
                     c[1] + x[1] + f * y[1],
                     c[2] + x[2] + f * y[2]),
                    (c[0] - x[0] + f * y[0],
                     c[1] - x[1] + f * y[1],
                     c[2] - x[2] + f * y[2])))
                sinrx = sp / math.cos(thetaX)
                sinry = sp / math.cos(thetaY)
                cosrx = math.sqrt(1.0 - sinrx * sinrx)
                cosry = math.sqrt(1.0 - sinry * sinry)
                if i == 0:
                    xlp = None
                    ybp = None
                else:
                    xlp = self.xrot[root](xfp, sinrx, cosrx)
                    ybp = self.yrot[root](yfp, sinry, cosry)
                    xlp = (-xlp[0], -xlp[1], -xlp[2])
                    ybp = (-ybp[0], -ybp[1], -ybp[2])
                if i == R:
                    xrp = None
                    ytp = None
                else:
                    xrp = self.xrot[root](xfp, -sinrx, cosrx)
                    ytp = self.yrot[root](yfp, -sinry, cosry)
                xplanes.append((xfp, xlp, xrp))
                yplanes.append((yfp, ybp, ytp))
            self.xplane.append(xplanes)
            self.yplane.append(yplanes)
        # Corner pixel neighbors.
        self.cornerNeighbors = (
            # root pixel 0
            (
                # x, y = 0, 0
                (self.id(0, 1, 0),
                 self.id(0, 1, 1),
                 self.id(0, 0, 1),
                 self.id(2, R - 1, R - 1),
                 self.id(2, R - 2, R - 1),
                 self.id(3, 0, R - 1),
                 self.id(3, 1, R - 1)),
                # x, y = 0, R-1
                (self.id(0, 1, R - 1),
                 self.id(0, 1, R - 2),
                 self.id(0, 0, R - 2),
                 self.id(1, R - 1, R - 1),
                 self.id(1, R - 2, R - 1),
                 self.id(2, 0, R - 1),
                 self.id(2, 1, R - 1)),
                # x, y = R-1, 0
                (self.id(0, R - 2, 0),
                 self.id(0, R - 2, 1),
                 self.id(0, R - 1, 1),
                 self.id(3, R - 2, R - 1),
                 self.id(3, R - 1, R - 1),
                 self.id(4, 0, R - 1),
                 self.id(4, 1, R - 1)),
                # x, y = R-1, R-1
                (self.id(0, R - 2, R - 1),
                 self.id(0, R - 2, R - 2),
                 self.id(0, R - 1, R - 2),
                 self.id(1, 0, R - 1),
                 self.id(1, 1, R - 1),
                 self.id(4, R - 2, R - 1),
                 self.id(4, R - 1, R - 1))
            ),
            # root pixel 1
            (
                # x, y = 0, 0
                (self.id(1, 1, 0),
                 self.id(1, 1, 1),
                 self.id(1, 0, 1),
                 self.id(4, R - 1, 0),
                 self.id(4, R - 1, 1),
                 self.id(5, R - 2, 0),
                 self.id(5, R - 1, 0)),
                # x, y = 0, R-1
                (self.id(1, 1, R - 1),
                 self.id(1, 1, R - 2),
                 self.id(1, 0, R - 2),
                 self.id(4, R - 1, R - 2),
                 self.id(4, R - 1, R - 1),
                 self.id(0, R - 2, R - 1),
                 self.id(0, R - 1, R - 1)),
                # x, y = R-1, 0
                (self.id(1, R - 2, 0),
                 self.id(1, R - 2, 1),
                 self.id(1, R - 1, 1),
                 self.id(2, 0, 0),
                 self.id(2, 0, 1),
                 self.id(5, 0, 0),
                 self.id(5, 1, 0)),
                # x, y = R-1, R-1
                (self.id(1, R - 2, R - 1),
                 self.id(1, R - 2, R - 2),
                 self.id(1, R - 1, R - 2),
                 self.id(2, 0, R - 2),
                 self.id(2, 0, R - 1),
                 self.id(0, 0, R - 1),
                 self.id(0, 1, R - 1))
            ),
            # root pixel 2
            (
                # x, y = 0, 0
                (self.id(2, 1, 0),
                 self.id(2, 1, 1),
                 self.id(2, 0, 1),
                 self.id(1, R - 1, 0),
                 self.id(1, R - 1, 1),
                 self.id(5, 0, 0),
                 self.id(5, 0, 1)),
                # x, y = 0, R-1
                (self.id(2, 1, R - 1),
                 self.id(2, 1, R - 2),
                 self.id(2, 0, R - 2),
                 self.id(1, R - 1, R - 2),
                 self.id(1, R - 1, R - 1),
                 self.id(0, 0, R - 2),
                 self.id(0, 0, R - 1)),
                # x, y = R-1, 0
                (self.id(2, R - 2, 0),
                 self.id(2, R - 2, 1),
                 self.id(2, R - 1, 1),
                 self.id(3, 0, 0),
                 self.id(3, 0, 1),
                 self.id(5, 0, R - 2),
                 self.id(5, 0, R - 1)),
                # x, y = R-1, R-1
                (self.id(2, R - 2, R - 1),
                 self.id(2, R - 2, R - 2),
                 self.id(2, R - 1, R - 2),
                 self.id(3, 0, R - 2),
                 self.id(3, 0, R - 1),
                 self.id(0, 0, 0),
                 self.id(0, 0, 1))
            ),
            # root pixel 3
            (
                # x, y = 0, 0
                (self.id(3, 1, 0),
                 self.id(3, 1, 1),
                 self.id(3, 0, 1),
                 self.id(2, R - 1, 0),
                 self.id(2, R - 1, 1),
                 self.id(5, 0, R - 1),
                 self.id(5, 1, R - 1)),
                # x, y = 0, R-1
                (self.id(3, 1, R - 1),
                 self.id(3, 1, R - 2),
                 self.id(3, 0, R - 2),
                 self.id(2, R - 1, R - 2),
                 self.id(2, R - 1, R - 1),
                 self.id(0, 0, 0),
                 self.id(0, 1, 0)),
                # x, y = R-1, 0
                (self.id(3, R - 2, 0),
                 self.id(3, R - 2, 1),
                 self.id(3, R - 1, 1),
                 self.id(4, 0, 0),
                 self.id(4, 0, 1),
                 self.id(5, R - 2, R - 1),
                 self.id(5, R - 1, R - 1)),
                # x, y = R-1, R-1
                (self.id(3, R - 2, R - 1),
                 self.id(3, R - 2, R - 2),
                 self.id(3, R - 1, R - 2),
                 self.id(4, 0, R - 2),
                 self.id(4, 0, R - 1),
                 self.id(0, R - 2, 0),
                 self.id(0, R - 1, 0))
            ),
            # root pixel 4
            (
                # x, y = 0, 0
                (self.id(4, 1, 0),
                 self.id(4, 1, 1),
                 self.id(4, 0, 1),
                 self.id(3, R - 1, 0),
                 self.id(3, R - 1, 1),
                 self.id(5, R - 1, R - 2),
                 self.id(5, R - 1, R - 1)),
                # x, y = 0, R-1
                (self.id(4, 1, R - 1),
                 self.id(4, 1, R - 2),
                 self.id(4, 0, R - 2),
                 self.id(3, R - 1, R - 2),
                 self.id(3, R - 1, R - 1),
                 self.id(0, R - 1, 0),
                 self.id(0, R - 1, 1)),
                # x, y = R-1, 0
                (self.id(4, R - 2, 0),
                 self.id(4, R - 2, 1),
                 self.id(4, R - 1, 1),
                 self.id(1, 0, 0),
                 self.id(1, 0, 1),
                 self.id(5, R - 1, 0),
                 self.id(5, R - 1, 1)),
                # x, y = R-1, R-1
                (self.id(4, R - 2, R - 1),
                 self.id(4, R - 2, R - 2),
                 self.id(4, R - 1, R - 2),
                 self.id(1, 0, R - 2),
                 self.id(1, 0, R - 1),
                 self.id(0, R - 1, R - 2),
                 self.id(0, R - 1, R - 1))
            ),
            # root pixel 5
            (
                # x, y = 0, 0
                (self.id(5, 1, 0),
                 self.id(5, 1, 1),
                 self.id(5, 0, 1),
                 self.id(1, R - 2, 0),
                 self.id(1, R - 1, 0),
                 self.id(2, 0, 0),
                 self.id(2, 1, 0)),
                # x, y = 0, R-1
                (self.id(5, 1, R - 1),
                 self.id(5, 1, R - 2),
                 self.id(5, 0, R - 2),
                 self.id(2, R - 2, 0),
                 self.id(2, R - 1, 0),
                 self.id(3, 0, 0),
                 self.id(3, 1, 0)),
                # x, y = R-1, 0
                (self.id(5, R - 2, 0),
                 self.id(5, R - 2, 1),
                 self.id(5, R - 1, 1),
                 self.id(1, 0, 0),
                 self.id(1, 1, 0),
                 self.id(4, R - 2, 0),
                 self.id(4, R - 1, 0)),
                # x, y = R-1, R-1
                (self.id(5, R - 2, R - 1),
                 self.id(5, R - 2, R - 2),
                 self.id(5, R - 1, R - 2),
                 self.id(3, R - 2, 0),
                 self.id(3, R - 1, 0),
                 self.id(4, 0, 0),
                 self.id(4, 1, 0))
            ),
        )
Esempio n. 8
0
def verifySkyTiles(cursor, qsp, kind, wcsMap, wcsList, inButler):
    """Verifies that the sky-tile to CCD mapping in the registry used by inButler
    is correct. Also computes statistics on the angular separation between amp
    corners pre and post WCS determination.
    """
    if kind == 'imsim':
        geomPolicy = cameraGeomUtils.getGeomPolicy(
            pexPolicy.Policy.createPolicy(
                pexPolicy.DefaultPolicyFile("obs_lsstSim", "Full_STA_geom.paf",
                                            "description")))
    else:
        geomPolicy = cameraGeomUtils.getGeomPolicy(
            pexPolicy.Policy.createPolicy(
                pexPolicy.DefaultPolicyFile("obs_cfht",
                                            "Full_Megacam_geom.paf",
                                            "megacam/description")))
    camera = cameraGeomUtils.makeCamera(geomPolicy)
    ampDiskLayout = {}
    for p in geomPolicy.get("CcdDiskLayout").getArray("Amp"):
        ampDiskLayout[p.get("serial")] = (p.get("flipLR"), p.get("flipTB"))
    actualTiles = set(wcsMap.keys())
    processedTiles = set(inButler.queryMetadata("raw", "skyTile"))
    emptyTiles = processedTiles - actualTiles
    missedTiles = actualTiles - processedTiles
    if len(emptyTiles) > 0:
        print dedent("""\
            %d tiles not overlapping any Science CCDs (calexps) were found. This can
            happen if the run didn't span all the input amps in the registry, or
            when a raw amp gets shifted off a tile after WCS determination. So this
            is normal, but note that empty sky-tiles should not contain any
            SourceAssoc output.""" % len(emptyTiles))
    for tile in emptyTiles:
        print "\t%d" % tile
    if len(missedTiles) > 0:
        print dedent("""\
            %d tiles overlapping Science CCDs (calexps) were not processed.
            This indicates the raw amp padding radius used by the registry
            creation script is too small!""" % len(missedTiles))
    for tile in missedTiles:
        print "\t%d" % tile
    print "----"
    missedAmps = set()
    for tile in actualTiles:
        tileGeom = qsp.getGeometry(tile)
        tileAmps = set()
        if kind == "imsim":
            for visit, raft, sensor, channel in inButler.queryMetadata(
                    "raw",
                    "channel", ("visit", "raft", "sensor", "channel"),
                    skyTile=tile,
                    snap=0):
                s1, comma, s2 = sensor
                c1, comma, c2 = channel
                raftNum = lsstSimRafts.index(raft)
                ccdNum = int(s1) * 3 + int(s2)
                ampNum = (int(c2) << 3) + int(c1)
                tileAmps.add((long(visit), raftNum, ccdNum, ampNum))
        else:
            for visit, ccd, amp in inButler.queryMetadata(
                    "raw", "amp", ("visit", "ccd", "amp"), skyTile=tile):
                tileAmps.add((long(visit), 0, int(ccd), int(amp)))
        for ccdData in wcsMap[tile]:
            amps = getAmps(ccdData, camera, ampDiskLayout, cursor, kind)
            for ampData in amps:
                sciCoords = [
                    ccdData[0].pixelToSky(c[0], c[1]) for c in ampData[2]
                ]
                # find amps that should have been included in tile
                ampSerial = ampData[0].getId().getSerial()
                if (tile in missedTiles
                        or not (ccdData[2], ccdData[3], ccdData[4], ampSerial)
                        in tileAmps):
                    if tileGeom.intersects(coordsToPolygon(sciCoords)):
                        print(
                            "Tile %d missing visit %s raft %s ccd %s amp %d" %
                            (tile, str(ccdData[1]), str(
                                ccdData[2]), str(ccdData[3]), ampSerial))
                        missedAmps.add((ccdData[1], ampSerial))
    print "----"
    print "Computing statistics on angular separation between"
    print "science corners and raw corners/edges..."
    nSamples = 0
    maxDist, maxEdgeDist, maxMissEdgeDist = 0.0, 0.0, 0.0
    meanDist, meanEdgeDist, meanMissEdgeDist = 0.0, 0.0, 0.0
    minDist, minEdgeDist, minMissEdgeDist = 180.0, 180.0, 180.0
    for ccdData in wcsList:
        amps = getAmps(ccdData, camera, ampDiskLayout, cursor, kind)
        for ampData in amps:
            sciCoords = [ccdData[0].pixelToSky(c[0], c[1]) for c in ampData[2]]
            rawCoords = [ampData[1].pixelToSky(c[0], c[1]) for c in ampData[3]]
            rawPoly = coordsToPolygon(rawCoords)
            v = rawPoly.getVertices()
            e = rawPoly.getEdges()
            for raw, sci in izip(rawCoords, sciCoords):
                sciPos = tuple(sci.getVector())
                dist = geom.cartesianAngularSep(tuple(raw.getVector()),
                                                tuple(sciPos))
                minDist = min(dist, minDist)
                maxDist = max(dist, maxDist)
                meanDist += dist
                edgeDist = 180.0
                for i in xrange(len(v)):
                    edgeDist = min(
                        edgeDist, geom.minEdgeSep(sciPos, e[i], v[i - 1],
                                                  v[i]))
                minEdgeDist = min(edgeDist, minEdgeDist)
                maxEdgeDist = max(edgeDist, maxEdgeDist)
                meanEdgeDist += edgeDist
                nSamples += 1
                if (ccdData[1], ampData[0].getId().getSerial()) in missedAmps:
                    minMissEdgeDist = min(edgeDist, minMissEdgeDist)
                    maxMissEdgeDist = max(edgeDist, maxMissEdgeDist)
                    meanMissEdgeDist += edgeDist
    meanDist /= nSamples
    meanEdgeDist /= nSamples
    print "----"
    print "Number of amps examined: %d" % nSamples
    print "Min  raw to science corner distance: %.6f deg" % minDist
    print "Max  raw to science corner distance: %.6f deg" % maxDist
    print "Mean raw to science corner distance: %.6f deg" % meanDist
    print "Min  min science corner to raw amp edge distance: %.6f deg" % minEdgeDist
    print "Max  min science corner to raw amp edge distance: %.6f deg" % maxEdgeDist
    print "Mean min science corner to raw amp edge distance: %.6f deg" % meanEdgeDist
    print "Number of amps missed: %d" % len(missedAmps)
    if len(missedAmps) > 0:
        meanMissEdgeDist /= len(missedAmps)
        print "Min  min science corner to missed raw amp edge distance: %.6f deg" % minMissEdgeDist
        print "Max  min science corner to missed raw amp edge distance: %.6f deg" % maxMissEdgeDist
        print "Mean min science corner to missed raw amp edge distance: %.6f deg" % meanMissEdgeDist
Esempio n. 9
0
def verifySkyTiles(cursor, qsp, kind, wcsMap, wcsList, inButler):
    """Verifies that the sky-tile to CCD mapping in the registry used by inButler
    is correct. Also computes statistics on the angular separation between amp
    corners pre and post WCS determination.
    """
    if kind == 'imsim':
        geomPolicy = cameraGeomUtils.getGeomPolicy(pexPolicy.Policy.createPolicy(
            pexPolicy.DefaultPolicyFile("obs_lsstSim", "Full_STA_geom.paf", "description")))
    else:
        geomPolicy =  cameraGeomUtils.getGeomPolicy(pexPolicy.Policy.createPolicy(
            pexPolicy.DefaultPolicyFile("obs_cfht", "Full_Megacam_geom.paf", "megacam/description")))
    camera = cameraGeomUtils.makeCamera(geomPolicy)
    ampDiskLayout = {}
    for p in geomPolicy.get("CcdDiskLayout").getArray("Amp"):
        ampDiskLayout[p.get("serial")] = (p.get("flipLR"), p.get("flipTB"))
    actualTiles = set(wcsMap.keys())
    processedTiles = set(inButler.queryMetadata("raw", "skyTile"))
    emptyTiles = processedTiles - actualTiles
    missedTiles = actualTiles - processedTiles
    if len(emptyTiles) > 0:
        print dedent("""\
            %d tiles not overlapping any Science CCDs (calexps) were found. This can
            happen if the run didn't span all the input amps in the registry, or
            when a raw amp gets shifted off a tile after WCS determination. So this
            is normal, but note that empty sky-tiles should not contain any
            SourceAssoc output.""" % len(emptyTiles))
    for tile in emptyTiles:
        print "\t%d" % tile
    if len(missedTiles) > 0:
        print dedent("""\
            %d tiles overlapping Science CCDs (calexps) were not processed.
            This indicates the raw amp padding radius used by the registry
            creation script is too small!""" % len(missedTiles))
    for tile in missedTiles:
        print "\t%d" % tile
    print "----"
    missedAmps = set()
    for tile in actualTiles:
        tileGeom = qsp.getGeometry(tile)
        tileAmps = set()
        if kind == "imsim":
            for visit, raft, sensor, channel in inButler.queryMetadata(
                    "raw", "channel", ("visit", "raft", "sensor", "channel"),
                    skyTile=tile, snap=0):
                s1, comma, s2 = sensor
                c1, comma, c2 = channel
                raftNum = lsstSimRafts.index(raft)
                ccdNum = int(s1) * 3 + int(s2)
                ampNum = (int(c2) << 3) + int(c1)
                tileAmps.add((long(visit), raftNum, ccdNum, ampNum))
        else:
            for visit, ccd, amp in inButler.queryMetadata(
                    "raw", "amp", ("visit", "ccd", "amp"), skyTile=tile):
                tileAmps.add((long(visit), 0, int(ccd), int(amp)))
        for ccdData in wcsMap[tile]:
            amps = getAmps(ccdData, camera, ampDiskLayout, cursor, kind)
            for ampData in amps:
               sciCoords = [ccdData[0].pixelToSky(c[0], c[1]) for c in ampData[2]]
               # find amps that should have been included in tile
               ampSerial = ampData[0].getId().getSerial()
               if (tile in missedTiles or not
                   (ccdData[2], ccdData[3], ccdData[4], ampSerial) in tileAmps):
                   if tileGeom.intersects(coordsToPolygon(sciCoords)):
                       print ("Tile %d missing visit %s raft %s ccd %s amp %d" %
                             (tile, str(ccdData[1]), str(ccdData[2]), str(ccdData[3]), ampSerial))
                       missedAmps.add((ccdData[1], ampSerial))
    print "----"
    print "Computing statistics on angular separation between"
    print "science corners and raw corners/edges..."
    nSamples = 0
    maxDist, maxEdgeDist, maxMissEdgeDist = 0.0, 0.0, 0.0
    meanDist, meanEdgeDist, meanMissEdgeDist = 0.0, 0.0, 0.0
    minDist, minEdgeDist, minMissEdgeDist = 180.0, 180.0, 180.0
    for ccdData in wcsList:
        amps = getAmps(ccdData, camera, ampDiskLayout, cursor, kind)
        for ampData in amps:
           sciCoords = [ccdData[0].pixelToSky(c[0], c[1]) for c in ampData[2]]
           rawCoords = [ampData[1].pixelToSky(c[0], c[1]) for c in ampData[3]]
           rawPoly = coordsToPolygon(rawCoords)
           v = rawPoly.getVertices()
           e = rawPoly.getEdges()
           for raw, sci in izip(rawCoords, sciCoords):
              sciPos = tuple(sci.getVector())
              dist = geom.cartesianAngularSep(tuple(raw.getVector()),
                                              tuple(sciPos))
              minDist = min(dist, minDist)
              maxDist = max(dist, maxDist)
              meanDist += dist
              edgeDist = 180.0
              for i in xrange(len(v)):
                  edgeDist = min(edgeDist, geom.minEdgeSep(sciPos, e[i], v[i - 1], v[i]))
              minEdgeDist = min(edgeDist, minEdgeDist)
              maxEdgeDist = max(edgeDist, maxEdgeDist)
              meanEdgeDist += edgeDist
              nSamples += 1
              if (ccdData[1], ampData[0].getId().getSerial()) in missedAmps:
                  minMissEdgeDist = min(edgeDist, minMissEdgeDist)
                  maxMissEdgeDist = max(edgeDist, maxMissEdgeDist)
                  meanMissEdgeDist += edgeDist
    meanDist /= nSamples
    meanEdgeDist /= nSamples
    print "----"
    print "Number of amps examined: %d" % nSamples
    print "Min  raw to science corner distance: %.6f deg" % minDist
    print "Max  raw to science corner distance: %.6f deg" % maxDist
    print "Mean raw to science corner distance: %.6f deg" % meanDist
    print "Min  min science corner to raw amp edge distance: %.6f deg" % minEdgeDist
    print "Max  min science corner to raw amp edge distance: %.6f deg" % maxEdgeDist
    print "Mean min science corner to raw amp edge distance: %.6f deg" % meanEdgeDist
    print "Number of amps missed: %d" % len(missedAmps)
    if len(missedAmps) > 0:
        meanMissEdgeDist /= len(missedAmps)
        print "Min  min science corner to missed raw amp edge distance: %.6f deg" % minMissEdgeDist
        print "Max  min science corner to missed raw amp edge distance: %.6f deg" % maxMissEdgeDist
        print "Mean min science corner to missed raw amp edge distance: %.6f deg" % meanMissEdgeDist
Esempio n. 10
0
def getAllSipWcs(cursor, qsp, kind):
    """Constructs a Wcs object from each entry in the Science_Ccd_Exposure
    table. SIP distortion parameters are read in from
    Science_Ccd_Exposure_Metadata. Returns a 3-tuple with the following
    contents:

    - a mapping from sky-tiles to a list of (WCS, filter) tuples for
      overlapping science CCDs
    - a list of all (WCS, filter) tuples read in
    - a bounding circle (lsst.geom.SphericalCircle) for all science CCDs.
    """
    wcsMap = {}
    wcsList = []
    offset = 0
    centers = []
    radius = 0.0
    n = 0
    blocksize = 1000
    # An alternative would be to create WCSes from FITS metadata or FITS
    # files themselves, but these aren't always available.
    while True:
        cursor.execute(
            """SELECT scienceCcdExposureId, filterId,
                      raDeSys, equinox, ctype1, ctype2,
                      crpix1, crpix2, crval1, crval2,
                      cd1_1, cd1_2, cd2_1, cd2_2
               FROM Science_Ccd_Exposure LIMIT %d, %d
            """ % (offset, blocksize))
        rows = cursor.fetchall()
        if len(rows) == 0:
            break
        # For each CCD in the results, build up a metadata PropertySet
        # including SIP distortion terms and use it to obtain a WCS.
        for row in rows:
            ps = dafBase.PropertySet()
            ps.setString("RADESYS", row[2])
            ps.setDouble("EQUINOX", row[3])
            ps.setString("CTYPE1", row[4])
            ps.setString("CTYPE2", row[5])
            ps.setString("CUNIT1", "deg")
            ps.setString("CUNIT2", "deg")
            ps.setDouble("CRPIX1", row[6])
            ps.setDouble("CRPIX2", row[7])
            ps.setDouble("CRVAL1", row[8])
            ps.setDouble("CRVAL2", row[9])
            ps.setDouble("CD1_1", row[10])
            ps.setDouble("CD1_2", row[11])
            ps.setDouble("CD2_1", row[12])
            ps.setDouble("CD2_2", row[13])
            if row[4].endswith("-SIP") and row[5].endswith("-SIP"):
                cursor.execute("""
                    SELECT metadataKey, intValue
                    FROM Science_Ccd_Exposure_Metadata
                    WHERE scienceCcdExposureId = %d AND
                          intValue IS NOT NULL AND
                          metadataKey RLIKE '^[AB]P?_ORDER$'
                    """ % row[0])
                for k, v in cursor.fetchall():
                    ps.setInt(k, v)
                cursor.execute("""
                    SELECT metadataKey, doubleValue
                    FROM Science_Ccd_Exposure_Metadata
                    WHERE scienceCcdExposureId = %d AND
                          doubleValue IS NOT NULL AND
                          metadataKey RLIKE '^[AB]P?_[0-9]+_[0-9]+$'
                    """ % row[0])
                for k, v in cursor.fetchall():
                    ps.setDouble(k, v)
            wcs = afwImage.makeWcs(ps)
            wcsList.append([wcs, row[1]])
            if kind == 'imsim':
                poly = qs.imageToPolygon(wcs, 4072.0, 4000.0, 0.0)
            else:
                poly = qs.imageToPolygon(wcs, 2048.0, 4610.0, 0.0)
            bc = poly.getBoundingCircle()
            radius = max(radius, bc.getRadius())
            centers.append(geom.cartesianUnitVector(bc.getCenter()))
            pix = qsp.intersect(poly)
            for skyTile in pix:
                if skyTile in wcsMap:
                    wcsMap[skyTile].append([wcs, row[1]])
                else:
                    wcsMap[skyTile] = [[wcs, row[1]]]
        n = offset + len(rows)
        print "... %d" % n
        offset += blocksize
    x, y, z = 0.0, 0.0, 0.0
    for v in centers:
        x += v[0]; y += v[1]; z += v[2]
    cen = geom.normalize((x, y, z))
    r = max(geom.cartesianAngularSep(cen, v) for v in centers) + 2.0*radius
    return wcsMap, wcsList, geom.SphericalCircle(cen, r)