├── README.md ├── quantity_api.py ├── .c9revisions └── ccdproc_api.py.c9save ├── ccdproc_api.py └── coordinates_example.py /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ----- 3 | 4 | The purpose of this repository is to collect documents describing 5 | planned APIs for major components of Astropy. Open a pull request for a 6 | new API or to suggest changes in a planned API. 7 | -------------------------------------------------------------------------------- /quantity_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | 6 | import astropy.units as u 7 | 8 | """ The Quantity class will represent a number + unit + uncertainty """ 9 | 10 | # ------------------- 11 | # Creating quantities 12 | # ------------------- 13 | 14 | # One method for creating a quantity is through operations with Unit objects: 15 | q = 11.42 * u.meter # returns a Quantity object 16 | q = 11.42 / u.meter 17 | q = 182.234 + u.meter # raises an error 18 | 19 | # You could also use the Quantity constructor, but 'unit' must be supplied: 20 | q1 = u.Quantity(11.412, unit=u.meter) 21 | q2 = u.Quantity(21.52, "cm") 22 | q3 = u.Quantity(11.412) # raises an exception because no unit specified 23 | 24 | # ---------- 25 | # Operations 26 | # ---------- 27 | 28 | # Each of these operations would return a new Quantity object 29 | q1 = u.Quantity(11.41, u.meter) 30 | q2 = u.Quantity(15.37, u.meter) 31 | new_quantity = q1 + q2 32 | new_quantity = q1 - q2 33 | new_quantity = q1 * q2 34 | new_quantity = q1 / q2 35 | new_quantity = -q1 36 | 37 | # Operations will *adopt the units from the object on the left* if the units are equivalent 38 | # for both objects involved, and may raise an exception otherwise: 39 | q1 = u.Quantity(11.41, u.meter) 40 | q2 = u.Quantity(15.37, u.kilometer) 41 | new_quantity = q1 + q2 # has unit u.meter! 42 | 43 | q1 = u.Quantity(11.41, u.meter) 44 | q2 = u.Quantity(15.37, u.second) 45 | q1 / q2 # valid, returns an object in meters per second 46 | q1 + q2 # raises an exception 47 | 48 | # The Units package will have a native type that represents "no unit." Operations with such dimensionless 49 | # objects will look like this (replace unit="" with whatever is decided for Unit package): 50 | Quantity(15.1234, unit=u.kilogram) * Quantity(0.75, unit="") # should work 51 | Quantity(15.1234, unit=u.kilogram) / Quantity(0.75, unit="") # should work 52 | 53 | Quantity(15.1234, unit=u.kilogram) + Quantity(0.75, unit="") # should NOT work 54 | Quantity(15.1234, unit=u.kilogram) - Quantity(0.75, unit="") # should NOT work 55 | 56 | # ---------------- 57 | # Converting units 58 | # ---------------- 59 | 60 | # to get a new quantity object in some new, equivalent units, you would use the to() method: 61 | q = 11.412*u.meter 62 | q.to(u.kilometer) # this returns a new quantity object with the supplied units 63 | q.to(u.zettastokes) # Fails because not equivalent 64 | 65 | # you may also just want the *value* of the quantity in some units. To do this, use the value attribute: 66 | q1 = Quantity(194.118, unit=u.kilometer) 67 | q1.value # returns 194.118 68 | q1.to(u.centimeter).value # returns the *value* (just a number, not an object) of this quantity in the specified units 69 | 70 | # to find out the unit of a quantity object, use the unit property 71 | q1.unit 72 | 73 | # -------- 74 | # Equality 75 | # -------- 76 | Quantity(1000, unit=u.m) == Quantity(1, units=u.km) # returns True 77 | Quantity(1, units=u.m) == Quantity(1, units=u.km) # returns False 78 | Quantity(1, units=u.m) == Quantity(1, units=u.s) # raises IncompatibleUnitError() 79 | 80 | # ---------- 81 | # Displaying 82 | # ---------- 83 | # Printing the quantity should display the number and the units when converting to a string 84 | str(quantity) # returns '11.412 m' 85 | repr(quantity) # returns '' or something 86 | 87 | # ================================================================================================ 88 | 89 | # ----------- 90 | # Uncertainty -- Note: The below is preliminary, and may be implemented on a subclass instead! 91 | # ----------- 92 | 93 | # A Quantity object may also have an uncertainty associated with them, but this will be 94 | # implemented at a later date, probably in a subclass of Quantity. The usage might look 95 | # something like this: 96 | q1 = u.Quantity(14.6, u.meter, uncertainty=some_probability_distribution) 97 | q2 = u.Quantity(14.6, u.meter, uncertainty=(0.1, 0.2)) 98 | q1 = u.Quantity(11.41, u.meter, uncertainty=0.01) 99 | q2 = u.Quantity(14.6, u.meter, uncertainty=0.2) 100 | 101 | # We will also support a number of astropy Uncertainty classes, but the names have not been 102 | # decided upon. That will look like: 103 | q1 = u.Quantity(14.6, u.meter, uncertainty=StandardDeviationUncertainty(0.5)) 104 | q2 = u.Quantity(14.6, u.meter, uncertainty=VarianceUncertainty(0.25)) 105 | # etc. 106 | 107 | # For objects with uncertainties, we can't propagate errors unless the user uses one of the astropy 108 | # Uncertainty classes, which know how to do their own error propagation (but are still in development). 109 | 110 | # In the future, something like this will work (names may change): 111 | q1 = u.Quantity(11.41, u.meter, uncertainty=StandardDeviationUncertainty(0.35)) 112 | q2 = u.Quantity(15.37, u.meter, uncertainty=StandardDeviationUncertainty(0.11)) 113 | new_quantity = q1 + q2 # error propagation done for the user -------------------------------------------------------------------------------- /.c9revisions/ccdproc_api.py.c9save: -------------------------------------------------------------------------------- 1 | {"ts":1353360988436,"silentsave":true,"restoring":false,"patch":[[{"diffs":[[1,"#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import print_function\n\nfrom astropy.nddata import NDdata\n'''\nThe ccdproc package provides tools for the reduction and \nanalysis of optical data captured with a CCD. The package\nis built around the CCDData class, which has built into\nit all of the functions to process data. The CCDData object \ncontains all the information to describe the 2-D readout\nfrom a single amplifier/detector. \n\nThe CCDData class inherits from the NDData class as its base object\nand the object on which all actions will be performed. By\ninheriting from the CCD data class, basic array manipulation\nand error handling are already built into the object.\n\nThe CCDData task should be able to properly deal with the\npropogation of errors and propogate bad pixel frames\nthrough each of the tasks. It should also update the meta\ndata, units, and WCS information as the data are processed\nthrough each step.\n\nThe following functions are required for performing basic CCD correction:\n-creation of variance frame\n-overscan subtraction\n-bias subtraction \n-trimming the data\n-gain correction\n-xtalk correction\n-dark frames correction\n-flat field correction\n-illumination correction\n-fringe correction\n-scattered light correction\n-cosmic ray cleaning\n-distortion correction \n\nIn addition to the CCDData and CCDList class, the ccdproc does \nrequire some additional features in order to properly \nreduce CCD data. The following features are required\nfor basic processing of CCD data:\n-fitting data\n-combining data\n-re-sampling data\n-transforming data\n\nAll actions of ccdproc should be logged and recorded.\n\nMulti-Extension FITS files can be handled by treating \neach extension as a CCDData object and \n\n'''\n\n# ============\n# Base Objects\n# ============\n'''\nCCDData is an object that inherits from NDData class and specifically\ndescribes an object created from the single readout of a CCD. \n\nUsers should be able to create a CCDData object from scratch, from\nan existing NDData object, or a single extension from a FITS file. \n\nIn the case of the CCDData, the parameter 'uncertainty' will\nbe mapped to variance as that will be more explicitly describing\nthe information that will be kept for the processing of the \n\n'''\ndata=100+10*np.random.random((110,100))\nccddata=CCDData(data=data)\nccddata=CCDData(NDData.NDData)\nccddata=CCDData(pyfits.ImageHDU)\n\n#Setting basic properties of the object\n# ----------------------\nccddata.variance=data**0.5\nccddata.mask=np.ones(110,100)\nccddata.flags=np.zeros(110,100)\nccddata.wcs=None #when will this be available?\nccddata.meta={}\nccddata.units=u.adu #is this valid? \n\n# Functional Requirements\n# ----------------------\n# A number of these different fucntions are convenient functions that\n# just outline the process that is needed. The important thing is that\n# the process is being logged and that a clear process is being handled \n# by each step to make building a pipeline easy. Then again, it might\n# not be easy to handle all possible steps which are needed, and the more\n# important steps will be things which aren't already handled by NDData.\n\n#All functions should propogate throught to the variance frame and \n#bad pixel mask\n\n#convenience function based on a given value for \n#the readnoise and gain. Units should be specified\n#for these values though.\n#Question: Do we have an object that is just a scalar\n#and a unit? Or a scalar, unit and an error? ie, This \n#could actually be handled by the gain and readnoise being\n#specified as an NDData object \nccddata.createvariance(gain=1.0, readnoise=5.0)\n\n#Overscan subtract the data\n#Should be able to provide the meta data for\n#the keyworkd or provide a section to define and\n#possible an axis to specify the oritation of the \n#Question: Best way to specify the section? Should it be given \n#Error Checks: That the section is within the image\nccddata.subtract_overscan(section='[:,100:110]', function='polynomial', order=3)\n\n#trim the images--the section gives the part of the image to keep\n#That the trim section is within the image\nccddata.trim_image(section='[0:100,0:100]')\n\n#subtract the master bias. Although this is a convenience function as subtracting\n#the two arrays will do the same thing. This should be able to handle logging of\n#of subtracting it off (logging should be added to NDData and then this is really\n#just a convenience function\n#Error checks: the masterbias and image are the same shape\nmasterbias=NDData.NDData(np.zeros(100,100))\nccddata.subtract_bias(masterbias)\n\n#correct for dark frames\n#Error checks: the masterbias and image are the same shape\nmasterdark=NDData.NDData(np.zeros(100,100))\nccddata.subtract_dark(darkframe)\n\n#correct for gain--once again gain should have a unit and even an error associated with it. \nccddata.gain_correct(gain=1.0)\n#Also the gain may be non-linear\nccddata.gain_correct(gain=np.array([1.0,0.5e-3])\n#although then this step should be apply before any other corrections if it is non-linear\n#but that is more up to the person processing their own data.\n\n#crosstalk corrections--also potential a convenience function, but basically multiples the\n#xtalkimage by the coeffient and then subtracts it. It is kept general because this will\n#be dependent on the CCD and the set up of the CCD. Not applicable for a single CCD\n#situation\n#Error checks: the xtalkimage and image are the same shape\nxtalkimage=NDData.NDData(np.zeros(100,100))\nccddata.xtalk_correct(xtalkimage, coef=1e-3)\n\n#flat field correction--this can either be a dome flat, sky flat, or an \n#illumination corrected image. This step should normalize by the value of the\n#flatfield after dividing by it.\n#Error checks: the flatimage and image are the same shape\n#Error checks: check for divive by zero\n#Features: If the flat is less than minvalue, minvalue is used\nflatimage=NDData.NDData(np.ones(100,100))\nccddata.flat_correct(flatimage, minvalue=1)\n\n#fringe correction or any correction that requires subtracting\n#off a potentially scaled image\n#Error checks: the flatimage and image are the same shape\nfringemage=NDData.NDData(np.zero(100,100))\nccddata.fringe_correct(fringeimage, scale=1)\n\n#cosmic ray cleaning step--this should have options for different\n#ways to do it with their associated steps. We also might want to \n#implement this as a slightly different step. The cosmic ray cleaning\n#step should update the mask and flags. So the user could have options\n#to replace the cosmic rays, only flag the cosmic rays, or flag and \n#mask the cosmic rays, or all of the above.\nccddata.cosmicray_clean(method='laplace', args=*kwargs)\n\n#Apply distortion corrections\n#Either update the WCS or transform the frame\nccddata.distortion_correct(distortion)\n\n\n# ================\n# Helper Functions \n# ================\n\n#fit a 1-D function with iterative rejections and the ability to \n#select different functions to fit. \n#other options are reject parameters, number of iteractions \n#and/or convergernce limit\ncoef=iterfit(x, y, function='polynomial', order=3)\n\n#fit a 2-D function with iterative rejections and the ability to \n#select different functions to fit. \n#other options are reject parameters, number of iteractions \n#and/or convergernce limit\ncoef=iterfit(data, function='polynomial', order=3)\n\n#combine a set of NDData objects\ncombine([ccddata, ccddata2], method='average', reject=None, **kwargs)\n\n#re-sample the data to different binnings (either larger or smaller\nccddata=rebin(ccddata, binning=(2,2))\n\n#tranform the data--ie shift, rotate, etc\n#question--add convience functions for image shifting and rotation?\n#should udpate WCS although that would actually be the prefered method\nccddata=transform(ccddata, transform, conserve_flux=True)\n"]],"start1":0,"start2":0,"length1":0,"length2":7720}]],"length":7720} 2 | {"contributors":[],"silentsave":false,"ts":1353361244077,"patch":[[{"diffs":[[0," objects"],[1,". Combining objects will need to handle how to\n#combine the error dat and masks as well. It should have different methods for\n#combining the data (average, median) and for rejection data (None, sigma clip, \n#ccdclip, minmax). Rejected pixels should be reflected in the final bad pixel\n#maps. Methods for scaling the data and weighting the data can also be \n#included. The task should also handle any memories issues that will occur to\n#dealing with large file sizes."],[0,"\ncombine"]],"start1":7294,"start2":7294,"length1":16,"length2":489},{"diffs":[[0,"x=True)\n"],[1,"\n\n"]],"start1":8185,"start2":8185,"length1":8,"length2":10}]],"length":8195,"saved":false} 3 | -------------------------------------------------------------------------------- /ccdproc_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | 6 | from astropy.nddata import NDdata 7 | ''' 8 | The ccdproc package provides tools for the reduction and 9 | analysis of optical data captured with a CCD. The package 10 | is built around the CCDData class, which has built into 11 | it all of the functions to process data. The CCDData object 12 | contains all the information to describe the 2-D readout 13 | from a single amplifier/detector. 14 | 15 | The CCDData class inherits from the NDData class as its base object 16 | and the object on which all actions will be performed. By 17 | inheriting from the CCD data class, basic array manipulation 18 | and error handling are already built into the object. 19 | 20 | The CCDData task should be able to properly deal with the 21 | propogation of errors and propogate bad pixel frames 22 | through each of the tasks. It should also update the meta 23 | data, units, and WCS information as the data are processed 24 | through each step. 25 | 26 | The following functions are required for performing basic CCD correction: 27 | -creation of variance frame 28 | -overscan subtraction 29 | -bias subtraction 30 | -trimming the data 31 | -gain correction 32 | -xtalk correction 33 | -dark frames correction 34 | -flat field correction 35 | -illumination correction 36 | -fringe correction 37 | -scattered light correction 38 | -cosmic ray cleaning 39 | -distortion correction 40 | 41 | In addition to the CCDData and CCDList class, the ccdproc does 42 | require some additional features in order to properly 43 | reduce CCD data. The following features are required 44 | for basic processing of CCD data: 45 | -fitting data 46 | -combining data 47 | -re-sampling data 48 | -transforming data 49 | 50 | All actions of ccdproc should be logged and recorded. 51 | 52 | Multi-Extension FITS files can be handled by treating 53 | each extension as a CCDData object and 54 | 55 | ''' 56 | 57 | # ============ 58 | # Base Objects 59 | # ============ 60 | ''' 61 | CCDData is an object that inherits from NDData class and specifically 62 | describes an object created from the single readout of a CCD. 63 | 64 | Users should be able to create a CCDData object from scratch, from 65 | an existing NDData object, or a single extension from a FITS file. 66 | 67 | In the case of the CCDData, the parameter 'uncertainty' will 68 | be mapped to variance as that will be more explicitly describing 69 | the information that will be kept for the processing of the 70 | 71 | ''' 72 | data=100+10*np.random.random((110,100)) 73 | ccddata=CCDData(data=data) 74 | ccddata=CCDData(NDData.NDData) 75 | ccddata=CCDData(pyfits.ImageHDU) 76 | 77 | #Setting basic properties of the object 78 | # ---------------------- 79 | ccddata.variance=data**0.5 80 | ccddata.mask=np.ones(110,100) 81 | ccddata.flags=np.zeros(110,100) 82 | ccddata.wcs=None #when will this be available? 83 | ccddata.meta={} 84 | ccddata.units=u.adu #is this valid? 85 | 86 | # Functional Requirements 87 | # ---------------------- 88 | # A number of these different fucntions are convenient functions that 89 | # just outline the process that is needed. The important thing is that 90 | # the process is being logged and that a clear process is being handled 91 | # by each step to make building a pipeline easy. Then again, it might 92 | # not be easy to handle all possible steps which are needed, and the more 93 | # important steps will be things which aren't already handled by NDData. 94 | 95 | #All functions should propogate throught to the variance frame and 96 | #bad pixel mask 97 | 98 | #convenience function based on a given value for 99 | #the readnoise and gain. Units should be specified 100 | #for these values though. 101 | #Question: Do we have an object that is just a scalar 102 | #and a unit? Or a scalar, unit and an error? ie, This 103 | #could actually be handled by the gain and readnoise being 104 | #specified as an NDData object 105 | ccddata.createvariance(gain=1.0, readnoise=5.0) 106 | 107 | #Overscan subtract the data 108 | #Should be able to provide the meta data for 109 | #the keyworkd or provide a section to define and 110 | #possible an axis to specify the oritation of the 111 | #Question: Best way to specify the section? Should it be given 112 | #Error Checks: That the section is within the image 113 | ccddata.subtract_overscan(section='[:,100:110]', function='polynomial', order=3) 114 | 115 | #trim the images--the section gives the part of the image to keep 116 | #That the trim section is within the image 117 | ccddata.trim_image(section='[0:100,0:100]') 118 | 119 | #subtract the master bias. Although this is a convenience function as subtracting 120 | #the two arrays will do the same thing. This should be able to handle logging of 121 | #of subtracting it off (logging should be added to NDData and then this is really 122 | #just a convenience function 123 | #Error checks: the masterbias and image are the same shape 124 | masterbias=NDData.NDData(np.zeros(100,100)) 125 | ccddata.subtract_bias(masterbias) 126 | 127 | #correct for dark frames 128 | #Error checks: the masterbias and image are the same shape 129 | masterdark=NDData.NDData(np.zeros(100,100)) 130 | ccddata.subtract_dark(darkframe) 131 | 132 | #correct for gain--once again gain should have a unit and even an error associated with it. 133 | ccddata.gain_correct(gain=1.0) 134 | #Also the gain may be non-linear 135 | ccddata.gain_correct(gain=np.array([1.0,0.5e-3]) 136 | #although then this step should be apply before any other corrections if it is non-linear 137 | #but that is more up to the person processing their own data. 138 | 139 | #crosstalk corrections--also potential a convenience function, but basically multiples the 140 | #xtalkimage by the coeffient and then subtracts it. It is kept general because this will 141 | #be dependent on the CCD and the set up of the CCD. Not applicable for a single CCD 142 | #situation 143 | #Error checks: the xtalkimage and image are the same shape 144 | xtalkimage=NDData.NDData(np.zeros(100,100)) 145 | ccddata.xtalk_correct(xtalkimage, coef=1e-3) 146 | 147 | #flat field correction--this can either be a dome flat, sky flat, or an 148 | #illumination corrected image. This step should normalize by the value of the 149 | #flatfield after dividing by it. 150 | #Error checks: the flatimage and image are the same shape 151 | #Error checks: check for divive by zero 152 | #Features: If the flat is less than minvalue, minvalue is used 153 | flatimage=NDData.NDData(np.ones(100,100)) 154 | ccddata.flat_correct(flatimage, minvalue=1) 155 | 156 | #fringe correction or any correction that requires subtracting 157 | #off a potentially scaled image 158 | #Error checks: the flatimage and image are the same shape 159 | fringemage=NDData.NDData(np.zero(100,100)) 160 | ccddata.fringe_correct(fringeimage, scale=1) 161 | 162 | #cosmic ray cleaning step--this should have options for different 163 | #ways to do it with their associated steps. We also might want to 164 | #implement this as a slightly different step. The cosmic ray cleaning 165 | #step should update the mask and flags. So the user could have options 166 | #to replace the cosmic rays, only flag the cosmic rays, or flag and 167 | #mask the cosmic rays, or all of the above. 168 | ccddata.cosmicray_clean(method='laplace', args=*kwargs) 169 | 170 | #Apply distortion corrections 171 | #Either update the WCS or transform the frame 172 | ccddata.distortion_correct(distortion) 173 | 174 | 175 | # ================ 176 | # Helper Functions 177 | # ================ 178 | 179 | #fit a 1-D function with iterative rejections and the ability to 180 | #select different functions to fit. 181 | #other options are reject parameters, number of iteractions 182 | #and/or convergernce limit 183 | coef=iterfit(x, y, function='polynomial', order=3) 184 | 185 | #fit a 2-D function with iterative rejections and the ability to 186 | #select different functions to fit. 187 | #other options are reject parameters, number of iteractions 188 | #and/or convergernce limit 189 | coef=iterfit(data, function='polynomial', order=3) 190 | 191 | #combine a set of NDData objects. Combining objects will need to handle how to 192 | #combine the error dat and masks as well. It should have different methods for 193 | #combining the data (average, median) and for rejection data (None, sigma clip, 194 | #ccdclip, minmax). Rejected pixels should be reflected in the final bad pixel 195 | #maps. Methods for scaling the data and weighting the data can also be 196 | #included. The task should also handle any memories issues that will occur to 197 | #dealing with large file sizes. 198 | combine([ccddata, ccddata2], method='average', reject=None, **kwargs) 199 | 200 | #re-sample the data to different binnings (either larger or smaller 201 | ccddata=rebin(ccddata, binning=(2,2)) 202 | 203 | #tranform the data--ie shift, rotate, etc 204 | #question--add convience functions for image shifting and rotation? 205 | #should udpate WCS although that would actually be the prefered method 206 | ccddata=transform(ccddata, transform, conserve_flux=True) 207 | 208 | 209 | -------------------------------------------------------------------------------- /coordinates_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | 6 | from astropy.coordinates import Angle 7 | from astropy.units import Units as u 8 | ''' 9 | The emphasis of this package is that common tasks should 10 | be performed in as simple and intuitive manner as possible. 11 | Complex or unusual tasks are still possible, but should not 12 | interfere with the simplicity of common tasks. 13 | 14 | One design principle is that objects should silently accept parameters 15 | of any data type that can unambiguously be determined. Where data types 16 | are accepted, they should be specified as constants, not as strings. 17 | If an unknown unit is specified as a string, the code certainly 18 | cannot handle it. 19 | 20 | A note on units: Units (as with everything else in Python) are objects. 21 | Any instance of a "unit" keyword will only accept "Unit" objects, not 22 | bare strings. All commonly used units will be predefined in astropy.units 23 | (which is still under development). The exact syntax might change, 24 | but the ideas will not. Units may be specified as part of a string in 25 | an initializer (subscribing to the "accept and parse values that are 26 | unambiguous to a human reader" philosophy), but will not be accepted as 27 | a bare string anywhere else. 28 | 29 | On arrays: The interface outlined here only accepts scalar angles (and hence 30 | coordinates) - we will likely later expand it to allow arrays to be stored in 31 | these objects, but only after the scalar interfaces is complete and working. 32 | ''' 33 | 34 | # ============ 35 | # Base Objects 36 | # ============ 37 | ''' 38 | The "angle" is a fundamental object. The internal representation 39 | is stored in radians, but this is transparent to the user. Units 40 | *must* be specified rather than a default value be assumed. This is 41 | as much for self-documenting code as anything else. 42 | 43 | Angle objects simply represent a single angular coordinate. More specific 44 | angular coordinates (e.g. RA, Dec) are subclasses of Angle. 45 | 46 | ''' 47 | # Creating Angles 48 | # --------------- 49 | angle = Angle(54.12412, unit=u.degree) 50 | angle = Angle("54.12412", unit=u.degree) 51 | angle = Angle("54:07:26.832", unit=u.degree) 52 | angle = Angle("54.12412 deg") 53 | angle = Angle("54.12412 degrees") 54 | angle = Angle("54.12412°") # because we like Unicode 55 | angle = Angle((54,7,26.832), unit=unit=u.degree) 56 | # (deg,min,sec) *tuples* are acceptable, but lists/arrays are *not* because of the need to eventually support arrays of coordinates 57 | 58 | angle = Angle(3.60827466667, unit=u.hour) 59 | angle = Angle("3:36:29.7888000120", unit=u.hour) 60 | angle = Angle((3,36,29.7888000120), unit=u.hour) #as above, *must* be a tuple 61 | 62 | angle = Angle(0.944644098745, unit=u.radian) 63 | 64 | angle = Angle(54.12412) 65 | #raises an exception because this is ambiguous 66 | 67 | # Angle operations 68 | ''' 69 | Angles can be added and subtracted. Multiplication and division by 70 | a scalar is also permitted. A negative operator is also valid. 71 | All of these operate in a single dimension. Attempting to 72 | multiply or divide two Angle objects will raise an exception. 73 | ''' 74 | a1 = Angle(3.60827466667, unit=u.hour) 75 | a2 = Angle("54:07:26.832", unit=u.degree) 76 | a3 = a1 + a2 # creates new Angle object 77 | a4 = a1 - a2 78 | a5 = -a1 79 | 80 | a6 = a1 / a2 # raises an exception 81 | a6 = a1 * a2 # raises an exception 82 | 83 | a7 = Angle(a1) # makes a *copy* of the object, but identical content as a1 84 | 85 | 86 | # Bounds 87 | # ------ 88 | ''' 89 | By default the Angle object can accept any value, but will return 90 | values in [-360,360] (retaining the sign that was specified). 91 | 92 | One can also set artificial bounds for custom applications and range checking. 93 | As Angle objects are intended to be immutable (the angle value anyway), this provides a bound check 94 | upon creation. The units given in the "bounds" keyword must match the units 95 | specified in the scalar. 96 | ''' 97 | a1 = Angle(13343, unit=u.degree) 98 | print(a1.degrees) 99 | # 23 100 | 101 | a2 = Angle(-50, unit=u.degree) 102 | print(a2.degrees) 103 | # -50 104 | 105 | a3 = Angle(-361, unit=u.degree) 106 | print (a3.degrees) 107 | # -1 108 | 109 | # custom bounds 110 | 111 | a4 = Angle(66, unit=u.degree, bounds=(-45,45)) 112 | # RangeError 113 | 114 | a5 = Angle(390, unit=u.degree, bounds=(-75,75)) 115 | print(a5.degrees) 116 | # 30, no RangeError because while 390>75, 30 is within the bounds 117 | 118 | a6 = Angle(390, unit=u.degree, bounds=(-720, 720)) 119 | print(a6.degrees) 120 | # 390 121 | 122 | a7 = Angle(1020, unit=u.degree, bounds=None) 123 | print(a7.degrees) 124 | # 1020 125 | 126 | # bounds and operations 127 | 128 | a8 = a5 + a6 129 | # ValueError - the bounds don't match 130 | 131 | a9 = a5 + a5 132 | print(a9.bounds) 133 | # (-75, 75) - if the bounds match, there is no error and the bound is kept 134 | print(a9.degrees) 135 | # 60 136 | 137 | #To get the default bounds back, you need to create a new object with the equivalent angle 138 | a9 = Angle(a5.degrees + a5.degrees, unit=u.degree) 139 | 140 | a10 = Angle(a5.degrees + a6.degrees, unit=u.degree, bounds=[-180,180]) 141 | print(a10.degrees) 142 | # 60 - if they don't match and you want to combine, just re-assign the bounds yourself 143 | 144 | a11 = a7 - a7 145 | print(a11.degrees) 146 | # 0 - bounds of None can also be operated on without complaint 147 | 148 | 149 | # Converting units 150 | # ---------------- 151 | angle = Angle("54.12412", unit=u.degree) 152 | 153 | print("Angle in hours: {0}.".format(angle.hours)) 154 | # Angle in hours: 3.60827466667. 155 | 156 | print("Angle in radians: {0}.".format(angle.radians)) 157 | # Angle in radians: 0.944644098745. 158 | 159 | print("Angle in degrees: {0}.".format(angle.degrees)) 160 | # Angle in degrees: 54.12412. 161 | 162 | print("Angle in HMS: {0}".format(angle.hms)) # returns a tuple, e.g. (12, 21, 2.343) 163 | # Angle in HMS: (3, 36, 29.78879999999947) 164 | 165 | print("Angle in DMS: {0}".format(angle.dms)) # returns a tuple, e.g. (12, 21, 2.343) 166 | # Angle in DMS: (54, 7, 26.831999999992036) 167 | 168 | 169 | # String formatting 170 | # ----------------- 171 | ''' 172 | The string method of Angle has this signature: 173 | def string(self, unit=DEGREE, decimal=False, sep=" ", precision=5, pad=False): 174 | 175 | The "decimal" parameter defaults to False since if you need to print the 176 | Angle as a decimal, there's no need to use the "to_string" method (see above). 177 | ''' 178 | print("Angle as HMS: {0}".format(angle.to_string(unit=u.hour))) 179 | # Angle as HMS: 3 36 29.78880 180 | 181 | print("Angle as HMS: {0}".format(angle.to_string(unit=u.hour, sep=":"))) 182 | # Angle as HMS: 3:36:29.78880 183 | 184 | print("Angle as HMS: {0}".format(angle.to_string(unit=u.hour, sep=":", precision=2))) 185 | # Angle as HMS: 3:36:29.79 186 | 187 | # Note that you can provide one, two, or three separators passed as a tuple or list 188 | # 189 | print("Angle as HMS: {0}".format(angle.string(unit=u.hour, sep=("h","m","s"), precision=4))) 190 | # Angle as HMS: 3h36m29.7888s 191 | 192 | print("Angle as HMS: {0}".format(angle.string(unit=u.hour, sep=["-","|"], precision=4))) 193 | # Angle as HMS: 3-36|29.7888 194 | 195 | print("Angle as HMS: {0}".format(angle.string(unit=u.hour, sep="-", precision=4))) 196 | # Angle as HMS: 3-36-29.7888 197 | 198 | # The "pad" parameter will add leading zeros to numbers less than 10. 199 | # 200 | print("Angle as HMS: {0}".format(angle.string(unit=u.hour, precision=4, pad=True))) 201 | # Angle as HMS: 03 36 29.7888 202 | 203 | # RA/Dec Objects 204 | # -------------- 205 | from astropy.coordinates import RA, Dec 206 | ''' 207 | RA and Dec are objects that are subclassed from Angle. As with Angle, RA and Dec can 208 | parse any unambiguous format (tuples, formatted strings, etc.). 209 | 210 | The intention is not to create an Angle subclass for every possible coordinate object 211 | (e.g. galactic l, galactic b). However, equatorial RA/dec are so prevalent in astronomy 212 | that it's worth creating ones for these units. They will be noted as "special" in the 213 | docs and use of the just the Angle class is to be used for other coordinate systems. 214 | ''' 215 | ra = RA("4:08:15.162342") # error - hours or degrees? 216 | ra = RA("26:34:65.345634") # unambiguous 217 | 218 | # Units can be specified 219 | ra = RA("4:08:15.162342", unit=u.hour) 220 | 221 | # Where RA values are commonly found in hours or degrees, declination is nearly always 222 | # specified in degrees, so this is the default. 223 | dec = Dec("-41:08:15.162342") 224 | dec = Dec("-41:08:15.162342", unit=u.degree) # same as above 225 | 226 | # The Dec object will have bounds hard-coded at [-90,90] degrees. 227 | 228 | # Coordinates 229 | # ----------- 230 | ''' 231 | A coordinate marks a position on the sky. This is an object that contains two Angle 232 | objects. There are a wide array of coordinate systems that should be implemented, and 233 | it will be easy to subclass to create custom user-made coordinates with conversions to 234 | standard coordinates. 235 | ''' 236 | from astropy.coordinates import ICRSCoordinate, GalacticCoordinate, HorizontalCoordinate, Coordinate 237 | 238 | # A coordinate in the ICRS standard frame (~= J2000) 239 | c = ICRSCoordinate(ra, dec) #ra and dec are RA and Dec objects, or Angle objects 240 | c = ICRSCoordinate("54.12412 deg", "-41:08:15.162342") #both strings are unambiguous 241 | 242 | dec = c.dec 243 | # dec is a Dec object 244 | print(dec.degrees) 245 | # -41.137545095 246 | 247 | # It would be convenient to accept both (e.g. ra, dec) coordinates as a single 248 | # string in the initializer. This can lead to ambiguities particularly when both 249 | # are different units. The solution is to accept an array for the "unit" keyword 250 | # that one would expect to sent to Angle: 251 | c = ICRSCoordinate('4 23 43.43 +23 45 12.324', unit=[u.hour]) 252 | # The first element in 'units' refers to the first coordinate. 253 | # DEGREE is assumed for the second coordinate 254 | 255 | c = ICRSCoordinate('4 23 43.43 +23 45 12.324', unit=[u.hour, u.degree]) 256 | # Both can be specified and should be when there is ambiguity. 257 | 258 | 259 | # Other types of coordinate systems have their own classes 260 | c = GalacticCoordinates(l, b) #this only accepts Angles, *not* RA and Dec objects 261 | 262 | c.l 263 | # this is an Angle object, *not* RA or Dec 264 | 265 | #some coordinates require an epoch - the argument will be an astropy.time.Time object 266 | #and the syntax for that is still being ironed out 267 | c = HorizontalCoordinates(alt, az, epoch=timeobj) 268 | 269 | 270 | # Coordinate Factory 271 | # ------------------ 272 | ''' 273 | To simplify usage, syntax will be provided to figure out the type of coordinates the user 274 | wants without requiring them to know exactly which frame they want. The coordinate 275 | system is determined from the keywords used when the object is initialized or can 276 | be explicitly specified using "a1" and "a2" as angle parameters. 277 | 278 | Question for the community: Should this be a *class* that overrides __new__ to provide 279 | a new object, or a generator *function*. Erik prefers a function because he thinks it's 280 | less magical to create an object from a function. Demitri prefers an abstract class 281 | (Coordinate) that will return a new object that is subclassed from Coordinate, as 282 | it's a more object-oriented solution and Demitri doesn't like functions in Python. 283 | ''' 284 | 285 | #Note: `Coordinate` as used below would be `coordinate` if a function were used 286 | c = Coordinate(ra="12:43:53", dec=-23, angle1_unit=u.hour, angle2_unit=u.degree) 287 | # The ra and dec keywords imply equatorial coordinates, which will default to ICRS 288 | # hence this returns an ICRSCoordinate 289 | c = Coordinate(l=158.558650, b=-43.350066, angle1_unit=u.degree, angle2_unit=u.degree) 290 | # l and b are for galactic coordinates, so this returns a GalacticCoordinate object 291 | 292 | # Any acceptable input for RA() is accepted in Coordinate, etc. 293 | c = Coordinate(ra="24:08:15.162342", dec=-41.432345, angle1_unit=u.hour, angle2_unit=u.degree) 294 | 295 | # Mismatched keywords produce an error 296 | c = Coordinate(ra="24:08:15.162342", b=-43.350066, angle1_unit=u.hour, angle2_unit=u.degree) # error 297 | 298 | # Angle objects also accepted, and thus do not require units 299 | ra = RA("4:08:15.162342", unit=u.hour) 300 | dec = Dec("-41:08:15.162342") 301 | c = Coordinate(ra=ra, dec=dec) 302 | 303 | 304 | 305 | # Coordinate Conversions 306 | # ---------------------- 307 | ''' 308 | Coordinate conversion occurs on-demand internally 309 | ''' 310 | # using the c from above, which is RA,Dec = 4:08:15, -41:08:15.2 311 | print(c.galactic) 312 | # GalacticCoordinate(245.28098,-47.554501) 313 | 314 | print(c.galactic.l, c.galactic.b) #the `galactic` result will be cached to speed this up 315 | # Angle(158.558650) Angle(-43.350066) 316 | 317 | # can also explicitly specify a coordinate class to convert to 318 | gal = c.convert_to(GalacticCoordinate) 319 | 320 | # can still convert back to equatorial using the shorthand 321 | print(gal.equatorial.ra.to_string(unit=u.hour, sep=":", precision=2)) 322 | # 4:08:15.16 323 | 324 | horiz = c.convert_to(HorizontalCoordinate) 325 | # ConvertError - there's no way to convert to alt/az without a specified location 326 | 327 | 328 | # users can specify their own coordinates and conversions 329 | class CustomCoordinate(BaseCoordinate): 330 | coordsysname = 'my_coord' 331 | ... specify conversions somewhere in the class ... 332 | 333 | # allows both ways of converting 334 | mycoord1 = c.my_coord 335 | mycoord2 = c.convert_to(CustomCoordinate) 336 | 337 | 338 | # Separations 339 | # ----------- 340 | ''' 341 | Angular separations between two points on a sphere are supported via the 342 | `separation` method. 343 | ''' 344 | c1 = Coordinate(ra=0, dec=0, unit=u.degree) 345 | c2 = Coordinate(ra=0, dec=1, unit=u.degree) 346 | 347 | sep = c2.separation(c1) 348 | #returns an AngularSeparation object (a subclass of Angle) 349 | 350 | print(sep.degrees) 351 | # 1.0 352 | print(sep.arcmin) 353 | # 60.0 354 | 355 | c1 + c2 356 | c1 - c2 357 | # TypeError - these operations have ambiguous interpretations for points on a sphere 358 | 359 | c3 = Coordinate(l=0, b=0, unit=u.degree) #Galactic Coordinates 360 | 361 | # if there is a defined conversion between the relevant coordinate systems, it will 362 | # be automatically performed to get the right angular separation 363 | print c3.separation(c1).degrees 364 | # 62.8716627659 - distance from the north galactic pole to celestial pole 365 | 366 | c4 = CustomCoordinate(0, 0, unit=u.degree) 367 | c4.separation(c1) 368 | # raises an error if no conversion from the custom to equatorial 369 | # coordinates is available 370 | 371 | 372 | # Distances 373 | # --------- 374 | ''' 375 | Distances can also be specified, and allow for a full 3D definition of the coordinate. 376 | ''' 377 | from astropy.coordinates import Distance 378 | 379 | d = Distance(12, u.PARSECS) 380 | # need to provide a unit 381 | 382 | #standard units are pre-defined 383 | print(distance.light_years) 384 | # 39.12 385 | 386 | print(distance.km) 387 | # 3.7e14 388 | 389 | print(distance.z) # redshift, assuming "current" cosmology 390 | # (very small for 12 pc) 391 | print(distance().z) # custom cosmology possible 392 | 393 | #Coordinate objects can be assigned a distance object, giving them a full 3D position: 394 | c.distance = Distance(12, u.PARSECS) 395 | 396 | # Coordinate objects can be initialized with a distance using special syntax 397 | c1 = Coordinate(l=158.558650, b=-43.350066, unit=u.degree, distance=12 * u.KPC) 398 | 399 | # Coordinate objects can be instantiated with cartesian coordinates 400 | # Internally they will immediately be converted to two angles + a distance 401 | c2 = ICRSCoordinates(x=2, y=4, z=8, unit=u.PARSEC) 402 | 403 | c1.separation3d(c2, cosmology=) #if cosmology isn't given, use current 404 | # returns a *3d* distance between the c1 and c2 coordinates with units 405 | 406 | 407 | # Cartesian Coordinates 408 | # ---------------------- 409 | ''' 410 | All spherical coordinate systems with distances can be converted to 411 | cartesian coordinates. 412 | ''' 413 | 414 | (x, y, z) = (c.x, c.y, c.z) 415 | #this only computes the CartesianPoint *once*, and then caches it 416 | 417 | c.cartesian 418 | # returns CartesianPoint object, raise exception if no distance was specified 419 | 420 | # CartesianPoint objects can be added and subtracted, which are vector/elementwise 421 | # they can also be given as arguments to a coordinate system 422 | csum = ICRSCoordinates(c1.cartesian + c2.cartesian) --------------------------------------------------------------------------------