Exemple #1
0
    def load_header(self, header, fobj=None):
        self.header = {}
        self.header.update(header.items())

        self.fix_bad_headers()

        source = []
        for key, value in header.items():
            source.append("%-8.8s= %-70.70s" % (key, repr(value)))

        # following https://gist.github.com/dsberry/4171277 to get a
        # usable WCS in Ast

        try:
            # read in the header and create the default WCS transform
            #adapter = Atl.PyFITSAdapter(hdu)
            #fitschan = Ast.FitsChan(adapter)
            fitschan = Ast.FitsChan(source)
            self.wcs = fitschan.read()
            # self.wcs is a FrameSet, with a Mapping
            #self.wcs.Report = True

            self.coordsys = choose_coord_system(self.header)

            # define a transform from this destination frame to icrs/j2000
            refframe = self.wcs.getframe(2)
            toframe = Ast.SkyFrame("System=ICRS, Equinox=J2000")
            self.icrs_trans = refframe.convert(toframe)

        except Exception as e:
            self.logger.error("Error making WCS object: %s" % (str(e)))
            self.wcs = None
Exemple #2
0
def readfitswcs(hdu, Iwc=False):
    r"""Reads an AST FrameSet from a FITS header.

       The header from the specified FITS HDU is read, and an AST FrameSet
       describing the WCS information in the header is returned. None is
       returned instead of a FrameSet if WCS information cannot be read
       from the header. A string identifying the scheme used to describe
       WCS information in the header (the encoding) is also returned.

       (frameset,encoding) = starlink.Atl.readfitswcs( hdu )

       Parameters:
          hdu: An element of the hdulist associated with a FITS file
             opened using pyfits.open(). If the entire hdulist is supplied,
             rather than an element of the hdulist, then the primary HDU
             (element zero) will be used.
          frameset: A reference to the FrameSet describing the pixel and
             world coordinate systems read from the FITS header, or "None"
             if no WCS could be read.
          encoding: Indicates how the WCS information was encoded in the
             header. For possible values, see the documentation for the
             "Encoding" attribute in SUN/211.

          Iwc: Include the Intermediate World Coordinate system in the
             returned FrameSet, to allow the addition of e.g. a distorted
             Cartesian frame between the image frame and sky.

       Example:
          >>> import pyfits
          >>> import starlink.Atl as Atl
          >>>
          >>> hdulist = pyfits.open( 'test.fit' )
          >>> (frameset,encoding) = Atl.readfitswcs( hdulist[ 3 ] )
          >>> if frameset == None:
          >>>    print( "Cannot read WCS from test.fit" )

    """

    try:
        myhdu = hdu[0]
    except TypeError:
        myhdu = hdu

    fitschan = Ast.FitsChan(PyFITSAdapter(myhdu))
    fitschan.Iwc = Iwc
    encoding = fitschan.Encoding
    frameset = fitschan.read()
    return (frameset, encoding)
Exemple #3
0
def writefitswcs(frameset, hdu, encoding="FITS-WCS"):
    r"""Write an AST FrameSet to a FITS file.

       The WCS information described by the supplied FrameSet is converted
       into a set of FITS header cards which are stored in the supplied
       HDU (all cards in the header are first removed).

       nobj = starlink.Atl.writefitswcs( frameset, hdu, encoding="FITS-WCS" )

       Parameters:
          frameset: A reference to the FrameSet to be written out to the
             FITS header.
          hdu: An element of the PyFITS hdulist associated with a FITS file.
             The header cards generated from the FrameSet are stored in the
             header associated with this HDU. All cards are first removed
             from the header. If an entire hdulist is supplied, rather than
             an element of the hdulist, then the primary HDU (element zero)
             will be used.
          encoding: Indicates how the WCS information is to be encoded in the
             header. For possible values, see the documentation for the
             "Encoding" attribute in SUN/211.
          nobj:
             Returned equal to 1 if the FrameSet was converted successfully
             to FITS headers using the requested encoding, and zero
             otherwise.
       Example:
          >>> import starlink.Atl as Atl
          >>>
          >>> (frameset,encoding) = Atl.readfitswcs( hdu1 )
          >>> if Atl.writefitswcs( frameset, hdu2, encoding="FITS-AIPS" ) == 0:
          >>>    print( "Cannot convert WCS to FITS-AIPS encoding" )

     """

    fitschan = Ast.FitsChan(None, PyFITSAdapter(hdu))
    fitschan.Encoding = encoding
    return fitschan.write(frameset)
Exemple #4
0
# This script is featured on pyast issue page:
# https://github.com/timj/starlink-pyast/issues/8
# It also constructs the file tanflip.fits, which we use in the test suite.
# PyAst natively flips the order of RA and Dec when writing this file as a TAN WCS.
# This was a kind of input that wasn't otherwise featured in our test suite, but is
# apparently allowed by the fits standard.  So I added it.

import starlink.Atl as Atl
import starlink.Ast as Ast
import astropy.io.fits as pyfits
import numpy

# http://fits.gsfc.nasa.gov/registry/tpvwcs/tpv.fits
hdu = pyfits.open('tpv.fits')[0]
fc = Ast.FitsChan(Atl.PyFITSAdapter(hdu))
wcs = fc.read()

# A random test position.  The "true" RA, Dec values are taken from ds9.
'033009.340034', '-284350.811107', 418, 78, 2859.53882
x = 418
y = 78
true_ra = (3 + 30 / 60. + 9.340034 / 3600.) * numpy.pi / 12.
true_dec = -(28 + 43 / 60. + 50.811107 / 3600.) * numpy.pi / 180.

ra1, dec1 = wcs.tran(numpy.array([[x], [y]]))
print 'Initial read of tpv.fits:'
print 'error in ra = ', (ra1 - true_ra) * 180. * 3600. / numpy.pi, 'arcsec'
print 'error in dec = ', (dec1 - true_dec) * 180. * 3600. / numpy.pi, 'arcsec'

# Now cycle through writing and reading to a file
Exemple #5
0
except (IOError):
    plot_atts = ""

#  Attempt to open an associated file holding the pixel bounds of the
#  area of the FITS array to be plotted.
try:
    fin2 = open(sys.argv[1] + ".box")
    box = [float(v) for v in fin2.read().strip().split()]
    fin2.close()
except (IOError):
    box = None

#  Read the header lines into a list, and store this list in a new
#  Ast.FitsChan, using the requested attributes to modify the
#  interpretation of the header.
fc = Ast.FitsChan(fin1.readlines(), None, fits_atts)

#  Close the header file.
fin1.close()

#  Create a FrameSet from the FITS headers in the FitsChan.
fs = fc.read()
if fs is None:
    print("Could not read WCS from headers in " + sys.argv[1] + ".head")
    sys.exit(2)

#  Exit if the header does not have exactly 2 pixel axes and 2 WCS axes.
if fs.Nin != 2 or fs.Nout != 2:
    print("The headers in " + sys.argv[1] + ".head do not describe a 2-D image")
    sys.exit(2)
Exemple #6
0
def wcsalign(hdu_in, header, outname=None, clobber=False):
    """
    Align one FITS image to a specified header

    Requires pyast.

    Parameters
    ----------
    hdu_in : `~astropy.io.fits.PrimaryHDU`
        The HDU to reproject (must have header & data)
    header : `~astropy.io.fits.Header`
        The target header to project to
    outname : str (optional)
        The filename to write to.
    clobber : bool
        Overwrite the file ``outname`` if it exists

    Returns
    -------
    The reprojected fits.PrimaryHDU

    Credits
    -------
    Written by David Berry and adapted to functional form by Adam Ginsburg
    ([email protected])
    """
    try:
        import starlink.Ast as Ast
        import starlink.Atl as Atl
    except ImportError:
        raise ImportError("starlink could not be imported, wcsalign is not "
                          "available")

    #  Create objects that will transfer FITS header cards between an AST
    #  FitsChan and the fits header describing the primary HDU of the
    #  supplied FITS file.
    adapter_in = Atl.PyFITSAdapter(hdu_in)
    hdu_ref = pyfits.PrimaryHDU(header=header)
    adapter_ref = Atl.PyFITSAdapter(hdu_ref)

    #  Create a FitsChan for each and use the above adapters to copy all the
    #  header cards into it.
    fitschan_in = Ast.FitsChan(adapter_in, adapter_in)
    fitschan_ref = Ast.FitsChan(adapter_ref, adapter_ref)

    #  Get the flavour of FITS-WCS used by the header cards currently in the
    #  input FITS file. This is so that we can use the same flavour when we
    #  write out the modified WCS.
    encoding = fitschan_in.Encoding

    #  Read WCS information from the two FitsChans. Additionally, this removes
    #  all WCS information from each FitsChan. The returned wcsinfo object
    #  is an AST FrameSet, in which the current Frame describes WCS coordinates
    #  and the base Frame describes pixel coodineates. The FrameSet includes a
    #  Mapping that specifies the transformation between the two Frames.
    wcsinfo_in = fitschan_in.read()
    wcsinfo_ref = fitschan_ref.read()

    #  Check that the input FITS header contained WCS in a form that can be
    #  understood by AST.
    if wcsinfo_in is None:
        raise ValueError(
            "Failed to read WCS information from {0}".format(hdu_in))

    #  This is restricted to 2D arrays, so check theinput FITS file has 2
    #  pixel axes (given by Nin) and 2 WCS axes (given by Nout).
    elif wcsinfo_in.Nin != 2 or wcsinfo_in.Nout != 2:
        raise ValueError("{0} is not 2-dimensional".format(hdu_in))

    #  Check the reference FITS file in the same way.
    elif wcsinfo_ref is None:
        raise ValueError(
            "Failed to read WCS information from {0}".format(hdu_ref))

    elif wcsinfo_ref.Nin != 2 or wcsinfo_ref.Nout != 2:
        raise ValueError("{0} is not 2-dimensional".format(hdu_ref))

    #  Proceed if the WCS information was read OK.

    #  Attempt to get a mapping from pixel coords in the input FITS file to
    #  pixel coords in the reference fits file, with alignment occuring by
    #  preference in the current WCS frame. Since the pixel coordinate frame
    #  will be the base frame in each Frameset, we first invert the
    #  FrameSets. This is because the Convert method aligns current Frames,
    #  not base frames.
    wcsinfo_in.invert()
    wcsinfo_ref.invert()
    alignment_fs = wcsinfo_in.convert(wcsinfo_ref)

    #  Invert them again to put them back to their original state (i.e.
    #  base frame = pixel coords, and current Frame = WCS coords).
    wcsinfo_in.invert()
    wcsinfo_ref.invert()

    #  Check alignment was possible.
    if alignment_fs is None:
        raise Exception("Cannot find a common coordinate system shared by "
                        "{0} and {1}".format(hdu_in, hdu_ref))

    else:
        #  Get the lower and upper bounds of the input image in pixel
        #  indices.  All FITS arrays by definition have lower pixel bounds of
        #  [1, 1] (unlike NDFs). Note, unlike fits AST uses FITS ordering for
        #  storing pixel axis values in an array (i.e. NAXIS1 first, NAXIS2
        #  second, etc).
        lbnd_in = [1, 1]
        ubnd_in = [fitschan_in["NAXIS1"], fitschan_in["NAXIS2"]]

        #  Find the pixel bounds of the input image within the pixel coordinate
        #  system of the reference fits file.
        (lb1, ub1, xl, xu) = alignment_fs.mapbox(lbnd_in, ubnd_in, 1)
        (lb2, ub2, xl, xu) = alignment_fs.mapbox(lbnd_in, ubnd_in, 2)

        #  Calculate the bounds of the output image.
        lbnd_out = [int(lb1), int(lb2)]
        ubnd_out = [int(ub1), int(ub2)]

        #  Unlike NDFs, FITS images cannot have an arbitrary pixel origin so
        #  we need to ensure that the bottom left corner of the input image
        #  gets mapped to pixel [1,1] in the output. To do this we, extract the
        #  mapping from the alignment FrameSet and add on a ShiftMap (a mapping
        #  that just applies a shift to each axis).
        shift = [1 - lbnd_out[0], 1 - lbnd_out[1]]

        alignment_mapping = alignment_fs.getmapping()
        shiftmap = Ast.ShiftMap(shift)
        total_map = Ast.CmpMap(alignment_mapping, shiftmap)

        #  Modify the pixel bounds of the output image to take account of this
        #  shift of origin.
        lbnd_out[0] += shift[0]
        lbnd_out[1] += shift[1]
        ubnd_out[0] += shift[0]
        ubnd_out[1] += shift[1]

        #  Get the value used to represent missing pixel values
        if "BLANK" in fitschan_in:
            badval = fitschan_in["BLANK"]
            flags = Ast.USEBAD
        else:
            badval = 0
            flags = 0

        # Resample the data array using the above mapping.
        # total_map was pixmap; is this right?
        (npix, out, out_var) = total_map.resample(lbnd_in, ubnd_in,
                                                  hdu_in.data, None,
                                                  Ast.LINEAR, None, flags,
                                                  0.05, 1000, badval, lbnd_out,
                                                  ubnd_out, lbnd_out, ubnd_out)

        #  Store the aligned data in the primary HDU, and update the NAXISi
        #  keywords to hold the number of pixels along each edge of the
        #  rotated image.
        hdu_in.data = out
        fitschan_in["NAXIS1"] = ubnd_out[0] - lbnd_out[0] + 1
        fitschan_in["NAXIS2"] = ubnd_out[1] - lbnd_out[1] + 1

    #  The WCS to store in the output is the same as the reference WCS
    #  except for the extra shift of origin. So use the above shiftmap to
    #  remap the pixel coordinate frame in the reference WCS FrameSet. We
    #  can then use this FrameSet as the output FrameSet.
    wcsinfo_ref.remapframe(Ast.BASE, shiftmap)

    #  Attempt to write the modified WCS information to the primary HDU (i.e.
    #  convert the FrameSet to a set of FITS header cards stored in the
    #  FITS file). Indicate that we want to use original flavour of FITS-WCS.
    fitschan_in.Encoding = encoding
    fitschan_in.clear('Card')

    if fitschan_in.write(wcsinfo_ref) == 0:
        raise Exception("Failed to convert the aligned WCS to Fits-WCS")

    #  If successful, force the FitsChan to copy its contents into the
    #  fits header, then write the changed data and header to the output
    #  FITS file.
    else:
        fitschan_in.writefits()

    if outname is not None:
        hdu_in.writeto(outname, clobber=clobber)

    return hdu_in
Exemple #7
0
# This script is featured on pyast issue page:
# https://github.com/timj/starlink-pyast/issues/8
# PyAst had been failing to write SIP files correctly, but they fixed this in
# v3.9.0.  We override their claim of success regardless, since they aren't
# necessarily accurate enough for our purposes (only accurate to 0.1 pixels).
# Thus, older PyAst versions work correctly in GalSim.

import starlink.Atl as Atl
import starlink.Ast as Ast
import astropy.io.fits as pyfits
import numpy

# http://fits.gsfc.nasa.gov/registry/sip/sipsample.fits
hdu = pyfits.open('sipsample.fits')[0]
fc = Ast.FitsChan(Atl.PyFITSAdapter(hdu))
wcs = fc.read()

# A random test position.  The "true" RA, Dec values are taken from ds9.
x = 242
y = 75
true_ra = (13 + 30 / 60. + 1.474154 / 3600. - 24.) * numpy.pi / 12.
true_dec = (47 + 12 / 60. + 51.794474 / 3600.) * numpy.pi / 180.

ra1, dec1 = wcs.tran(numpy.array([[x], [y]]))
print 'Initial read of sipsample.fits:'
print 'error in ra = ', (ra1 - true_ra) * 180. * 3600. / numpy.pi, 'arcsec'
print 'error in dec = ', (dec1 - true_dec) * 180. * 3600. / numpy.pi, 'arcsec'

# Now cycle through writing and reading to a file

hdu2 = pyfits.PrimaryHDU()
Exemple #8
0
#  Use pyfits to open a test files file
ffile = pyfits.open( 'starlink/ast/test/cobe.fit' )

#  Use matplotlib to plot an annotated grid of the WCS coords
Atl.plotfitswcs( matplotlib.pyplot.figure(figsize=(8,8)).add_subplot(111),
                 [ 0.1, 0.1, 0.9, 0.9 ], ffile )
matplotlib.pyplot.show()

#  Create a FitsChan telling it to use the pyfits primary hdu as the
#  external data source and sink. Note, we take the default value of
#  "True" for the "clear" property when creating the PyFITSADapater,
#  which means the PyFITS header will be cleared immediately before
#  the FitsChan.writefits() method writes to it.
adapter = Atl.PyFITSAdapter(ffile)
fc = Ast.FitsChan( adapter, adapter )

#  Read the FrameSet from the FitsChan. This will read all headers from
#  the pyfits hdu into the FitsChan, create a FrameSet from the WCS
#  headers, and remove all WCS-related headers from the FitsChan (but not
#  the pyfits primary hdu as yet).
fs = fc.read()

#  Tell the FitsChan to write out the remaining headers to its external data
#  sink.
fc.writefits()

#  Display the headers now in the pyfits primary hdu.
print()
print("The non-WCS cards in cobe.fit: ")
for v in ffile[0].header.cards:
Exemple #9
0
def rotate(infile, outfile, angle, xcen=None, ycen=None):
    #  Open the FITS file using pyfits. A list of the HDUs in the FITS file is
    #  returned.
    hdu_list = pyfits.open(infile)

    #  Create an object that will transfer FITS header cards between an AST
    #  FitsChan and the PyFITS header describing the primary HDU of the
    #  supplied FITS file.
    adapter = Atl.PyFITSAdapter(hdu_list[0])

    #  Create a FitsChan and use the above adapter to copy all the header
    #  cards into it.
    fitschan = Ast.FitsChan(adapter, adapter)

    #  Get the flavour of FITS-WCS used by the header cards currently in the
    #  FitsChan. This is so that we can use the same flavour when we write
    #  out the modified WCS.
    encoding = fitschan.Encoding

    #  Read WCS information from the FitsChan. Additionally, this removes all
    #  WCS information from the FitsChan. The returned wcsinfo object
    #  is an AST FrameSet, in which the current Frame describes WCS coordinates
    #  and the base Frame describes pixel coodineates. The FrameSet includes a
    #  Mapping that specifies the transformation between the two Frames.
    wcsinfo = fitschan.read()

    #  Check that the FITS header contained WCS in a form that can be
    #  understood by AST.
    if wcsinfo == None:
        print("Failed to read WCS information from {0}".format(infile))

    #  Rotation is restricted to 2D arrays, so check the FITS file has 2 pixel
    #  axes (given by Nin) and 2 WCS axes (given by Nout).
    elif wcsinfo.Nin != 2 or wcsinfo.Nout != 2:
        print("{0} is not 2-dimensional".format(infile))

    #  Proceed if the WCS information was read OK.
    else:

        #  Get the lower and upper bounds of the input image in pixel indices.
        #  All FITS arrays by definition have lower pixel bounds of [1,1] (unlike
        #  NDFs). Note, unlike pyfits AST uses FITS ordering for storing pixel axis
        #  values in an array (i.e. NAXIS1 first, NAXIS2 second, etc).
        lbnd_in = [1, 1]
        ubnd_in = [fitschan["NAXIS1"], fitschan["NAXIS2"]]

        #  Get the rotation angle and convert from degrees to radians.
        angle = float(angle) * Ast.DD2R

        #  Construct a MatrixMap that rotates by the required angle, converted to radians.
        sinang = math.sin(angle)
        cosang = math.cos(angle)
        pixmap = Ast.MatrixMap([[cosang, sinang], [-sinang, cosang]])

        #  If supplied, get the centre of rotation.
        if (xcen is not None) and (ycen is not None):
            #  Create aShiftMap to shift the origin and combine it with the
            #  MatrixMap, to give the desired centre of rotation (shift,
            #  rotate, then shift back again).
            shiftmap = Ast.ShiftMap([-xcen, -ycen])
            pixmap = Ast.CmpMap(shiftmap, pixmap)
            shiftmap.invert()
            pixmap = Ast.CmpMap(pixmap, shiftmap)

            # The dimensions of the output array are the same as the input array.
            lbnd_out = lbnd_in
            ubnd_out = ubnd_in

        #  If no centre of rotation was specified, we use the MatrixMap
        #  unchanged, and determine the bounds of the smallest output image that
        #  will hold the entire rotated input image. These are with respect to
        #  the pixel coordinates of the input array, and so some corners may have
        #  negative pixel coordinates.
        else:
            (lb1, ub1, xl, xu) = pixmap.mapbox(lbnd_in, ubnd_in, 1)
            (lb2, ub2, xl, xu) = pixmap.mapbox(lbnd_in, ubnd_in, 2)
            lbnd_out = [int(lb1), int(lb2)]
            ubnd_out = [int(ub1), int(ub2)]

        #  Get the value used to represent missing pixel values
        if "BLANK" in fitschan:
            badval = fitschan["BLANK"]
            flags = Ast.USEBAD
        else:
            badval = 0
            flags = 0

        # Resample the data array using the above mapping.
        (npix, out, out_var) = pixmap.resample(lbnd_in, ubnd_in,
                                               hdu_list[0].data, None,
                                               Ast.LINEAR, None, flags, 0.05,
                                               1000, badval, lbnd_out,
                                               ubnd_out, lbnd_out, ubnd_out)

        #  Store the rotated data in the HDU, and update the NAXISi keywords
        #  to hold the number of pixels along each edge of the rotated image.
        hdu_list[0].data = out
        fitschan["NAXIS1"] = ubnd_out[0] - lbnd_out[0] + 1
        fitschan["NAXIS2"] = ubnd_out[1] - lbnd_out[1] + 1

        #  Move the pixel origin of the output from "lbnd_out" to (1,1). Create a
        #  suitable ShiftMap, and append it to the total "old pixel coord" to
        #  "new pixel coords" mapping.
        shiftmap = Ast.ShiftMap([-lbnd_out[0], -lbnd_out[1]])
        pixmap = Ast.CmpMap(pixmap, shiftmap)

        #  Re-map the base Frame (i.e. the pixel coordinates Frame) so that it
        #  refers to the new data grid instead of the old one.
        wcsinfo.remapframe(Ast.BASE, pixmap)

        #  Attempt to write the modified WCS information to the primary HDU (i.e.
        #  convert the FrameSet to a set of FITS header cards stored in the
        #  FITS file). Indicate that we want to use original flavour of FITS-WCS.
        fitschan.Encoding = encoding
        fitschan.clear('Card')
        if fitschan.write(wcsinfo) == 0:
            print("Failed to convert the rotated WCS to Fits-WCS")

        #  If successfull, force the FitsCHan to copy its contents into the
        #  PyFITS header, then write the changed data and header to the output
        #  FITS file.
        else:
            fitschan.writefits()
            hdu_list.writeto(outfile, clobber=True, output_verify='ignore')
Exemple #10
0
import math
import starlink.Ast as Ast
import starlink.Atl as Atl

#  Open the FITS file using pyfits. A list of the HDUs in the FITS file is
#  returned.
hdu_list = pyfits.open( sys.argv[1] )

#  Create an object that will transfer FITS header cards between an AST
#  FitsChan and the PyFITS header describing the primary HDU of the
#  supplied FITS file.
adapter = Atl.PyFITSAdapter( hdu_list[ 0 ] )

#  Create a FitsChan and use the above adapter to copy all the header
#  cards into it.
fitschan = Ast.FitsChan( adapter, adapter )

#  Get the flavour of FITS-WCS used by the header cards currently in the
#  FitsChan. This is so that we can use the same flavour when we write
#  out the modified WCS.
encoding = fitschan.Encoding

#  Read WCS information from the FitsChan. Additionally, this removes all
#  WCS information from the FitsChan. The returned wcsinfo object
#  is an AST FrameSet, in which the current Frame describes WCS coordinates
#  and the base Frame describes pixel coodineates. The FrameSet includes a
#  Mapping that specifies the transformation between the two Frames.
wcsinfo = fitschan.read()

#  Check that the FITS header contained WCS in a form that can be
#  understood by AST.
Exemple #11
0
	def __init__(self, ast_object:Ast.FitsChan=None, hdu:Union[astropy.io.fits.hdu.base.ExtensionHDU,fitsio.hdu.base.HDUBase]=None, header=None):
		'''
		Initialize object with either an HDU or header from fitsio or astropy.io.fits.
		
		Parameters
		----------
		header : astropy.io.fits.header.Header or fitsio.fitslib.FITSHDR or list of tuples/arrays (keyword,value)
		
		@param header FITS header as a dictionary of strings (keyword,value) or one of these types: astropy.io.fits.header.Header, fitsio.fitslib.FITSHDR, or a plain string divisible by 80 characters.
		'''
		self._frameSet = None

		if ast_object:
			if any([hdu, header]):
				raise ValueError("If 'ast_object' is provided, 'hdu' or 'header' should not be set.")
			else:
				if isinstance(ast_object, starlink.Ast.FitsChan):
					super().__init__(ast_object=ast_object)
				else:
					raise ValueError
		
		# ----------
		# Validation
		# ----------
		if all([hdu, header]):
			raise Exception("Only specify an HDU or header to create a ASTFITSChannel object.")
			
		# get header from HDU
		if hdu:
			if hdu and _astropy_available and isinstance(hdu, astropy.io.fits.hdu.base.ExtensionHDU):
				header = hdu.header        # type: astropy.io.fits.header.Header
			elif hdu and _fitsio_available and isinstance(hdu, fitsio.fitslib.HDUBase):
				header = hdu.read_header() # type: fitsio.fitslib.FITSHDR
			else:
				raise Exception("ASTFITSChannel: unknown HDU type specified ('{0}').".format(type(hdu)))
		# ----------
		
		self._dimensions = None
		self.astObject = Ast.FitsChan() # sink=self
		
		if header is not None:
			# Note that the starlink.Ast.Channel.read() operation is destructive.
			# Save the header so it can be restored/reused.
			self.header = header
			if isinstance(header, (astropy.io.fits.header.Header, fitsio.fitslib.FITSHDR)) or \
			   (isinstance(header, list) and isinstance(header[0], str)):
			   self._readHeader()
			elif isinstance(header, list):
				self._readHeader()
			elif isinstance(header, str) and (len(header) % 80 == 0):
				# split into list, then process
				self.header = [header[i:i+80] for i in range(0, len(header), 80)]
				self._readHeader()
			elif isinstance(header, dict):
				for keyword in header:
					self.astObject[keyword] = header[keyword]
					#self.addHeader(keyword=key, value=header[key])
				#self._readHeader()
			else:
				raise Exception("Could not work with the type of header provided ('{0}').".format(type(header)))
		else:
			pass # work with an empty Ast.FitsChan()
			logger.warning("ASTFITSChannel: no data found: working with an empty FITSChannel.")
Exemple #12
0
    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)