示例#1
0
文件: polygon.py 项目: LionSR/cornish
    def __init__(self,
                 ast_object: starlink.Ast.Polygon = None,
                 frame: Union[ASTFrame, starlink.Ast.Frame] = None,
                 points=None,
                 fits_header=None):
        '''
		ASTPolygon is an ASTRegion that represents a polygon, a collection of vertices on a sphere in a 2D plane.

		Accepted signatures for creating an ASTPolygon:
		
		p = ASTPolygon(frame, points)
		p = ASTPolygon(fits_header, points)   # get the frame from the FITS header provided
		p = ASTPolygon(ast_object)            # where ast_object is a starlink.Ast.Polygon object

		Points may be provided as a list of coordinate points, e.g.
			[(x1, y1), (x2, y2), ... , (xn, yn)]
		or as two parallel arrays, e.g.
			[[x1, x2, x3, ..., xn], [y1, y2, y3, ..., yn]]
		
		:param ast_object: Create a new ASTPolygon from an existing :class:`starlink.Ast.Polygon` object.
		:param frame: The frame the provided points lie in, accepts either ASTFrame or starlink.Ast.Frame objects.
		:param points: Points (in degrees if frame is a SkyFrame) that describe the polygon, may be a list of pairs of points or two parallel arrays of axis points.
		:returns: Returns a new ASTPolygon object.
		'''

        if ast_object:
            if any([frame, points, fits_header]):
                raise ValueError(
                    "ASTPolygon: Cannot specify 'ast_object' along with any other parameter."
                )
            # test object
            if isinstance(ast_object, starlink.Ast.Polygon):
                super().__init__(ast_object=ast_object)
                return
            else:
                raise Exception(
                    "ASTPolygon: The 'ast_object' provided was not of type starlink.Ast.Polygon."
                )

        if points is None:
            raise Exception(
                "A list of points must be provided to create a polygon. This doesn't seem like an unreasonable request."
            )

        # Get the frame from the FITS header
        if fits_header:
            if frame is not None:
                raise ValueError(
                    "ASTPolygon: Provide the frame via the 'frame' parameter or the FITS header, but not both."
                )

            from ..channel import ASTFITSChannel
            frame_set = ASTFrameSet.fromFITSHeader(
                fits_header=fits_header
            ).baseFrame  # raises FrameNotFoundException

        if isinstance(frame, starlink.Ast.Frame):
            ast_frame = frame
        elif isinstance(frame, ASTFrame):
            ast_frame = frame.astObject
        else:
            raise Exception(
                "ASTPolygon: The supplied 'frame' object must either be a starlink.Ast.Frame or ASTFrame object."
            )

        if isinstance(frame, (starlink.Ast.SkyFrame, ASTSkyFrame)):
            points = np.deg2rad(points)

        # The problem with accepting both forms is that the case of two points is ambiguous:
        # [[x1,x2], [y1, y2]]
        # [(x1,y1), (x2, y2}]
        # I'm going to argue that two points does not a polygon make.
        if len(points) == 2 and len(points[0]) == 2:
            raise Exception(
                "There are only two points in this polygon, making the point ordering ambiguous. But is it really a polygon?"
            )

        # Internally, the starlink.Ast.Polygon constructor takes the parallel array form of points.
        # starlink.Ast.Polygon( ast_frame, points, unc=None, options=None )

        parallel_arrays = not (len(points[0]) == 2)

        if parallel_arrays:
            self.astObject = Ast.Polygon(ast_frame, points)
        else:
            if isinstance(points, np.ndarray):
                self.astObject = Ast.Polygon(ast_frame, points.T)
            else:
                # Could be a list or lists or tuples - reshape into parallel array form
                dim1 = np.zeros(len(points))
                dim2 = np.zeros(len(points))
                for idx, (x, y) in points:
                    dim1[idx] = x
                    dim2[idx] = y

                self.astObject = Ast.Polygon(ast_frame, np.array([dim1, dim2]))
示例#2
0
文件: polygon.py 项目: LionSR/cornish
    def fromPointsOnSkyFrame(radec_pairs: np.ndarray = None,
                             ra=None,
                             dec=None,
                             system: str = None,
                             skyframe: ASTSkyFrame = None,
                             expand_by=20 *
                             u.pix):  # astropy.coordinates.BaseRADecFrame
        '''
		Create an ASTPolygon from an array of points. NOTE: THIS IS SPECIFICALLY FOR SKY FRAMES.
		
		:param ra: list of RA points, must be in degrees (or :class:`astropy.units.Quantity` objects)
		:param dec: list of declination points, must be in degrees (or :class:`astropy.units.Quantity` objects)
		:param system: the coordinate system, see cornish.constants for accepted values
		:param frame: the frame the points lie in, specified as an ASTSkyFrame object
		:returns: new ASTPolygon object
		'''
        # author: David Berry
        #
        #  This method uses astConvex to find the shortest polygon enclosing a
        #  set of positions on the sky. The astConvex method determines the
        #  required polygon by examining an array of pixel values, so we first
        #  need to create a suitable pixel array. An (M,M) integer array is first
        #  created and initialised to hold zero at every pixel. A tangent plane
        #  projection is then determined that maps the smallest circle containing
        #  the specified (RA,Dec) positions onto the grid of (M,M) pixels. This
        #  projection is then used to convert each (RA,Dec) position into a pixel
        #  position and a value of 1 is poked into the array at each such pixel
        #  position. The astConvex method is then used to determine the shortest
        #  polygon that encloses all pixels that have value 1 in the array.
        #
        #  This polygon is then modified by moving each vertex 20 pixels radially
        #  away from the centre of the bounding disc used to define the extent of
        #  the pixel grid.
        #
        #  Finally, the resulting polygon is mapping from pixel coordinates to
        #  (RA,Dec).

        #  Set the required positional accuracy for the polygon vertices, given
        #  as an arc-distance in radians. The following value corresponds to 10
        #  arc-seconds. The size of the array (M) is selected to give pixels
        #  that have this size. Alternatively, specify a non-zero value for M
        #  explicitly, in which case the pixel size will be determined from M.
        ACC = 4.85E-5
        M = 0

        #  A SkyFrame describing the (RA,Dec) values.
        #skyfrm = Ast.SkyFrame( "System=FK5,Equinox=J2000,Epoch=1982.0" )

        #  The RA values (radians).
        # 		ra_list = [ 0.1646434, 0.1798973, 0.1925398, 0.2024329, 0.2053291,
        # 		            0.1796907, 0.1761278, 0.1701603, 0.1762123, 0.1689954,
        # 		            0.1725925, 0.1819018, 0.1865827, 0.19369, 0.1766037 ]
        #
        # 		#  The Dec values (radians).
        # 		dec_list = [ 0.6967545, 0.706133, 0.7176528, 0.729342, 0.740609,
        # 		             0.724532, 0.7318467, 0.7273944, 0.7225725, 0.7120513,
        # 		             0.7087136, 0.7211723, 0.7199059, 0.7268493, 0.7119532 ]

        # .. todo:: handle various input types (np.ndarray, Quantity)
        if isinstance(skyframe, (ASTSkyFrame, Ast.SkyFrame)):
            # if it's a sky frame of some kind, we will expect degrees
            ra = np.deg2rad(ra)
            dec = np.deg2rad(dec)

        ra_list = ra
        dec_list = dec

        # convert frame parameter to an Ast.Frame object
        if isinstance(skyframe, ASTFrame):
            skyframe = skyframe.astObject
        elif isinstance(skyframe, Ast.Frame):
            pass
        else:
            raise ValueError(
                f"The 'skyframe' parameter must be either an Ast.SkyFrame or ASTSkyFrame object; got {type(skyframe)}"
            )

        #  Create a PointList holding the (RA,Dec) positions.
        plist = Ast.PointList(skyframe, [ra_list, dec_list])

        #  Get the centre and radius of the circle that bounds the points (in
        #  radians).
        (centre, radius) = plist.getregiondisc()

        #  Determine the number of pixels (M) along each size of the grid that
        #  will produce pixels equal is size of ACC. If a non-zero value for M
        #  has already been set, use it.
        if M == 0:
            M = int(1 + 2.0 * radius / ACC)
        #logger.debug(f"Using grid size {M}")

        #  Create a minimal set of FITS-WCS headers that describe a TAN
        #  projection that projects the above circle into a square of M.M
        #  pixels. The reference point is the centre of the circle and is put
        #  at the centre of the square grid. Put the headers into a FitsChan.
        fc = Ast.FitsChan()
        fc["NAXIS1"] = M
        fc["NAXIS2"] = M
        fc["CRPIX1"] = 0.5 * (1 + M)
        fc["CRPIX2"] = 0.5 * (1 + M)
        fc["CRVAL1"] = centre[0] * Ast.DR2D
        fc["CRVAL2"] = centre[1] * Ast.DR2D
        fc["CDELT1"] = 2.0 * radius * Ast.DR2D / (M - 1)
        fc["CDELT2"] = 2.0 * radius * Ast.DR2D / (M - 1)
        fc["CTYPE1"] = 'RA---TAN'
        fc["CTYPE2"] = 'DEC--TAN'

        #  Re-wind the FitsChan and read the FrameSet corresponding to the above
        #  FITS headers.
        fc.clear("Card")
        wcs = fc.read()

        #  Use this FrameSet to transform all the (RA,Dec) positions into pixel
        #  coordinates within the grid.
        (x_list, y_list) = wcs.tran([ra_list, dec_list], False)

        #  Create an integer numpy array of the same shape, filled with zeros.
        ar = np.zeros(shape=(M, M), dtype=int)

        #  Poke a value 1 into the above array at each pixel position, checking
        #  each such position is inside the array.
        for (x, y) in zip(x_list, y_list):
            ix = int(round(x))
            iy = int(round(y))
            if ix >= 1 and ix <= M and iy >= 1 and iy <= M:
                ar[iy - 1, ix - 1] = 1

        #  Create a Polygon representing the convex hull that encloses the
        #  positions. This Polygon is defined in pixel coordinaates within the
        #  grid defined by the above FITS headers.
        pix_poly = Ast.convex(1, Ast.EQ, ar, [1, 1], [M, M], False)

        if expand_by.to_value(u.pix) > 0:
            #  Now expand the above polygon a bit. First get the vertex positions
            #  from the Polygon.
            (x_list, y_list) = pix_poly.getregionpoints()

            # Transform the centre position from sky to pixel coordinates.
            (x_cen, y_cen) = wcs.tran([[centre[0]], [centre[1]]], False)

            #  For each vertex, extend it's radial vector by 20 pixels. Create lists
            #  of extended x and y vertex positions. [Expanding about the centroid of
            #  the original vertices may give better results than expanding about the
            #  centre of the bounding disc in some cases].
            x_new = []
            y_new = []
            for (x, y) in zip(x_list, y_list):
                dx = x - x_cen[0]
                dy = y - y_cen[0]
                old_radius = math.sqrt(dx * dx + dy * dy)
                new_radius = old_radius + 20
                factor = new_radius / old_radius
                dx *= factor
                dy *= factor
                x_new.append(dx + x_cen[0])
                y_new.append(dy + y_cen[0])

            #  Create a new polygon in pixel coordinates using the extended vertex positions.
            big_poly = Ast.Polygon(wcs.getframe(Ast.BASE), [x_new, y_new])

            # Transform the Polygon into (RA,Dec).
            new_ast_polygon = big_poly.mapregion(wcs, skyframe)

        else:
            # Transform the Polygon into (RA,Dec)
            new_ast_polygon = pix_poly.mapregion(wcs, skyframe)

        return ASTPolygon(ast_object=new_ast_polygon)