def ref_fk4_no_e_fk5(fnout='fk4_no_e_fk5.csv'): """ Accuracy tests for the FK4 (with no E-terms of aberration) to/from FK5 conversion, with arbitrary equinoxes and epoch of observation. """ import starlink.Ast as Ast np.random.seed(12345) N = 200 # Sample uniformly on the unit sphere. These will be either the FK4 # coordinates for the transformation to FK5, or the FK5 coordinates for the # transformation to FK4. ra = np.random.uniform(0., 360., N) dec = np.degrees(np.arcsin(np.random.uniform(-1., 1., N))) # Generate random observation epoch and equinoxes obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950., 2000., N)] equinox_fk4 = [f"B{x:7.2f}" for x in np.random.uniform(1925., 1975., N)] equinox_fk5 = [f"J{x:7.2f}" for x in np.random.uniform(1975., 2025., N)] ra_fk4, dec_fk4 = [], [] ra_fk5, dec_fk5 = [], [] for i in range(N): # Set up frames for AST frame_fk4 = Ast.SkyFrame( 'System=FK4-NO-E,Epoch={epoch},Equinox={equinox_fk4}'.format( epoch=obstime[i], equinox_fk4=equinox_fk4[i])) frame_fk5 = Ast.SkyFrame( 'System=FK5,Epoch={epoch},Equinox={equinox_fk5}'.format( epoch=obstime[i], equinox_fk5=equinox_fk5[i])) # FK4 to FK5 frameset = frame_fk4.convert(frame_fk5) coords = np.degrees( frameset.tran([[np.radians(ra[i])], [np.radians(dec[i])]])) ra_fk5.append(coords[0, 0]) dec_fk5.append(coords[1, 0]) # FK5 to FK4 frameset = frame_fk5.convert(frame_fk4) coords = np.degrees( frameset.tran([[np.radians(ra[i])], [np.radians(dec[i])]])) ra_fk4.append(coords[0, 0]) dec_fk4.append(coords[1, 0]) # Write out table to a CSV file t = Table() t.add_column(Column(name='equinox_fk4', data=equinox_fk4)) t.add_column(Column(name='equinox_fk5', data=equinox_fk5)) t.add_column(Column(name='obstime', data=obstime)) t.add_column(Column(name='ra_in', data=ra)) t.add_column(Column(name='dec_in', data=dec)) t.add_column(Column(name='ra_fk5', data=ra_fk5)) t.add_column(Column(name='dec_fk5', data=dec_fk5)) t.add_column(Column(name='ra_fk4', data=ra_fk4)) t.add_column(Column(name='dec_fk4', data=dec_fk4)) f = open(os.path.join('data', fnout), 'wb') f.write("# This file was generated with the {} script, and the reference " "values were computed using AST\n".format( os.path.basename(__file__))) t.write(f, format='ascii', delimiter=',')
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 == 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)
def ref_galactic_fk4(fnout='galactic_fk4.csv'): """ Accuracy tests for the ICRS (with no E-terms of aberration) to/from FK5 conversion, with arbitrary equinoxes and epoch of observation. """ import starlink.Ast as Ast np.random.seed(12345) N = 200 # Sample uniformly on the unit sphere. These will be either the ICRS # coordinates for the transformation to FK5, or the FK5 coordinates for the # transformation to ICRS. lon = np.random.uniform(0., 360., N) lat = np.degrees(np.arcsin(np.random.uniform(-1., 1., N))) # Generate random observation epoch and equinoxes obstime = [f"B{x:7.2f}" for x in np.random.uniform(1950., 2000., N)] equinox_fk4 = [f"J{x:7.2f}" for x in np.random.uniform(1975., 2025., N)] lon_gal, lat_gal = [], [] ra_fk4, dec_fk4 = [], [] for i in range(N): # Set up frames for AST frame_gal = Ast.SkyFrame(f'System=Galactic,Epoch={obstime[i]}') frame_fk4 = Ast.SkyFrame( f'System=FK4,Epoch={obstime[i]},Equinox={equinox_fk4[i]}') # ICRS to FK5 frameset = frame_gal.convert(frame_fk4) coords = np.degrees( frameset.tran([[np.radians(lon[i])], [np.radians(lat[i])]])) ra_fk4.append(coords[0, 0]) dec_fk4.append(coords[1, 0]) # FK5 to ICRS frameset = frame_fk4.convert(frame_gal) coords = np.degrees( frameset.tran([[np.radians(lon[i])], [np.radians(lat[i])]])) lon_gal.append(coords[0, 0]) lat_gal.append(coords[1, 0]) # Write out table to a CSV file t = Table() t.add_column(Column(name='equinox_fk4', data=equinox_fk4)) t.add_column(Column(name='obstime', data=obstime)) t.add_column(Column(name='lon_in', data=lon)) t.add_column(Column(name='lat_in', data=lat)) t.add_column(Column(name='ra_fk4', data=ra_fk4)) t.add_column(Column(name='dec_fk4', data=dec_fk4)) t.add_column(Column(name='lon_gal', data=lon_gal)) t.add_column(Column(name='lat_gal', data=lat_gal)) f = open(os.path.join('data', fnout), 'wb') f.write("# This file was generated with the {} script, and the reference " "values were computed using AST\n".format( os.path.basename(__file__))) t.write(f, format='ascii', delimiter=',')
# 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:
def run(self): # Get the names of library files Name of shared object # library, and ensure -bundle isn't called in linking on osx. libtype = 'shared' rdirs = ['$ORIGIN'] ldirs = [] ext_runtime_library_dirs = '$ORIGIN/{}' ext_extra_link_args = None osx = None if 'osx' in self.plat_name or 'darwin' in self.plat_name: osx = True print('\n\nBuilding under OSX!\n\n\n') libtype = 'dylib' from distutils import sysconfig vars = sysconfig.get_config_vars() vars['LDSHARED'] = vars['LDSHARED'].replace( '-bundle', '-dynamiclib') rdirs = [] ldirs = ['-Wl,-rpath,' + '@loader_path/'] ldirs = [] ext_runtime_library_dirs = None ext_extra_link_args = '-Wl,-rpath,@loader_path/{}' install_name_pattern = '-Wl,-install_name,@rpath/{}' # Ensure the directories and files are in appropriate locations. setup_building() # Before we can build the extensions, we have to run ./configure # and make for HDF5. basedir = os.getcwd() os.chdir('hdf5') env = os.environ if not FAKEBUILDING: subprocess.check_call('./configure', env=env) subprocess.check_call('make', env=env) os.chdir(basedir) #Now we need to get the header files we need copied into our #output directory. if not os.path.isdir('include'): os.mkdir('include') if not os.path.isdir(os.path.join('include', 'star')): os.mkdir(os.path.join('include', 'star')) # Copy hdf5 needed header files over. shutil.copy(os.path.join('hdf5', 'src', 'hdf5.h'), 'include') shutil.copy(os.path.join('hdf5', 'src', 'H5public.h'), 'include') shutil.copy(os.path.join('hdf5', 'src', 'H5pubconf.h'), 'include') shutil.copy(os.path.join('hdf5', 'src', 'H5version.h'), 'include') # Get the sources for the ndf and hds dependencies. hds_source_dep = [] for name_ in HDS_DEP_LIBS: hds_source_dep += get_source(name_) ndf_source_dep = [] for name_ in ['prm', 'ast', 'ary']: ndf_source_dep += get_source(name_) hdsex_includedirs = ['include', 'hds', 'missingincludes', 'hds_missingincludes', os.path.join('hdf5','src'), os.path.join('hdf5','hl','src')] + \ ['starutil', 'starmem', 'cnf', 'ems', 'mers', 'chr',\ 'one'] + \ [numpy.get_include()] from starlink import Ast ndfex_includedirs = hdsex_includedirs + [ 'prm', 'ast', 'ary', 'ast_missingincludes', Ast.get_include() ] define_macros = get_starlink_macros(osx=osx) # Now build all. # This is the directory where the extra library's built here # have to be copied to, relative to the final build. This must # be called '.libs' if you want to use this with # cibuildwheel/auditwheel. extra_lib_dir = '.libs' # Get the compilers. compiler = ccompiler.new_compiler(dry_run=FAKEBUILDING, verbose=0) compiler2 = ccompiler.new_compiler(verbose=1) # Ensure we have any distutils options set. customize_compiler(compiler) customize_compiler(compiler2) # Now go through each extension, build the shared libraries we # need and ensure they are copied to the build directory. We # will use rpath and $ORIGIN to ensure everything is portable # as it will be moved around during the build process by pip # etc. for ext in self.extensions: linked_libraries = [] if ext.name == 'starlink.hds': hds_deps = compiler.compile( sources=hds_source_dep, output_dir=OUTPUTDIR, macros=define_macros, include_dirs=HDS_DEP_INCLUDES, depends=hds_source_dep, ) hds_deps_libname = compiler2.library_filename('pyhdsdeps', lib_type=libtype) # Build this into a library print('Linking HDSDEPS\n\n\n') extra_preargs = None if osx: extra_preargs = [ '-v', '-Wl,-v', install_name_pattern.format(hds_deps_libname) ] compiler2.link('shared', hds_deps, hds_deps_libname, output_dir=OUTPUTDIR, extra_preargs=extra_preargs, extra_postargs=None) linked_libraries += [os.path.join(OUTPUTDIR, hds_deps_libname)] # Now build hds-v4 and hds-v5: have to do this separately. hdsv4_libname = compiler.library_filename('pyhdsv4', lib_type=libtype) hdsv4objs = compiler.compile( sources=get_source('hds-v4'), output_dir=OUTPUTDIR, macros=define_macros, include_dirs=('hds-v4_missingincludes', ) + HDS_DEP_INCLUDES, depends=get_source('hds-v4')) extra_preargs = None if osx: extra_preargs = [ install_name_pattern.format(hdsv4_libname) ] print('Linking HDSV4\n\n\n') compiler2.link('shared', hdsv4objs, hdsv4_libname, output_dir=OUTPUTDIR, extra_preargs=extra_preargs, target_lang='c') linked_libraries += [os.path.join(OUTPUTDIR, hdsv4_libname)] print('CREATING HDF5 LIBRARY\n\n\n') # Create the HDF5 library hdf5_libpath = os.path.join('hdf5', 'src', '.libs') hdf5_objects = glob.glob(os.path.join(hdf5_libpath, '*.o')) hdf5_libname = compiler.library_filename('pystarhdf5', lib_type=libtype) extra_preargs = None if osx: extra_preargs = [install_name_pattern.format(hdf5_libname)] compiler2.link('shared', hdf5_objects, hdf5_libname, output_dir=OUTPUTDIR, library_dirs=[OUTPUTDIR], runtime_library_dirs=rdirs, extra_postargs=ldirs, extra_preargs=extra_preargs) linked_libraries += [os.path.join(OUTPUTDIR, hdf5_libname)] hdsv5_libname = compiler2.library_filename('pyhdsv5', lib_type=libtype) hdsv5objs = compiler.compile( sources=get_source('hds-v5'), output_dir=OUTPUTDIR, macros=define_macros, include_dirs=('hds-v5_missingincludes', ) + HDS_DEP_INCLUDES, depends=get_source('hds-v5')) extra_preargs = None if osx: extra_preargs = [ install_name_pattern.format(hdsv5_libname) ] compiler2.link('shared', hdsv5objs, hdsv5_libname, output_dir=OUTPUTDIR, libraries=['pystarhdf5'], library_dirs=[OUTPUTDIR], runtime_library_dirs=rdirs, extra_postargs=ldirs, extra_preargs=extra_preargs) linked_libraries += [os.path.join(OUTPUTDIR, hdsv5_libname)] hds_libname = compiler2.library_filename('pyhds', lib_type=libtype) hdsobjs = compiler.compile(sources=get_source('hds'), output_dir=OUTPUTDIR, macros=define_macros, include_dirs=hdsex_includedirs, depends=get_source('hds')) extra_preargs = None if osx: extra_preargs = [install_name_pattern.format(hds_libname)] compiler2.link('shared', hdsobjs, hds_libname, output_dir=OUTPUTDIR, libraries=['pyhdsdeps', 'pyhdsv5', 'pyhdsv4'], library_dirs=[OUTPUTDIR], runtime_library_dirs=rdirs, extra_postargs=ldirs, extra_preargs=extra_preargs) linked_libraries += [os.path.join(OUTPUTDIR, hds_libname)] ext.libraries += ['pyhds'] ext.library_dirs += [OUTPUTDIR] if ext_runtime_library_dirs: ext.runtime_library_dirs += [ ext_runtime_library_dirs.format(extra_lib_dir) ] if ext_extra_link_args: ext.extra_link_args += [ ext_extra_link_args.format(extra_lib_dir) ] if ext.name == 'starlink.ndf': ndf_libname = compiler2.library_filename('pyndf', lib_type=libtype) ndfobjs = compiler.compile( sources=get_source('ndf') + ndf_source_dep, include_dirs=['ndf/', 'ndf_missingincludes/'] + ndfex_includedirs, macros=define_macros, depends=get_source('ndf')) extra_preargs = None if osx: extra_preargs = [install_name_pattern.format(ndf_libname)] compiler2.link( 'shared', ndfobjs, ndf_libname, output_dir=OUTPUTDIR, libraries=['pyhdsdeps', 'pyhdsv5', 'pyhdsv4', 'pyhds'], library_dirs=[OUTPUTDIR], runtime_library_dirs=rdirs, extra_postargs=ldirs, extra_preargs=extra_preargs) linked_libraries += [os.path.join(OUTPUTDIR, ndf_libname)] ext.libraries += ['pyndf'] ext.library_dirs += [OUTPUTDIR] if ext_runtime_library_dirs: ext.runtime_library_dirs += [ ext_runtime_library_dirs.format(extra_lib_dir) ] if ext_extra_link_args: ext.extra_link_args += [ ext_extra_link_args.format(extra_lib_dir) ] # Copy over the libraries to the build directory manually, and add to package data. if not os.path.isdir( os.path.join(self.build_lib, 'starlink', extra_lib_dir)): os.mkdir( os.path.join(self.build_lib, 'starlink', extra_lib_dir)) for lib in linked_libraries: shutil.copy( lib, os.path.join(self.build_lib, 'starlink', extra_lib_dir)) output_lib = os.path.join('starlink', extra_lib_dir, os.path.split(lib)[1]) self.distribution.package_data.get('starlink', list()).extend(output_lib) # Run the standard build_ext process. build_ext.run(self)
def __init__(self, ast_object: starlink.Ast.Circle = None, frame=None, center: Iterator = None, edge_point=None, radius: [float, astropy.units.quantity.Quantity] = None): ''' Parameters ---------- centerPoint : `numpy.ndarray`, list, tuple Two elements that describe the center point of the circle in the provided frame in degrees edgePoint : `numpy.ndarray`, list, tuple Two elements that describe a point on the circumference of the circle in the provided frame in degrees :param radius: float, `astropy.units.quantity.Quantity` The radius in degrees (if float) of the circle to be created. ''' self._uncertainty = 4.848e-6 # defaults to 1 arcsec if ast_object: if any( [x is None for x in [frame, centerPoint, radius, edgePoint]]): raise Exception( "ASTCircle: cannot specify both 'ast_object' and any other parameter." ) if isinstance(ast_object, starlink.Ast.Circle): # make sure no other parameters are set self.astObject = ast_object return else: raise Exception( "ASTCircle: The 'ast_object' provided was not of type starlink.Ast.Circle." ) # check valid combination of parameters # ------------------------------------- # make sure we have a frame we can work with if frame is None: raise Exception( "ASTCircle: A frame must be specified when creating an ASTCircle." ) else: if isinstance(frame, ASTFrame): self.frame = frame elif isinstance(frame, starlink.Ast.Frame): self.frame = ASTFrame(frame=frame) else: raise Exception( "ASTCircle: unexpected frame type specified ('{0}').". format(type(frame))) if all([x is not None for x in [edge_point, radius]]): raise ValueError( "Both 'edge_point' and 'radius' cannot be simultaneously specified." ) if center is None: raise ValueError("The 'center' parameter must be set.") if all([x is None for x in [edge_point, radius]]): raise ValueError( "Along with 'center', a 'radius' or 'edge_point' must be specified." ) # input forms: # CENTER_EDGE (0) : circle specified by center point and any point on the circumference (p1 = [float,float], p2 = [float,float]) # CENTER_RADIUS (1) : circle specified by center point and radius (p1 = [float,float], p2 = float) input_form = None if edge_point is None: input_form = CENTER_RADIUS # convert np.array types to lists so that the value can be used in 'any' and 'all' comparisons. # if isinstance(centerPoint, np.ndarray): # centerPoint = centerPoint.tolist() # if isinstance(edgePoint, np.ndarray): # edgePoint = edgePoint.tolist() # if isinstance(centerPoint, astropy.coordinates.SkyCoord): # centerPoint = [centerPoint.ra.to(u.deg).value, centerPoint.dec.to(u.deg).value] # if all([centerPoint, edgePoint]) or all([centerPoint, radius]): # if edgePoint: # input_form = CENTER_EDGE # else: # input_form = CENTER_RADIUS # else: # raise Exception("ASTCircle: Either 'centerPoint' and 'edgePoint' OR 'centerPoint' " + \ # "and 'radius' must be specified when creating an ASTCircle.") if isinstance(center, astropy.coordinates.SkyCoord): p1 = [center.ra.to(u.rad).value, center.dec.to(u.rad).value] elif isinstance(center[0], astropy.units.quantity.Quantity): try: p1 = [center[0].to(u.rad).value, center[1].to(u.rad).value] except astropy.units.core.UnitConversionError as e: # todo: be more descriptive raise e else: p1 = [deg2rad(center[0]), deg2rad(center[1])] if input_form == CENTER_EDGE: # p1 = center point, p2 = edge point if isinstance(edge_point, astropy.coordinates.SkyCoord): p2 = [ edge_point.ra.to(u.rad).value, edge_point.dec.to(u.rad).value ] else: p2 = [deg2rad(edge_point[0]), deg2rad(edge_point[1])] else: # p1 = center point, p2 = radius if isinstance(radius, astropy.units.quantity.Quantity): p2 = [radius.to(u.rad).value] else: p2 = [deg2rad(radius)] self.astObject = Ast.Circle(self.frame.astObject, input_form, p1, p2, unc=self.uncertainty)
# Get the Starlink specific defines. defines = get_starlink_macros() # Get the lists of source files for the NDF extra dependencies. ndf_source_dep = [] for name_ in ['prm', 'ast', 'ary']: ndf_source_dep += get_source(name_) hdsex_includedirs = ['include/', 'hds/', 'missingincludes/', 'hds_missingincludes/', 'hdf5/src/', 'hdf5/hl/src'] + \ ['starutil', 'starmem/', 'cnf', 'ems', 'mers', 'chr', 'one'] + \ [numpy.get_include()] ndfex_includedirs = hdsex_includedirs + ['prm', 'ast', 'ary', 'ast_missingincludes/', Ast.get_include()] # Can't build NDF without Ast! # Define the two extensions. hdsExtension = Extension('starlink.hds', sources=['starlink/hds.pyx'], include_dirs=hdsex_includedirs, define_macros=defines, libraries=['z'], ) ndfExtension = Extension('starlink.ndf',
def __init__(self,axes): if isinstance(axes,matplotlib.axes.Axes): self.axes = axes self.renderer = None # Save the current axis scales. self.Scales() # Create a temporary text string and line from which we can determine # the default graphics properties. xl,xr = self.axes.get_xlim() yb,yt = self.axes.get_ylim() xc = 0.5*(xl+xr); yc = 0.5*(yt+yb); text = matplotlib.text.Text( xc, yc, "a") line = matplotlib.lines.Line2D( [xc], [yc], marker="+") # Save the current default marker and text sizes. self.__deftsize = text.get_size() self.__defmsize = line.get_markersize() # Save the default text colour. defcol = text.get_color() # Save the default text font family and style. deffont = {"family":text.get_family(),"style":text.get_style()} # Save the default line style defstyle = line.get_linestyle() # A list used to convert AST integer marker types into matplotlib # character marker types. self.markers = ['s','.','+','*','o','x',',','^','v','<','>', 'p','h','D'] # A list used to convert AST integer line style types into corresponding matplotlib # properties. Ensure the first line style is the default. self.styles = [ {"linestyle":defstyle}, {"linestyle":'-'}, {"linestyle":'--'}, {"linestyle":':'}, {"linestyle":'-.'} ] # A list used to convert AST integer font types into corresponding matplotlib # properties. Ensure the first font is the default. self.fonts = [ deffont, {"family":'serif',"style":'normal'}, {"family":'serif',"style":'italic'}, {"family":'sans-serif',"style":'normal'}, {"family":'sans-serif',"style":'italic'}, {"family":'monospace',"style":'normal'}, {"family":'monospace',"style":'italic'} ] # A list used to convert AST integer colours into corresponding matplotlib # properties. Ensure the first colour is the default. self.colours = [ {"color":defcol}, {"color":'#ff0000'}, {"color":'#00ff00'}, {"color":'#0000ff'}, {"color":'#00ffff'}, {"color":'#ff00ff'}, {"color":'#ffff00'}, {"color":'#000000'}, {"color":'#a9a9a9'}, {"color":'#808080'}, {"color":'#d3d3d3'}, {"color":'#ffffff'} ] # The current graphics attribute values as used by AST self.__attrs = { Ast.grfLINE:{Ast.grfSTYLE:1, Ast.grfWIDTH:1, Ast.grfSIZE:1, Ast.grfFONT:1, Ast.grfCOLOUR:1}, Ast.grfMARK:{Ast.grfSTYLE:1, Ast.grfWIDTH:1, Ast.grfSIZE:1, Ast.grfFONT:1, Ast.grfCOLOUR:1}, Ast.grfTEXT:{Ast.grfSTYLE:1, Ast.grfWIDTH:1, Ast.grfSIZE:1, Ast.grfFONT:1, Ast.grfCOLOUR:1}} # The corresponding graphics properties used by matplotlib self.__props = { Ast.grfLINE:{"solid_capstyle":'butt'}, Ast.grfMARK:{}, Ast.grfTEXT:{}} # Ensure the defaults are current. for attr in ( Ast.grfCOLOUR, Ast.grfWIDTH, Ast.grfSIZE, Ast.grfFONT, Ast.grfSTYLE ): for prim in ( Ast.grfTEXT, Ast.grfLINE, Ast.grfMARK ): self.Attr( attr, 1.0, prim ) # Set new delimiters for graphical sky axis values, using appropriate escape # sequences to get he superscripts looking nice. Ast.tunec( "hrdel", "%-%^85+%s70+h%>45+%+" ) Ast.tunec( "mndel", "%-%^85+%s70+m%>45+%+" ) Ast.tunec( "scdel", "%-%^85+%s70+s%>45+%+" ) Ast.tunec( "dgdel", "%-%^90+%s60+o%>45+%+" ) Ast.tunec( "amdel", "%-%^30+%s85+'%>45+%+" ) Ast.tunec( "asdel", "%-%^30+%s85+\"%>45+%+" ) Ast.tunec( "exdel", "10%-%^85+%s60+%>20+" ) # Initialise the correction vector for text. self._xcorr = 0.0 self._ycorr = 0.0 # Save the current character heights, and update the vertical offset # correction for text. self.Qch() # Report an error if the supplied object is not suitable else: m = axes.__class__.__module__ c = axes.__class__.__name__ if m != "builtins": c = m + "." + c raise TypeError("The supplied axes object is a "+c+", it should " "an instance of matplotlib.axes.Axes or a subclass")
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.")
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.
def __init__(self, circle_extent=None, figsize=(12.0, 12.0)): self.ast_plot = None # type: Ast.Plot # ------------------------------------------------------- # Create frame set that will map the position in the plot # (i.e. pixel coordinates) to the sky (i.e. WCS) fits_chan = ASTFITSChannel() naxis1 = 100 naxis2 = 100 # The NAXIS values are arbitrary; this object is used for mapping. cards = { "CRVAL1":circle_extent.center[0], # reference point (image center) in sky coords "CRVAL2":circle_extent.center[1], "CTYPE1":"RA---TAN", #"GLON-TAN", # projection type "CTYPE2":"DEC--TAN", #"GLAT-TAN", "CRPIX1":50.5, # reference point (image center) point in pixel coords "CRPIX2":50.5, "CDELT1":2.1*circle_extent.radius/100, "CDELT2":2.1*circle_extent.radius/100, "NAXIS1":naxis1, "NAXIS2":naxis2, "NAXES":2, } naxis1 = cards['NAXIS1'] naxis2 = cards['NAXIS2'] pix2sky_mapping = ASTFrameSet.fromFITSHeader(fits_header=cards) # ------------------------------------------------------- # Create a matplotlib figure, 12x12 inches in size. dx = figsize[0] # 12.0 dy = figsize[1] # 12.0 fig = plt.figure( figsize=(dx,dy) ) fig_aspect_ratio = dy/dx # Set up the bounding box of the image in pixel coordinates, and get # the aspect ratio of the image. bbox = (0.5, 0.5, naxis1 + 0.5, naxis2 + 0.5) fits_aspect_ratio = ( bbox[3] - bbox[1] )/( bbox[2] - bbox[0] ) # Set up the bounding box of the image as fractional offsets within the # figure. The hx and hy variables hold the horizontal and vertical half # widths of the image, as fractions of the width and height of the figure. # Shrink the image area by a factor of 0.7 to leave room for annotated axes. if fig_aspect_ratio > fits_aspect_ratio : hx = 0.5 hy = 0.5*fits_aspect_ratio/fig_aspect_ratio else: hx = 0.5*fig_aspect_ratio/fits_aspect_ratio hy = 0.5 hx *= 0.7 hy *= 0.7 gbox = ( 0.5 - hx, 0.5 - hy, 0.5 + hx, 0.5 + hy ) # Add an Axes structure to the figure and display the image within it, # scaled between data values zero and 100. Suppress the axes as we will # be using AST to create axes. ax_image = fig.add_axes( [ gbox[0], gbox[1], gbox[2] - gbox[0], gbox[3] - gbox[1] ], zorder=1 ) ax_image.xaxis.set_visible( False ) ax_image.yaxis.set_visible( False ) #ax_image.imshow( hdu_list[0].data, vmin=0, vmax=200, cmap=plt.cm.gist_heat, # origin='lower', aspect='auto') # Add another Axes structure to the figure to hold the annotated axes # produced by AST. It is displayed on top of the previous Axes # structure. Make it transparent so that the image will show through. ax_plot = fig.add_axes( [ 0, 0, 1, 1 ], zorder=2 ) ax_plot.xaxis.set_visible(False) ax_plot.yaxis.set_visible(False) ax_plot.patch.set_alpha(0.0) # Create a drawing object that knows how to draw primitives (lines, # marks and strings) into this second Axes structure. grf = Grf.grf_matplotlib( ax_plot ) #print(f"gbox: {gbox}") #print(f"bbox: {bbox}") # box in graphics coordinates (area to draw on, dim of plot) #plot = Ast.Plot( frameset.astObject, gbox, bbox, grf ) self.ast_plot = Ast.Plot( pix2sky_mapping.astObject, gbox, bbox, grf, options="Uni1=ddd:mm:ss" ) #, options="Grid=1" ) #plot.set( "Colour(border)=2, Font(textlab)=3" ); self.ast_plot.Grid = True # can change the line properties self.ast_plot.Format_1 = "dms" # colors: # 1 = black # 2 = red # 3 = lime # 4 = blue # 5 = # 6 = pink self.ast_plot.grid() self.ast_plot.Width_Border = 2
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]))
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)
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]) """ import starlink.Ast as Ast import starlink.Atl as Atl # 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
# 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
# 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
#!/usr/bin/env python import numpy as np import starlink.Ast as Ast # Read in initial coordinates as J2000 coordinates data_j2000 = np.radians(np.loadtxt('../initial_coords.txt')) ra_j2000_fk5, dec_j2000_fk5 = data_j2000[:, 0], data_j2000[:, 1] # Create a Frame to describe J2000 FK5 coordinates, and another that # will be used in turn to describe each of the output coordinate systems. # Assume that the epoch of observation is J2000.0. The default values for # the reference equinox will be used (J2000.0 for FK5 and ecliptic, and # B1950.0 for FK4). fk5_frame = Ast.SkyFrame( 'System=FK5,Format(1)=hms.5,Format(2)=dms.5,Epoch=2000.0') out_frame = Ast.SkyFrame('Format(1)=hms.5,Format(2)=dms.5,Epoch=2000.0') # Loop round each output coordinate system, modifying "out_frame" to # describe each one. vals = {} for system in 'FK4', 'Ecliptic', 'Galactic', 'ICRS': out_frame.System = system # Get the transformation from FK5 J2000 to the current output system. fk5_to_out = fk5_frame.convert(out_frame) # Transform the FK5 J2000 positions into the curent output system using # the above transformation. vals[system] = fk5_to_out.tran([ra_j2000_fk5, dec_j2000_fk5])
ndf_source_dep = [] for name_ in ['prm', 'ast', 'ary']: ndf_source_dep += get_source(name_) hdsex_includedirs = ['include/', 'hds/', 'missingincludes/', 'hds_missingincludes/', 'hdf5/src/', 'hdf5/hl/src'] + \ ['starutil', 'starmem/', 'cnf', 'ems', 'mers', 'chr',\ 'one'] + \ [numpy.get_include()] from starlink import Ast ndfex_includedirs = hdsex_includedirs + [ 'prm', 'ast', 'ary', 'ast_missingincludes/', Ast.get_include() ] # Can't build NDF without Ast! ndfex_includedirs.append(Ast.get_include()) # Define the two extensions. hdsExtension = Extension( 'starlink.hds', sources=[os.path.join('starlink', 'hds', 'hds.c')], include_dirs=hdsex_includedirs, define_macros=defines, libraries=['z'], )
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')
def __init__(self, axes): if isinstance(axes, matplotlib.axes.Axes): self.axes = axes self.renderer = None # Save the current axis scales. self.Scales() # Create a temporary text string and line from which we can determine # the default graphics properties. xl, xr = self.axes.get_xlim() yb, yt = self.axes.get_ylim() xc = 0.5 * (xl + xr) yc = 0.5 * (yt + yb) text = matplotlib.text.Text(xc, yc, "a") line = matplotlib.lines.Line2D([xc], [yc], marker="+") # Save the current default marker and text sizes. self.__deftsize = text.get_size() self.__defmsize = line.get_markersize() # Save the default text colour. defcol = text.get_color() # Save the default text font family and style. deffont = {"family": text.get_family(), "style": text.get_style()} # Save the default line style defstyle = line.get_linestyle() # A list used to convert AST integer marker types into matplotlib # character marker types. self.markers = ['s', '.', '+', '*', 'o', 'x', ',', '^', 'v', '<', '>', 'p', 'h', 'D'] # A list used to convert AST integer line style types into corresponding matplotlib # properties. Ensure the first line style is the default. self.styles = [{"linestyle": defstyle}, {"linestyle": '-'}, {"linestyle": '--'}, {"linestyle": ':'}, {"linestyle": '-.'}] # A list used to convert AST integer font types into corresponding matplotlib # properties. Ensure the first font is the default. self.fonts = [deffont, {"family": 'serif', "style": 'normal'}, {"family": 'serif', "style": 'italic'}, {"family": 'sans-serif', "style": 'normal'}, {"family": 'sans-serif', "style": 'italic'}, {"family": 'monospace', "style": 'normal'}, {"family": 'monospace', "style": 'italic'}] # A list used to convert AST integer colours into corresponding matplotlib # properties. Ensure the first colour is the default. self.colours = [{"color": defcol}, {"color": '#ff0000'}, {"color": '#00ff00'}, {"color": '#0000ff'}, {"color": '#00ffff'}, {"color": '#ff00ff'}, {"color": '#ffff00'}, {"color": '#000000'}, {"color": '#a9a9a9'}, {"color": '#808080'}, {"color": '#d3d3d3'}, {"color": '#ffffff'}] # The current graphics attribute values as used by AST self.__attrs = {Ast.grfLINE: {Ast.grfSTYLE: 1, Ast.grfWIDTH: 1, Ast.grfSIZE: 1, Ast.grfFONT: 1, Ast.grfCOLOUR: 1}, Ast.grfMARK: {Ast.grfSTYLE: 1, Ast.grfWIDTH: 1, Ast.grfSIZE: 1, Ast.grfFONT: 1, Ast.grfCOLOUR: 1}, Ast.grfTEXT: {Ast.grfSTYLE: 1, Ast.grfWIDTH: 1, Ast.grfSIZE: 1, Ast.grfFONT: 1, Ast.grfCOLOUR: 1}} # The corresponding graphics properties used by matplotlib self.__props = {Ast.grfLINE: {"solid_capstyle": 'butt'}, Ast.grfMARK: {}, Ast.grfTEXT: {}} # Ensure the defaults are current. for attr in (Ast.grfCOLOUR, Ast.grfWIDTH, Ast.grfSIZE, Ast.grfFONT, Ast.grfSTYLE): for prim in (Ast.grfTEXT, Ast.grfLINE, Ast.grfMARK): self.Attr(attr, 1.0, prim) # Set new delimiters for graphical sky axis values, using appropriate escape # sequences to get he superscripts looking nice. Ast.tunec("hrdel", "%-%^85+%s70+h%>45+%+") Ast.tunec("mndel", "%-%^85+%s70+m%>45+%+") Ast.tunec("scdel", "%-%^85+%s70+s%>45+%+") Ast.tunec("dgdel", "%-%^90+%s60+o%>45+%+") Ast.tunec("amdel", "%-%^30+%s85+'%>45+%+") Ast.tunec("asdel", "%-%^30+%s85+\"%>45+%+") Ast.tunec("exdel", "10%-%^85+%s60+%>20+") # Initialise the correction vector for text. self._xcorr = 0.0 self._ycorr = 0.0 # Save the current character heights, and update the vertical offset # correction for text. self.Qch() # Report an error if the supplied object is not suitable else: m = axes.__class__.__module__ c = axes.__class__.__name__ if m != "builtins": c = m + "." + c raise TypeError("The supplied axes object is a " + c + ", it should " "an instance of matplotlib.axes.Axes or a subclass")
#!/usr/bin/python3 import starlink.Ast as Ast bmask = open("bmask.log", "r") lut = [] igood = -0.5 for i in range(32): for j in range(40): maskin = bmask.readline().rstrip() if maskin == "BAD": lut.append(Ast.BAD) else: igood += 1 lut.append(igood) bmask.close() mapping = open("mapping.ast", "w") mapping.write( str( Ast.CmpMap(Ast.UnitMap(1), Ast.LutMap(lut, 0.5, 1.0, "LutInterp=1"), False))) mapping.close()