def __init__(self, input_, component=None, filetype='sdf', getshape=False): # TODO: this should be using python NDF! # If input is a string, assume it is either an NDF file or a # FITS file, and read in the object frameset = None shape = None # TODO: (Should useNDF interface once that is easy to install) if isinstance(input_, str) and filetype == 'sdf': try: hdsloc = hds.open(input_, 'READ') if component: comps = component.split('.') for c in comps: hdsloc = hdsloc.find(c) astwcs = hdsloc.find('WCS').find('DATA').get() astwcs = [i.decode() for i in astwcs] astwcs = ''.join([ i[1:] if i.startswith('+') else '\n' + i for i in astwcs ]).split('\n') chan = Ast.Channel(astwcs) frameset = chan.read() except: logger.error('Could not get a frameset from %s', input_) hdsloc.annul() raise # Get the shape of the data. if getshape: try: data = hdsloc.find('DATA_ARRAY').find('DATA').get() shape = data.shape except: logger.debug('No data component found in input file.') shape = None hdsloc.annul() # If its a fits file, try and read a elif isinstance(input_, str) and filetype == 'fits': from astropy.io import fits from starlink import Atl hdulist = fits.open(input_) if not component: component = 0 (frameset, encoding) = Atl.readfitswcs(hdulist[component]) shape = hdulist[component].shape print('shape is', shape) hdulist.close() # Create low level object from frameset and shape print(type(frameset), type(shape)) self._low_level_wcs = AstWCSLowLevel(frameset, arrayshape=shape) self._info_string = None
# 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
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
from __future__ import print_function try: from astrop.io import fits as pyfits except ImportError: import pyfits import starlink.Atl as Atl import starlink.Ast as Ast import matplotlib.pyplot # 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()
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')
# - Requires pyast version 2.3 import pyfits import sys 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.
# 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