├── .gitignore ├── LICENSE ├── README.md ├── codemeta.json ├── example_use_tess_stars2py_byfunction.py ├── setup.cfg ├── setup.py ├── tess_stars2px.py └── wasm ├── CMakeLists.txt ├── README.md ├── include ├── mat_ra.h ├── mat_ra3.h └── vec.h ├── public ├── tess_stars2px.js └── tess_stars2px.wasm └── src ├── mat_ra3.c ├── tess_stars2px.c └── vec.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | sandbox/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christopher Burke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TESS-Point 2 | High Precision TESS pointing tool. 3 | 4 | Convert target coordinates given in Right Ascension and Declination to TESS detector pixel coordinates and vice versa. Can also query MAST to obtain detector pixel coordinates for a star by TIC ID or common star name (must be online for this option). Provides the target ecliptic coordinates, Sector number, camera number, detector number, and pixel column and row. If there is no output, then the target is not visible to TESS. 5 | 6 | ### Install or Upgrade 7 | `pip install tess-point` 8 | 9 | `pip install tess-point --upgrade` 10 | 11 | ### Examples 12 | - Display command line arguments and features 13 | 14 | `python -m tess_stars2px -h` 15 | 16 | - Return pixel coordinates for Pi Mensae ra and dec in degrees 17 | 18 | `python -m tess_stars2px -c 84.291188 -80.469119` 19 | 20 | - Return pixel coordinates for target by TIC ID (must be online) 21 | 22 | `python -m tess_stars2px -t 261136679` 23 | 24 | - Return pixel coordinates by target name (name resolved via [SESAME](http://cds.u-strasbg.fr/cgi-bin/Sesame)) 25 | 26 | `python -m tess_stars2px -n "pi Mensae"` 27 | 28 | - Multi-target pixel coordinates results. List the target TIC ID or other integer identifier [can be zero]; ra [deg]; dec [deg] in a whitespace delimited text file. Process the target list. 29 | 30 | `python -m tess_stars2px -f ` 31 | 32 | Alternatively, the python module is a single file, tess_stars2px.py, so one can avoid pip install. Just download tess_stars2px.py from github and use it in a local directory. The above commands would then be python tess_starspx.py -t 261136679 33 | 34 | - tess_stars2px can be called from a python program. See example_use_tess_strs2py_byfunction.py for this way to use tess_stars2px 35 | 36 | ### AUTHORS 37 | Original programming in C and focal plane geometry solutions by Alan Levine (MIT). This python translation by Christopher J. Burke (MIT). Testing and focal plane geometry refinements by Michael Fausnaugh & Roland Vanderspek (MIT). Testing by Thomas Barclay (NASA Goddard) and Jessica Roberts (Univ. of Colorado). By target name resolving implemented by Brett Morris (UW). Python help from Brigitta Sipocz and Martin Owens. Bug reports by Adina Feinstein (Univ. Chicago). Proxy implementation by Dishendra Mishra. Depcrecatin fixes by Ethan Kruse. 38 | 39 | ### VERSION: 0.9 40 | 41 | ### WHAT'S NEW: 42 | - Year 8 pointings for Sectors 97-107 now available. 43 | 44 | ### VERSION: 0.8.1 45 | 46 | ### WHAT'S NEW: 47 | - Year 7 pointings for Sectors 84-96 now available. 48 | 49 | ### VERSION: 0.8 50 | 51 | ### WHAT'S NEW: 52 | - Year 6 pointings for Sectors 70-83 now available. 53 | 54 | ### VERSION: 0.7.1 55 | 56 | ### WHAT'S NEW: 57 | - Year 5 pointings for Sectors 56-69 now available. 58 | - Bug correction for aberration. Only impacts if you were using aberration flag WITHOUT the single sector. In other words, the bug does not impact users that did not use aberrate flag or aberrate flag with s flag 59 | - Sector 46 field update 2021 October 60 | - Too close to edge Warning flag now output in column. If a target is within 6 pixels of the edge of the science region (edgeWarn==1), then the target is unlikely to be assigned a 2minute or 20s aperture. The science pixels range in column from 45-2092 and row from 1-2048 61 | - An approximate aberration correction is available with command line option. Uses astropy GCRS Earth based frame which is close to TESS aberration 62 | - Inverse transform (input Sector, Camera, CCD, pixel Column, pixel Row --> RA and Dec) is now 'analytic' rather than through brute force minimization. The inverse transform is much faster and much more reliable. 63 | 64 | ### CITATION: 65 | A citation for tess-point is available through the [Astrophysics Source Code Library](http://www.ascl.net/2003.001) entry. More complete BibTeX at bottom of page. 66 | 67 | Burke, C. J., Levine, A., Fausnaugh, M., Vanderspek, R., Barclay, T., Libby-Roberts, J. E., Morris, B., Sipocz, B., Owens, M., Feinstein, A. D., Camacho, J., 2020, 0.4.1, Astrophysics Source Code Library, record ascl:2003:001 68 | 69 | ### NOTES 70 | - Pointing table is for TESS Year 1 - 6 (Sectors 1-83) . 71 | 72 | - Testing shows pointing with this tool should be accurate to better than a pixel, but without including aberration effects, ones algorithm adopted for centroiding highly assymmetric point-spread function at edge of camera, and by-eye source location, a 2 pixel accuracy estimate is warranted. Use aberration option for better accuracy 73 | 74 | - The output pixel coordinates assume the ds9 convention with 1,1 being the middle of the lower left corner. 75 | 76 | - Pointing table is unofficial, and the pointings may change. 77 | 78 | - See https://tess.mit.edu/observations/ for latest TESS pointing table 79 | 80 | - No corrections for velocity aberration are calculated by default. Potentially more accurate results can be obtained if the target RA and Declination coordinates have aberration effects applied. The aberrate option uses the astropy GCRS Earth based frame in order to approximate a TESS frame. Earth has a velocity of 30km/s in solar system whereas TESS moves <4km/s relative to Earth, thus the GCRS correction should largely remove the 20arcsecond Earth induced aberration amplitude 81 | 82 | - For proposals to the TESS science office or directors discretionary time, please consult the TESS prediction webtool available at https://heasarc.gsfc.nasa.gov/cgi-bin/tess/webtess/wtv.py for official identification of 'observable' targets. However, if your proposal depends on a single or few targets, then this tool is helpful to further refine the likelihood of the target being available on the detectors. 83 | 84 | - The calibrated FFI fits file release at MAST and calibrated by NASA Ames SPOC will have WCS information available to supplant this code. The WCS generation is independent of the focal plane geometry model employed in this code and will give different results at the pixel level. However, the WCS information is not available until the FFI files are released, whereas this code can predict positions in advance of data release. 85 | 86 | - Hard coded focal plane geometry parameters from rfpg5_c1kb.txt 87 | 88 | ### OLD NOTES: 89 | - Query by name using Sesame by Brett Morris 90 | 91 | - Wrapper function implemented tess_stars2px_function_entry() with an example program, example_use_tess_stars2py_byfunction.py for using tess_stars2px in your own python program rather than on the command line. 92 | 93 | - Pre filter step previously depended on the current mission profile of pointings aligned with ecliptic coordinates to work. The pre filter step was rewritten in order to support mission planning not tied to ecliptic alignment. End users should not see any change in results with this change. However, local copies can be modified for arbitrary spacecraft ra,dec, roll and get same functionality. 94 | 95 | - A reverse option is added to find the ra and dec for a given sector, camera, ccd, colpix, rowpix. This is most useful for planning arbitrary pointing boundaries and internal use to identify targets on uncalibrated images that don't have WCS info available. For precision work one should defer to WCS information on calibrated FFIs rather than this tool. 96 | 97 | 98 | ### TODOS: 99 | 1. Time dependent Focal plane geometry 100 | 101 | ### DEPENDENCIES: 102 | - python 3+ 103 | - astropy 104 | - numpy 105 | 106 | ### SPECIAL THANKS TO: 107 | Includes code from the python MAST query examples 108 | https://mast.stsci.edu/api/v0/pyex.html 109 | 110 | ### IMPLEMENTATION DETAILS: 111 | In summary, the code begins with a space craft bore site pointing in RA, Dec, and roll angle. A series of Euler angle translation matrices are calculated based upon the space craft bore site. Next the target coordinates in RA and Dec are translated to the space craft bore site frame. Next, the target coordinates are translated to each of the four TESS camera frames. Once target coordinates are translated to the camera frame the radial position of the target relative to the camera center is checked to see if it is potentially in the camera field of view. If so, the focal plane position is calculated using a radial polynomial model with a constant term and terms the even powers (2nd ,4th , and 8th). Rotations are applied to convert the on sky positions to the detector readout directions. 112 | 113 | ### Notes to self 114 | 1. Make code changes 115 | 2. Update version number in README.md, code, and setup.py 116 | 3. git add, commit, push 117 | 4. upload to PyPI - python setup.py sdist upload -r pypi 118 | 5. Make release on github 119 | 120 | ### BibTeX 121 | 122 | ``` 123 | @MISC{2020ascl.soft03001B, 124 | author = {{Burke}, C.~J. and {Levine}, A. and {Fausnaugh}, M. and {Vanderspek}, R. and {Barclay}, T. and {Libby-Roberts}, J.~E. and {Morris}, B. and {Sipocz}, B. and {Owens}, M. and {Feinstein}, A.~D. and {Camacho}, J. 125 | }, 126 | title = "{TESS-Point: High precision TESS pointing tool}", 127 | keywords = {Software }, 128 | howpublished = {Astrophysics Source Code Library}, 129 | year = 2020, 130 | month = mar, 131 | archivePrefix = "ascl", 132 | eprint = {2003.001}, 133 | adsurl = {http://adsabs.harvard.edu/abs/2020ascl.soft03001B}, 134 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 135 | } 136 | ``` 137 | -------------------------------------------------------------------------------- /codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://doi.org/10.5063/schema/codemeta-2.0", 3 | "@type": "SoftwareSourceCode", 4 | "license": "https://spdx.org/licenses/MIT", 5 | "codeRepository": "https://github.com/christopherburke/tess-point", 6 | "dateCreated": "2018-11-13", 7 | "datePublished": "2018-11-13", 8 | "dateModified": "2020-02-24", 9 | "downloadUrl": "https://github.com/christopherburke/tess-point/archive/v0.4.0.tar.gz", 10 | "name": "tess-point", 11 | "version": "0.4.0", 12 | "description": "Tess-point converts astronomical target coordinates given in right ascension and declination to detector pixel coordinates for the MIT-led NASA Transiting Exoplanet Survey Satellite (TESS) spacecraft. The program can also provide detector pixel coordinates for a star by TESS input catalog identifier number and common astronomical name. Tess-point outputs the observing sector number, camera number, detector number, and pixel column and row. Original programming in C and focal plane geometry solutions by Alan Levine (MIT). This python translation by Christopher J. Burke (MIT)", 13 | "applicationCategory": "Astronomy", 14 | "developmentStatus": "active", 15 | "funder": { 16 | "@type": "Organization", 17 | "name": "MIT and NASA" 18 | }, 19 | "keywords": [ 20 | "astronomy", 21 | "coordinates", 22 | "TESS", 23 | "NASA" 24 | ], 25 | "programmingLanguage": [ 26 | "Python" 27 | ], 28 | "author": [ 29 | { 30 | "@type": "Person", 31 | "@id": "https://orcid.org/0000-0002-7754-9486", 32 | "givenName": "Christopher J.", 33 | "familyName": "Burke", 34 | "affiliation": { 35 | "@type": "Organization", 36 | "name": "MIT" 37 | } 38 | }, 39 | { 40 | "@type": "Person", 41 | "givenName": "Alan", 42 | "familyName": "Levine", 43 | "affiliation": { 44 | "@type": "Organization", 45 | "name": "MIT" 46 | } 47 | }, 48 | { 49 | "@type": "Person", 50 | "@id": "https://orcid.org/0000-0002-9113-7162", 51 | "givenName": "Michael", 52 | "familyName": "Fausnaugh", 53 | "affiliation": { 54 | "@type": "Organization", 55 | "name": "MIT" 56 | } 57 | }, 58 | { 59 | "@type": "Person", 60 | "@id": "https://orcid.org/0000-0001-6763-6562", 61 | "givenName": "Roland", 62 | "familyName": "Vanderspek", 63 | "affiliation": { 64 | "@type": "Organization", 65 | "name": "MIT" 66 | } 67 | }, 68 | { 69 | "@type": "Person", 70 | "@id": "https://orcid.org/0000-0001-7139-2724", 71 | "givenName": "Thomas", 72 | "familyName": "Barclay", 73 | "affiliation": { 74 | "@type": "Organization", 75 | "name": "NASA GSFC; Univ. of Maryland Baltimore County" 76 | } 77 | }, 78 | { 79 | "@type": "Person", 80 | "@id": "https://orcid.org/0000-0002-2990-7613", 81 | "givenName": "Jessica E.", 82 | "familyName": "Libby-Roberts", 83 | "affiliation": { 84 | "@type": "Organization", 85 | "name": "Univ. of Colorado" 86 | } 87 | }, 88 | { 89 | "@type": "Person", 90 | "@id": "https://orcid.org/0000-0003-2528-3409", 91 | "givenName": "Brett", 92 | "familyName": "Morris", 93 | "affiliation": { 94 | "@type": "Organization", 95 | "name": "Univ. of Washington" 96 | } 97 | }, 98 | { 99 | "@type": "Person", 100 | "givenName": "Brigitta", 101 | "familyName": "Sipocz", 102 | "affiliation": { 103 | "@type": "Organization", 104 | "name": "Univ. of Washington" 105 | } 106 | }, 107 | { 108 | "@type": "Person", 109 | "@id": "https://orcid.org/0000-0001-8852-499X", 110 | "givenName": "Martin", 111 | "familyName": "Owens" 112 | }, 113 | { 114 | "@type": "Person", 115 | "@id": "https://orcid.org/0000-0002-9464-8101", 116 | "givenName": "Adina D.", 117 | "familyName": "Feinstein", 118 | "affiliation": { 119 | "@type": "Organization", 120 | "name": "Univ. of Chicago; NSF Graduate Research Fellow" 121 | } 122 | }, 123 | { 124 | "@type": "Person", 125 | "@id": "https://orcid.org/0000-0001-5121-5560", 126 | "givenName": "Joao", 127 | "familyName": "Camacho", 128 | "affiliation": { 129 | "@type": "Organization", 130 | "name": "Institute of Astrophysics and Space Sciences; Univ. of Porto" 131 | } 132 | } 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /example_use_tess_stars2py_byfunction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jan 6 09:14:04 2019 5 | 6 | @author: cjburke 7 | """ 8 | 9 | import numpy as np 10 | from tess_stars2px import tess_stars2px_function_entry 11 | 12 | if __name__ == '__main__': 13 | # This is an example of using tess_stars2px functionality 14 | # from a program rather than the typical command line interface 15 | # the function example only takes ra and dec [deg] 16 | # Your program should provide these 17 | # The other ways of getting coordinates 'TIC MAST query' 'By name' 'file list' 18 | # are not supported in the wrapper function. Just ra and decs 19 | # Location for pi mensae 20 | ra = 84.291188 21 | dec = -80.469119 22 | ticid = 261136679 # code doesn't actually use ticid, so this can be 23 | # any integer you like. It is included just for convenience of 24 | # keeping track of target in the output because 25 | # for a given target it is not known ahead of time how many 26 | # output entries a target will have. Thus the user 27 | # should make sure to match the output ticid (outID) 28 | # with the ticid they are using 29 | outID, outEclipLong, outEclipLat, outSec, outCam, outCcd, \ 30 | outColPix, outRowPix, scinfo = tess_stars2px_function_entry( 31 | ticid, ra, dec) 32 | for i in range(len(outID)): 33 | print('{0:d} {1:d} {2:d} {3:d} {4:f} {5:f}'.format(outID[i], outSec[i], \ 34 | outCam[i], outCcd[i], outColPix[i], outRowPix[i])) 35 | 36 | # For efficiency purposes if you save scinfo between calls 37 | # you will save time in setting up the the telescope fields 38 | outID, outEclipLong, outEclipLat, outSec, outCam, outCcd, \ 39 | outColPix, outRowPix, scinfo = tess_stars2px_function_entry( 40 | ticid, ra, dec, scInfo=scinfo) 41 | print('Faster to re-use scinfo in repeated calls') 42 | for i in range(len(outID)): 43 | print('{0:d} {1:d} {2:d} {3:d} {4:f} {5:f}'.format(outID[i], outSec[i], \ 44 | outCam[i], outCcd[i], outColPix[i], outRowPix[i])) 45 | 46 | print('Also accepts multiple targets') 47 | ra = np.array([219.90085,10.897379], dtype=np.float) 48 | dec = np.array([-60.835619,-17.986606], dtype=np.float) 49 | ticid = np.array([0,1], dtype=np.int) 50 | outID, outEclipLong, outEclipLat, outSec, outCam, outCcd, \ 51 | outColPix, outRowPix, scinfo = tess_stars2px_function_entry( 52 | ticid, ra, dec, scInfo=scinfo) 53 | for i in range(len(outID)): 54 | print('{0:d} {1:d} {2:d} {3:d} {4:f} {5:f}'.format(outID[i], outSec[i], \ 55 | outCam[i], outCcd[i], outColPix[i], outRowPix[i])) 56 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="tess-point", 8 | version="0.9.2", 9 | author="Christopher J. Burke", 10 | author_email="tesshelp@bigbang.gsfc.nasa.gov", 11 | description="Determine pixel coordinates for TESS targets", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/christopherburke/tess-point", 15 | license="MIT", 16 | install_requires=["numpy", "astropy", "scipy"], 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Intended Audience :: Science/Research", 22 | "Topic :: Scientific/Engineering :: Astronomy", 23 | ], 24 | py_modules=["tess_stars2px"], 25 | ) 26 | -------------------------------------------------------------------------------- /tess_stars2px.py: -------------------------------------------------------------------------------- 1 | """ 2 | tess_stars2px.py - High precision TESS pointing tool. 3 | Convert target coordinates given in Right Ascension and Declination to 4 | TESS detector pixel coordinates for the prime mission TESS observing 5 | sectors (Year 1 & 2), Extendend mission Years 3-5. 6 | Can also query MAST to obtain detector 7 | pixel coordinates for a star by TIC ID or common star name (must be online for this option). 8 | 9 | USAGE to display command line arguments: 10 | python tess_stars2px.py -h 11 | 12 | AUTHORS: Original programming in C and focal plane geometry solutions 13 | by Alan Levine (MIT) 14 | This python translation by Christopher J. Burke (MIT) 15 | Testing and focal plane geometry refinements by Michael Fausnaugh & 16 | Roland Vanderspek (MIT) 17 | Testing by Thomas Barclay (NASA Goddard) & 18 | Jessica Roberts (Univ. of Colorado) 19 | Sesame queries by Brett Morris (UW) 20 | Proxy Support added by Dishendra Mishra 21 | Deprecation warnings correctsion by Ethan Kruse 22 | Updates by Tyler Pritchard, Christina Hedges 23 | 24 | VERSION: 0.9.2 25 | 26 | WHAT'S NEW: 27 | -Year 8 pointings for Sectors 97-107 now available 28 | -Year 7 pointings for Sectors 84-96 now available 29 | -Year 6 pointings for Sectors 70-83 now available 30 | -Deprecation Warning Corrections 31 | -Year 5 pointings (Sectors 56-69) now available 32 | -Added Sector Pointing override file input 33 | Supports mission planning as well as enabling the user 34 | to speed up the calculation by only searching a subset of all sectors 35 | -Bug correction for aberration. Only impacts if you were using 36 | aberration flag (-a) WITHOUT the single sector flag (-s). In other words, 37 | does not affect users that did not use --aberrate or -aberrate with -s 38 | 39 | NOTES: 40 | -Pointing table is for TESS Year 1 - 5 (Sectors 1-69) 41 | -Pointing table is unofficial, and the pointings may change. 42 | -See https://tess.mit.edu/observations/ for latest TESS pointing table 43 | -Pointing prediction algorithm is similar to internally at MIT for 44 | target management. However, hard coded focal plane geometry is not 45 | up to date and may contain inaccurate results. 46 | -Testing shows pointing with this tool should be accurate to better than 47 | a pixel, but without including aberration effects, ones algorithm 48 | adopted for centroiding highly assymmetric point-spread function 49 | at edge of 50 | camera, and by-eye source location, a 2 pixel accuracy estimate is 51 | warranted. There is an approximate aberration option now available 52 | -The output pixel coordinates assume the ds9 convention with 53 | 1,1 being the middle of the lower left corner pixel. 54 | -No corrections for velocity aberration are calculated by default. 55 | Potentially more accurate 56 | results can be obtained if the target RA and Declination coordinates 57 | have aberration effects applied. An aberration approximation is available 58 | by using the -a flag. The aberration approximation assumes Earth motion without 59 | taking into account the TESS spacecraft motion around Earth. 60 | -For proposals to the TESS science office or directors discretionary time, 61 | please consult the TESS prediction webtool available at 62 | https://heasarc.gsfc.nasa.gov/cgi-bin/tess/webtess/wtv.py 63 | for official identification of 'observable' targets. However, 64 | if your proposal depends on a single or few targets, then this tool is 65 | helpful to further refine the likelihood of the target being available 66 | on the detectors. 67 | -The calibrated FFI fits file release at MAST and calibrated by 68 | NASA Ames SPOC will have WCS information available to 69 | supplant this code. The WCS generation is independent of the 70 | focal plane geometry model employed in this code and will give 71 | different results at the pixel level. However, the WCS information 72 | is not available until the FFI files are released, whereas 73 | this code can predict positions in advance of data release. 74 | -Hard coded focal plane geometry parameters from rfpg5_c1kb.txt 75 | 76 | 77 | TODOS: 78 | -Time dependent Focal plane geometry 79 | 80 | DEPENDENCIES: 81 | python 3+ 82 | astropy 83 | numpy 84 | 85 | SPECIAL THANKS TO: 86 | Includes code from the python MAST query examples 87 | https://mast.stsci.edu/api/v0/pyex.html 88 | 89 | IMPLEMENTATION DETAILS: 90 | In summary, the code begins with a space craft bore site pointing in RA, 91 | Dec, and roll angle. A series of Euler angle translation matrices 92 | are calculated based upon the space craft bore site. Next the target 93 | coordinates in RA and Dec are translated to the space craft bore site 94 | frame. Next, the target coordinates are translated to each of the four 95 | TESS camera frames. Once target coordinates are translated to the 96 | camera frame the radial position of the target relative to the camera 97 | center is checked to see if it is potentially in the camera field of view. 98 | If so, the focal plane position is calculated using a radial polynomial 99 | model with a constant term and terms the even powers (2nd ,4th , and 8th). 100 | Rotations are applied to convert the on sky positions to the detector 101 | readout directions. 102 | 103 | MIT License 104 | Copyright (c) 2018 Christopher J Burke 105 | 106 | Permission is hereby granted, free of charge, to any person obtaining a copy 107 | of this software and associated documentation files (the "Software"), to deal 108 | in the Software without restriction, including without limitation the rights 109 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 110 | copies of the Software, and to permit persons to whom the Software is 111 | furnished to do so, subject to the following conditions: 112 | 113 | The above copyright notice and this permission notice shall be included in all 114 | copies or substantial portions of the Software. 115 | 116 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 117 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 118 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 119 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 120 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 121 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 122 | SOFTWARE. 123 | """ 124 | 125 | import numpy as np 126 | import os 127 | import argparse 128 | from astropy.coordinates import SkyCoord 129 | import astropy.units as u 130 | from astropy.time import Time 131 | import sys 132 | import datetime 133 | import json 134 | 135 | try: # Python 3.x 136 | from urllib.parse import quote as urlencode 137 | from urllib.request import urlretrieve 138 | from urllib.parse import urlparse 139 | except ImportError: # Python 2.x 140 | from urllib import pathname2url as urlencode 141 | from urllib import urlretrieve 142 | from urlparse import urlparse 143 | try: # Python 3.x 144 | import http.client as httplib 145 | except ImportError: # Python 2.x 146 | import httplib 147 | import scipy.optimize as opt 148 | import base64 149 | 150 | max_sector = 107 151 | 152 | 153 | class Levine_FPG: 154 | """Al Levine Focal Plane Geometry Methods 155 | Translated from starspx6.c 156 | INPUT: 157 | sc_ra_dec_roll = numpy array of the SpaceCraft boresite (sc Z-axis) 158 | ra, dec, and roll [deg] 159 | The roll angle is in RA, Dec space clockwise relative to the celestial 160 | pole. roll angle = 0 [deg] implies space craft X-axis points N celestial (increasing dec) 161 | roll angle = 90 [deg] implies sc X-axis points towards increasing/decreasing (?) RA 162 | *** In practice there is a separate fpg file for each of the four cameras *** 163 | rmat1[3,3] = is the rotation matrix from ra&dec to spacecraft boresite coords 164 | rmat4[NCAM,3,3] - is the rotation matrix from ra&dec to NCAM coords 165 | """ 166 | 167 | parm_dict_list = [{}, {}, {}, {}] 168 | NCAM = 4 # Number of Cameras 169 | NCCD = 4 # Number of CCDs per Camera 170 | 171 | def __init__(self, sc_ra_dec_roll=None, fpg_file_list=None): 172 | self.eulcam = np.zeros((self.NCAM, 3), dtype=np.double) 173 | self.optcon = np.zeros((self.NCAM, 6), dtype=np.double) 174 | self.ccdxy0 = np.zeros((self.NCAM, self.NCCD, 2), dtype=np.double) 175 | self.pixsz = np.zeros((self.NCAM, self.NCCD, 2), dtype=np.double) 176 | self.ccdang = np.zeros((self.NCAM, self.NCCD), dtype=np.double) 177 | self.ccdtilt = np.zeros((self.NCAM, self.NCCD, 2), dtype=np.double) 178 | self.asymang = np.zeros((self.NCAM,), dtype=np.double) 179 | self.asymfac = np.zeros((self.NCAM,), dtype=np.double) 180 | self.rmat1 = np.zeros((3, 3), dtype=np.double) 181 | self.rmat4 = np.zeros((self.NCAM, 3, 3), dtype=np.double) 182 | self.havePointing = False 183 | # Read in the fpg parameter files 184 | self.read_all_levine_fpg_files(fpg_file_list) 185 | # Generate rotation matrices if ra dec and roll values given 186 | if not sc_ra_dec_roll is None: 187 | # go from sky to spacecraft 188 | self.sky_to_sc_mat(sc_ra_dec_roll) 189 | # Go from spacecraft to each camera's coords 190 | for icam in range(self.NCAM): 191 | cureul = self.eulcam[icam, :] 192 | rmat2 = self.sc_to_cam_mat(cureul) 193 | self.rmat4[icam] = np.matmul(rmat2, self.rmat1) 194 | self.havePointing = True 195 | 196 | def read_all_levine_fpg_files(self, fpg_file_list=None): 197 | default_fpg_file_list = [ 198 | "fpg_pars.txt-", 199 | "fpg_pars.txt-", 200 | "fpg_pars.txt-", 201 | "fpg_pars.txt-", 202 | ] 203 | # For each camera read in the separate fpg parameter file 204 | for icam in range(self.NCAM): 205 | if fpg_file_list == None: 206 | fpg_file = default_fpg_file_list[icam] 207 | else: 208 | fpg_file = fpg_file_list[icam] 209 | self.read_levine_fpg_file(icam, fpg_file) 210 | # We now have parameters for all 4 cameras in the parm_dict_list 211 | # parse the dictionary values into the working numpy arrays 212 | for icam in range(self.NCAM): 213 | pd = self.parm_dict_list[icam] 214 | self.eulcam[icam][0] = pd["ang1_cam1"] 215 | self.eulcam[icam][1] = pd["ang2_cam1"] 216 | self.eulcam[icam][2] = pd["ang3_cam1"] 217 | self.optcon[icam][0] = pd["fl_cam1"] 218 | self.optcon[icam][1] = pd["opt_coef1_cam1"] 219 | self.optcon[icam][2] = pd["opt_coef2_cam1"] 220 | self.optcon[icam][3] = pd["opt_coef3_cam1"] 221 | self.optcon[icam][4] = pd["opt_coef4_cam1"] 222 | self.optcon[icam][5] = pd["opt_coef5_cam1"] 223 | self.asymang[icam] = pd["asymang_cam1"] 224 | self.asymfac[icam] = pd["asymfac_cam1"] 225 | for iccd in range(self.NCCD): 226 | self.ccdxy0[icam][iccd][0] = pd["x0_ccd{0:1d}_cam1".format(iccd + 1)] 227 | self.ccdxy0[icam][iccd][1] = pd["y0_ccd{0:1d}_cam1".format(iccd + 1)] 228 | self.pixsz[icam][iccd][0] = pd["pix_x_ccd{0:1d}_cam1".format(iccd + 1)] 229 | self.pixsz[icam][iccd][1] = pd["pix_y_ccd{0:1d}_cam1".format(iccd + 1)] 230 | self.ccdang[icam][iccd] = pd["ang_ccd{0:1d}_cam1".format(iccd + 1)] 231 | self.ccdtilt[icam][iccd][0] = pd[ 232 | "tilt_x_ccd{0:1d}_cam1".format(iccd + 1) 233 | ] 234 | self.ccdtilt[icam][iccd][1] = pd[ 235 | "tilt_y_ccd{0:1d}_cam1".format(iccd + 1) 236 | ] 237 | 238 | def read_levine_fpg_file(self, icam, fpg_file): 239 | gotParm = False 240 | parm_dict = {} 241 | if os.path.isfile(fpg_file): 242 | try: 243 | fpin = open(fpg_file, "r") 244 | # Read in parameters 245 | dtypeseq = ["U20", "i4", "f16"] 246 | dataBlock = np.genfromtxt(fpin, dtype=dtypeseq) 247 | parm_keys = dataBlock["f0"] 248 | parm_fitted_flags = dataBlock["f1"] 249 | parm_values = dataBlock["f2"] 250 | # Now build dictionary of the parameters 251 | for i in range(len(parm_keys)): 252 | parm_dict[parm_keys[i]] = parm_values[i] 253 | self.parm_dict_list[icam] = parm_dict 254 | gotParm = True 255 | print("Successful Focal Plane Geometry Read From {0}".format(fpg_file)) 256 | except: 257 | print( 258 | "Could not open {0}! Using Hard-coded Focal Plane Geometry from Levine_FPG read_levine_fpg_file()".format( 259 | fpg_file 260 | ) 261 | ) 262 | # If anything goes wrong with reading in parameters revert to hard coded version 263 | # or file was never given and default_fpg_file does not exist 264 | if not gotParm: 265 | # print('Using Hard-coded Focal Plane Geometry from Levine_FPG read_levine_fpg_file') 266 | # *** For now this hard code is just a filler need to actually fill in values for all cameras separately 267 | # to prepare parameters for dictionary 268 | # awk -v q="'" -v d=":" '{print q $1 q d $3 ",\"}' rfpg5_c1kb.txt 269 | if icam == 0: 270 | parm_dict = { 271 | "ang1_cam1": 0.101588, 272 | "ang2_cam1": -36.022035, 273 | "ang3_cam1": 90.048315, 274 | "fl_cam1": 145.948116, 275 | "opt_coef1_cam1": 1.00000140, 276 | "opt_coef2_cam1": 0.24779006, 277 | "opt_coef3_cam1": -0.22681254, 278 | "opt_coef4_cam1": 10.78243356, 279 | "opt_coef5_cam1": -34.97817276, 280 | "asymang_cam1": 0.00000000, 281 | "asymfac_cam1": 1.00000000, 282 | "x0_ccd1_cam1": 31.573417, 283 | "y0_ccd1_cam1": 31.551637, 284 | "pix_x_ccd1_cam1": 0.015000, 285 | "pix_y_ccd1_cam1": 0.015000, 286 | "ang_ccd1_cam1": 179.980833, 287 | "tilt_x_ccd1_cam1": 0.000000, 288 | "tilt_y_ccd1_cam1": 0.000000, 289 | "x0_ccd2_cam1": -0.906060, 290 | "y0_ccd2_cam1": 31.536148, 291 | "pix_x_ccd2_cam1": 0.015000, 292 | "pix_y_ccd2_cam1": 0.015000, 293 | "ang_ccd2_cam1": 180.000000, 294 | "tilt_x_ccd2_cam1": 0.000000, 295 | "tilt_y_ccd2_cam1": 0.000000, 296 | "x0_ccd3_cam1": -31.652818, 297 | "y0_ccd3_cam1": -31.438350, 298 | "pix_x_ccd3_cam1": 0.015000, 299 | "pix_y_ccd3_cam1": 0.015000, 300 | "ang_ccd3_cam1": -0.024851, 301 | "tilt_x_ccd3_cam1": 0.000000, 302 | "tilt_y_ccd3_cam1": 0.000000, 303 | "x0_ccd4_cam1": 0.833161, 304 | "y0_ccd4_cam1": -31.458180, 305 | "pix_x_ccd4_cam1": 0.015000, 306 | "pix_y_ccd4_cam1": 0.015000, 307 | "ang_ccd4_cam1": 0.001488, 308 | "tilt_x_ccd4_cam1": 0.000000, 309 | "tilt_y_ccd4_cam1": 0.000000, 310 | } 311 | 312 | if icam == 1: 313 | parm_dict = { 314 | "ang1_cam1": -0.179412, 315 | "ang2_cam1": -12.017260, 316 | "ang3_cam1": 90.046500, 317 | "fl_cam1": 145.989933, 318 | "opt_coef1_cam1": 1.00000140, 319 | "opt_coef2_cam1": 0.24069345, 320 | "opt_coef3_cam1": 0.15391120, 321 | "opt_coef4_cam1": 4.05433503, 322 | "opt_coef5_cam1": 3.43136895, 323 | "asymang_cam1": 0.00000000, 324 | "asymfac_cam1": 1.00000000, 325 | "x0_ccd1_cam1": 31.653635, 326 | "y0_ccd1_cam1": 31.470291, 327 | "pix_x_ccd1_cam1": 0.015000, 328 | "pix_y_ccd1_cam1": 0.015000, 329 | "ang_ccd1_cam1": 180.010890, 330 | "tilt_x_ccd1_cam1": 0.000000, 331 | "tilt_y_ccd1_cam1": 0.000000, 332 | "x0_ccd2_cam1": -0.827405, 333 | "y0_ccd2_cam1": 31.491388, 334 | "pix_x_ccd2_cam1": 0.015000, 335 | "pix_y_ccd2_cam1": 0.015000, 336 | "ang_ccd2_cam1": 180.000000, 337 | "tilt_x_ccd2_cam1": 0.000000, 338 | "tilt_y_ccd2_cam1": 0.000000, 339 | "x0_ccd3_cam1": -31.543794, 340 | "y0_ccd3_cam1": -31.550699, 341 | "pix_x_ccd3_cam1": 0.015000, 342 | "pix_y_ccd3_cam1": 0.015000, 343 | "ang_ccd3_cam1": -0.006624, 344 | "tilt_x_ccd3_cam1": 0.000000, 345 | "tilt_y_ccd3_cam1": 0.000000, 346 | "x0_ccd4_cam1": 0.922834, 347 | "y0_ccd4_cam1": -31.557268, 348 | "pix_x_ccd4_cam1": 0.015000, 349 | "pix_y_ccd4_cam1": 0.015000, 350 | "ang_ccd4_cam1": -0.015464, 351 | "tilt_x_ccd4_cam1": 0.000000, 352 | "tilt_y_ccd4_cam1": 0.000000, 353 | } 354 | 355 | if icam == 2: 356 | parm_dict = { 357 | "ang1_cam1": 0.066596, 358 | "ang2_cam1": 12.007750, 359 | "ang3_cam1": -89.889085, 360 | "fl_cam1": 146.006602, 361 | "opt_coef1_cam1": 1.00000140, 362 | "opt_coef2_cam1": 0.23452229, 363 | "opt_coef3_cam1": 0.33552009, 364 | "opt_coef4_cam1": 1.92009863, 365 | "opt_coef5_cam1": 12.48880182, 366 | "asymang_cam1": 0.00000000, 367 | "asymfac_cam1": 1.00000000, 368 | "x0_ccd1_cam1": 31.615486, 369 | "y0_ccd1_cam1": 31.413644, 370 | "pix_x_ccd1_cam1": 0.015000, 371 | "pix_y_ccd1_cam1": 0.015000, 372 | "ang_ccd1_cam1": 179.993948, 373 | "tilt_x_ccd1_cam1": 0.000000, 374 | "tilt_y_ccd1_cam1": 0.000000, 375 | "x0_ccd2_cam1": -0.832993, 376 | "y0_ccd2_cam1": 31.426621, 377 | "pix_x_ccd2_cam1": 0.015000, 378 | "pix_y_ccd2_cam1": 0.015000, 379 | "ang_ccd2_cam1": 180.000000, 380 | "tilt_x_ccd2_cam1": 0.000000, 381 | "tilt_y_ccd2_cam1": 0.000000, 382 | "x0_ccd3_cam1": -31.548296, 383 | "y0_ccd3_cam1": -31.606976, 384 | "pix_x_ccd3_cam1": 0.015000, 385 | "pix_y_ccd3_cam1": 0.015000, 386 | "ang_ccd3_cam1": 0.000298, 387 | "tilt_x_ccd3_cam1": 0.000000, 388 | "tilt_y_ccd3_cam1": 0.000000, 389 | "x0_ccd4_cam1": 0.896018, 390 | "y0_ccd4_cam1": -31.569542, 391 | "pix_x_ccd4_cam1": 0.015000, 392 | "pix_y_ccd4_cam1": 0.015000, 393 | "ang_ccd4_cam1": -0.006464, 394 | "tilt_x_ccd4_cam1": 0.000000, 395 | "tilt_y_ccd4_cam1": 0.000000, 396 | } 397 | 398 | if icam == 3: 399 | parm_dict = { 400 | "ang1_cam1": 0.030756, 401 | "ang2_cam1": 35.978116, 402 | "ang3_cam1": -89.976802, 403 | "fl_cam1": 146.039793, 404 | "opt_coef1_cam1": 1.00000140, 405 | "opt_coef2_cam1": 0.23920416, 406 | "opt_coef3_cam1": 0.13349450, 407 | "opt_coef4_cam1": 4.77768896, 408 | "opt_coef5_cam1": -1.75114744, 409 | "asymang_cam1": 0.00000000, 410 | "asymfac_cam1": 1.00000000, 411 | "x0_ccd1_cam1": 31.575820, 412 | "y0_ccd1_cam1": 31.316510, 413 | "pix_x_ccd1_cam1": 0.015000, 414 | "pix_y_ccd1_cam1": 0.015000, 415 | "ang_ccd1_cam1": 179.968217, 416 | "tilt_x_ccd1_cam1": 0.000000, 417 | "tilt_y_ccd1_cam1": 0.000000, 418 | "x0_ccd2_cam1": -0.890877, 419 | "y0_ccd2_cam1": 31.363511, 420 | "pix_x_ccd2_cam1": 0.015000, 421 | "pix_y_ccd2_cam1": 0.015000, 422 | "ang_ccd2_cam1": 180.000000, 423 | "tilt_x_ccd2_cam1": 0.000000, 424 | "tilt_y_ccd2_cam1": 0.000000, 425 | "x0_ccd3_cam1": -31.630470, 426 | "y0_ccd3_cam1": -31.716942, 427 | "pix_x_ccd3_cam1": 0.015000, 428 | "pix_y_ccd3_cam1": 0.015000, 429 | "ang_ccd3_cam1": -0.024359, 430 | "tilt_x_ccd3_cam1": 0.000000, 431 | "tilt_y_ccd3_cam1": 0.000000, 432 | "x0_ccd4_cam1": 0.824159, 433 | "y0_ccd4_cam1": -31.728751, 434 | "pix_x_ccd4_cam1": 0.015000, 435 | "pix_y_ccd4_cam1": 0.015000, 436 | "ang_ccd4_cam1": -0.024280, 437 | "tilt_x_ccd4_cam1": 0.000000, 438 | "tilt_y_ccd4_cam1": 0.000000, 439 | } 440 | 441 | self.parm_dict_list[icam] = parm_dict 442 | 443 | def sky_to_sc_mat(self, sc_ra_dec_roll): 444 | """Calculate the rotation matrix that will convert a vector in ra&dec 445 | into the spacecraft boresite frame 446 | """ 447 | deg2rad = np.pi / 180.0 448 | # Define the 3 euler angles of rotation 449 | xeul = np.zeros((3,), dtype=np.double) 450 | xeul[0] = deg2rad * sc_ra_dec_roll[0] 451 | xeul[1] = np.pi / 2.0 - deg2rad * sc_ra_dec_roll[1] 452 | xeul[2] = deg2rad * sc_ra_dec_roll[2] + np.pi 453 | # Generate the rotation matrix from the 3 euler angles 454 | self.rmat1 = self.eulerm323(xeul) 455 | 456 | def sc_to_cam_mat(self, eul): 457 | """Calculate the rotation matrix that will convert a vector in spacecraft 458 | into the a camera's coords 459 | """ 460 | deg2rad = np.pi / 180.0 461 | # Generate the rotation matrix from the 3 euler angles 462 | xeul = deg2rad * eul 463 | return self.eulerm323(xeul) 464 | 465 | def eulerm323(self, eul): 466 | mat1 = self.rotm1(2, eul[0]) 467 | mat2 = self.rotm1(1, eul[1]) 468 | mata = np.matmul(mat2, mat1) 469 | mat1 = self.rotm1(2, eul[2]) 470 | rmat = np.matmul(mat1, mata) 471 | return rmat 472 | 473 | def rotm1(self, ax, ang): 474 | mat = np.zeros((3, 3), dtype=np.double) 475 | n1 = ax 476 | n2 = np.mod((n1 + 1), 3) 477 | n3 = np.mod((n2 + 1), 3) 478 | sinang = np.sin(ang) 479 | cosang = np.cos(ang) 480 | mat[n1][n1] = 1.0 481 | mat[n2][n2] = cosang 482 | mat[n3][n3] = cosang 483 | mat[n2][n3] = sinang 484 | mat[n3][n2] = -sinang 485 | return mat 486 | 487 | def sphereToCart(self, ras, decs): 488 | """Convert 3d spherical coordinates to cartesian""" 489 | deg2rad = np.pi / 180.0 490 | rarads = deg2rad * ras 491 | decrads = deg2rad * decs 492 | sinras = np.sin(rarads) 493 | cosras = np.cos(rarads) 494 | sindecs = np.sin(decrads) 495 | cosdecs = np.cos(decrads) 496 | vec0s = cosras * cosdecs 497 | vec1s = sinras * cosdecs 498 | vec2s = sindecs 499 | return vec0s, vec1s, vec2s 500 | 501 | def cartToSphere(self, vec): 502 | ra = 0.0 503 | dec = 0.0 504 | norm = np.sqrt(np.sum(vec * vec)) 505 | if norm > 0.0: 506 | dec = np.arcsin(vec[2] / norm) 507 | if (not vec[0] == 0.0) or (not vec[1] == 0.0): 508 | ra = np.arctan2(vec[1], vec[0]) 509 | ra = np.mod(ra, 2.0 * np.pi) 510 | return ra, dec 511 | 512 | def star_in_fov(self, lng, lat): 513 | deg2rad = np.pi / 180.0 514 | inView = False 515 | if lat > 70.0: 516 | vec0, vec1, vec2 = self.sphereToCart(lng, lat) 517 | vec = np.array([vec0, vec1, vec2], dtype=np.double) 518 | norm = np.sqrt(np.sum(vec * vec)) 519 | if norm > 0.0: 520 | vec = vec / norm 521 | xlen = np.abs(np.arctan(vec[0] / vec[2])) 522 | ylen = np.abs(np.arctan(vec[1] / vec[2])) 523 | if (xlen <= (12.5 * deg2rad)) and (ylen <= (12.5 * deg2rad)): 524 | inView = True 525 | return inView 526 | 527 | def optics_fp(self, icam, lng_deg, lat_deg): 528 | deg2rad = np.pi / 180.0 529 | thetar = np.pi / 2.0 - (lat_deg * deg2rad) 530 | tanth = np.tan(thetar) 531 | cphi = np.cos(deg2rad * lng_deg) 532 | sphi = np.sin(deg2rad * lng_deg) 533 | rfp0 = self.optcon[icam][0] * tanth 534 | noptcon = len(self.optcon[icam]) 535 | ii = np.arange(1, noptcon) 536 | rfp = np.sum(self.optcon[icam][1:] * np.power(tanth, 2.0 * (ii - 1))) 537 | xytmp = np.zeros((2,), dtype=np.double) 538 | xytmp[0] = -cphi * rfp0 * rfp 539 | xytmp[1] = -sphi * rfp0 * rfp 540 | return self.make_az_asym(icam, xytmp) 541 | 542 | def fp_optics(self, icam, xyfp): 543 | deg2rad = np.pi / 180.0 544 | xy = self.rev_az_asym(icam, xyfp) 545 | rfp_times_rfp0 = np.sqrt(xy[0] * xy[0] + xy[1] * xy[1]) 546 | phirad = np.arctan2(-xy[1], -xy[0]) 547 | phideg = phirad / deg2rad 548 | if phideg < 0.0: 549 | phideg += 360.0 550 | thetarad = self.tanth_of_r(icam, rfp_times_rfp0) 551 | thetadeg = thetarad / deg2rad 552 | lng_deg = phideg 553 | lat_deg = 90.0 - thetadeg 554 | return lng_deg, lat_deg 555 | 556 | def r_of_tanth(self, icam, z): 557 | tanth = np.tan(z) 558 | rfp0 = self.optcon[icam][0] * tanth 559 | noptcon = len(self.optcon[icam]) 560 | ii = np.arange(1, noptcon) 561 | rfp = np.sum(self.optcon[icam][1:] * np.power(tanth, 2.0 * (ii - 1))) 562 | return rfp0 * rfp 563 | 564 | def tanth_of_r(self, icam, rprp0): 565 | if np.abs(rprp0) > 1.0e-10: 566 | c0 = self.optcon[icam][0] 567 | zi = np.arctan(np.sqrt(rprp0) / c0) 568 | 569 | def minFunc(z, icam, rp): 570 | rtmp = self.r_of_tanth(icam, z) 571 | return (rtmp - rprp0) * (rtmp - rprp0) 572 | 573 | optResult = opt.minimize( 574 | minFunc, 575 | [zi], 576 | args=(icam, rprp0), 577 | method="Nelder-Mead", 578 | tol=1.0e-10, 579 | options={"maxiter": 500}, 580 | ) 581 | # print(optResult) 582 | return optResult.x[0] 583 | else: 584 | return 0.0 585 | 586 | def make_az_asym(self, icam, xy): 587 | xyp = self.xyrotate(self.asymang[icam], xy) 588 | xypa = np.zeros_like(xyp) 589 | xypa[0] = self.asymfac[icam] * xyp[0] 590 | xypa[1] = xyp[1] 591 | xyout = self.xyrotate(-self.asymang[icam], xypa) 592 | return xyout 593 | 594 | def rev_az_asym(self, icam, xyin): 595 | xyp = self.xyrotate(self.asymang[icam], xyin) 596 | xypa = np.zeros_like(xyp) 597 | xypa[0] = xyp[0] / self.asymfac[icam] 598 | xypa[1] = xyp[1] 599 | xyout = self.xyrotate(-self.asymang[icam], xypa) 600 | return xyout 601 | 602 | def xyrotate(self, angle_deg, xin): 603 | deg2rad = np.pi / 180.0 604 | ca = np.cos(deg2rad * angle_deg) 605 | sa = np.sin(deg2rad * angle_deg) 606 | xyout = np.zeros_like(xin) 607 | xyout[0] = ca * xin[0] + sa * xin[1] 608 | xyout[1] = -sa * xin[0] + ca * xin[1] 609 | return xyout 610 | 611 | def mm_to_pix(self, icam, xy): 612 | """Convert focal plane to pixel location also need to add in the 613 | auxillary pixels added into FFIs 614 | """ 615 | CCDWD_T = 2048 616 | CCDHT_T = 2058 617 | ROWA = 44 618 | ROWB = 44 619 | COLDK_T = 20 620 | xya = np.copy(xy) 621 | xyb = np.zeros_like(xya) 622 | ccdpx = np.zeros_like(xya) 623 | fitpx = np.zeros_like(xya) 624 | if xya[0] >= 0.0: 625 | if xya[1] >= 0.0: 626 | iccd = 0 627 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 628 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 629 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 630 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 631 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 632 | fitpx[0] = (CCDWD_T - ccdpx[0]) + CCDWD_T + 2 * ROWA + ROWB - 1.0 633 | fitpx[1] = (CCDHT_T - ccdpx[1]) + CCDHT_T + 2 * COLDK_T - 1.0 634 | else: 635 | iccd = 3 636 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 637 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 638 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 639 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 640 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 641 | fitpx[0] = ccdpx[0] + CCDWD_T + 2 * ROWA + ROWB 642 | fitpx[1] = ccdpx[1] 643 | else: 644 | if xya[1] >= 0.0: 645 | iccd = 1 646 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 647 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 648 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 649 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 650 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 651 | fitpx[0] = (CCDWD_T - ccdpx[0]) + ROWA - 1.0 652 | fitpx[1] = (CCDHT_T - ccdpx[1]) + CCDHT_T + 2 * COLDK_T - 1.0 653 | else: 654 | iccd = 2 655 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 656 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 657 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 658 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 659 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 660 | fitpx[0] = ccdpx[0] + ROWA 661 | fitpx[1] = ccdpx[1] 662 | 663 | return iccd, ccdpx, fitpx 664 | 665 | def mm_to_pix_single_ccd(self, icam, xy, iccd): 666 | """Convert focal plane to pixel location also need to add in the 667 | auxillary pixels added into FFIs 668 | """ 669 | CCDWD_T = 2048 670 | CCDHT_T = 2058 671 | ROWA = 44 672 | ROWB = 44 673 | COLDK_T = 20 674 | xya = np.copy(xy) 675 | xyb = np.zeros_like(xya) 676 | ccdpx = np.zeros_like(xya) 677 | fitpx = np.zeros_like(xya) 678 | if iccd == 0: 679 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 680 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 681 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 682 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 683 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 684 | fitpx[0] = (CCDWD_T - ccdpx[0]) + CCDWD_T + 2 * ROWA + ROWB - 1.0 685 | fitpx[1] = (CCDHT_T - ccdpx[1]) + CCDHT_T + 2 * COLDK_T - 1.0 686 | if iccd == 3: 687 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 688 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 689 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 690 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 691 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 692 | fitpx[0] = ccdpx[0] + CCDWD_T + 2 * ROWA + ROWB 693 | fitpx[1] = ccdpx[1] 694 | if iccd == 1: 695 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 696 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 697 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 698 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 699 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 700 | fitpx[0] = (CCDWD_T - ccdpx[0]) + ROWA - 1.0 701 | fitpx[1] = (CCDHT_T - ccdpx[1]) + CCDHT_T + 2 * COLDK_T - 1.0 702 | if iccd == 2: 703 | xyb[0] = xya[0] - self.ccdxy0[icam][iccd][0] 704 | xyb[1] = xya[1] - self.ccdxy0[icam][iccd][1] 705 | xyccd = self.xyrotate(self.ccdang[icam][iccd], xyb) 706 | ccdpx[0] = (xyccd[0] / self.pixsz[icam][iccd][0]) - 0.5 707 | ccdpx[1] = (xyccd[1] / self.pixsz[icam][iccd][1]) - 0.5 708 | fitpx[0] = ccdpx[0] + ROWA 709 | fitpx[1] = ccdpx[1] 710 | 711 | return ccdpx, fitpx 712 | 713 | def pix_to_mm_single_ccd(self, icam, ccdpx, iccd): 714 | """convert pixel to mm focal plane position""" 715 | xyccd = np.zeros_like(ccdpx) 716 | xyccd[0] = (ccdpx[0] + 0.5) * self.pixsz[icam][iccd][0] 717 | xyccd[1] = (ccdpx[1] + 0.5) * self.pixsz[icam][iccd][1] 718 | xyb = self.xyrotate(-self.ccdang[icam][iccd], xyccd) 719 | xya = np.zeros_like(xyb) 720 | xya[0] = xyb[0] + self.ccdxy0[icam][iccd][0] 721 | xya[1] = xyb[1] + self.ccdxy0[icam][iccd][1] 722 | 723 | return xya 724 | 725 | def radec2pix(self, ras, decs): 726 | """After the rotation matrices are defined to the actual 727 | ra and dec to pixel coords mapping 728 | """ 729 | nStar = len(ras) 730 | inCamera = np.array([], dtype=int) 731 | ccdNum = np.array([], dtype=int) 732 | fitsxpos = np.array([], dtype=np.double) 733 | fitsypos = np.array([], dtype=np.double) 734 | ccdxpos = np.array([], dtype=np.double) 735 | ccdypos = np.array([], dtype=np.double) 736 | 737 | deg2rad = np.pi / 180.0 738 | if self.havePointing == True: 739 | # Convert ra and dec spherical coords to cartesian 740 | vec0s, vec1s, vec2s = self.sphereToCart(ras, decs) 741 | for i in range(nStar): 742 | curVec = np.array([vec0s[i], vec1s[i], vec2s[i]], dtype=np.double) 743 | # Find the new vector in all cameras 744 | for j in range(self.NCAM): 745 | # Do the rotation from ra dec coords to camera coords 746 | camVec = np.matmul(self.rmat4[j], curVec) 747 | # Get the longitude and latitude of camera coords position 748 | lng, lat = self.cartToSphere(camVec) 749 | lng = lng / deg2rad 750 | lat = lat / deg2rad 751 | if self.star_in_fov(lng, lat): 752 | # Get the xy focal plane position in mm 753 | xyfp = self.optics_fp(j, lng, lat) 754 | # Convert mm to pixels 755 | iccd, ccdpx, fitpx = self.mm_to_pix(j, xyfp) 756 | inCamera = np.append( 757 | inCamera, j + 1 758 | ) # Als code is base 0 convert to base 1 759 | ccdNum = np.append(ccdNum, iccd + 1) # "" 760 | fitsxpos = np.append(fitsxpos, fitpx[0]) 761 | fitsypos = np.append(fitsypos, fitpx[1]) 762 | ccdxpos = np.append(ccdxpos, ccdpx[0]) 763 | ccdypos = np.append(ccdypos, ccdpx[1]) 764 | 765 | else: 766 | print("Spacecraft Pointing Not specified!") 767 | 768 | return inCamera, ccdNum, fitsxpos, fitsypos, ccdxpos, ccdypos 769 | 770 | def radec2pix_nocheck_single(self, ras, decs, cam, iccd): 771 | """ 772 | ra and dec to pixel coords mapping 773 | With no checks and assuming a single target and detector 774 | Supports minimizing for reverse mode 775 | """ 776 | deg2rad = np.pi / 180.0 777 | # Convert ra and dec spherical coords to cartesian 778 | vec0s, vec1s, vec2s = self.sphereToCart(ras, decs) 779 | curVec = np.array([vec0s, vec1s, vec2s], dtype=np.double) 780 | j = cam 781 | # Do the rotation from ra dec coords to camera coords 782 | camVec = np.matmul(self.rmat4[j], curVec) 783 | # Get the longitude and latitude of camera coords position 784 | lng, lat = self.cartToSphere(camVec) 785 | lng = lng / deg2rad 786 | lat = lat / deg2rad 787 | # Get the xy focal plane position in mm 788 | xyfp = self.optics_fp(j, lng, lat) 789 | # Convert mm to pixels 790 | ccdpx, fitpx = self.mm_to_pix_single_ccd(j, xyfp, iccd) 791 | ccdNum = iccd + 1 792 | fitsxpos = fitpx[0] 793 | fitsypos = fitpx[1] 794 | ccdxpos = ccdpx[0] 795 | ccdypos = ccdpx[1] 796 | return ccdNum, fitsxpos, fitsypos, ccdxpos, ccdypos, lat 797 | 798 | def pix2radec_nocheck_single(self, cam, iccd, ccdpx): 799 | """ 800 | Reverse the transform going from pixel coords to Ra & Dec 801 | """ 802 | deg2rad = np.pi / 180.0 803 | # Convert pixels to mm 804 | xyfp = self.pix_to_mm_single_ccd(cam, ccdpx, iccd) 805 | lng_deg, lat_deg = self.fp_optics(cam, xyfp) 806 | vcam0, vcam1, vcam2 = self.sphereToCart(lng_deg, lat_deg) 807 | vcam = np.array([vcam0, vcam1, vcam2], dtype=np.double) 808 | curVec = np.matmul(np.transpose(self.rmat4[cam]), vcam) 809 | ra, dec = self.cartToSphere(curVec) 810 | 811 | return ra / deg2rad, dec / deg2rad 812 | 813 | 814 | class TESS_Spacecraft_Pointing_Data: 815 | # Hard coded spacecraft pointings by Sector 816 | # When adding sectors the arg2 needs to end +1 from sector 817 | # due to the np.arange function ending at arg2-1 818 | sectors = np.arange(1, max_sector + 1, dtype=int) 819 | 820 | # Arrays are broken up into the following sectors: 821 | # Line 1: Sectors 1-5 Start Year 1 822 | # Line 2: Secotrs 6-9 823 | # Line 3: Sectors 10-13 End Year 1 824 | # Line 4: Sectors 14-17 Start Year 2 825 | # Line 5: Sectors 18-22 826 | # Line 6: Sectors 23-26 End Year 2 827 | # Line 7: Sectors 27-30 Star Year 3 828 | # Line 8: Sectors 31-34 829 | # Line 9: Sectors 35-38 830 | # Line 10: Sectors 39 End Year 3 831 | # Line 11: Sectors 40-43 Star Year 4 832 | # Line 12: S 44-47 833 | # Line 13: S 48-51 834 | # Line 14: S 52-55 END YEAR 4 835 | # Line 15: S 56-59 START YEAR 5 836 | # Line 16: S 60-63 837 | # Line 17: S 64-67 838 | # Line 18: S 68-69 END Year 5 839 | # Line 19: S 70-72 START Year 6 840 | # Line 20: S 73-76 841 | # Line 21: S 77-80 842 | # Line 22: S 81-83 END Year 6 843 | # Line 23: S 84-87 START Year 7 844 | # Line 24: S 88-90 845 | # Line 25: S 91-93 846 | # Line 26: S 94-96 END Year 7 847 | # Line 27: S 97-99 START Year 8 848 | # Line 28: S 100-102 849 | # Line 29: S 103-105 850 | # Line 30: S 106-107 END Year 8 851 | 852 | ### NOTE IF you add Sectors be sure to update the allowed range 853 | ### for sectors in argparse arguments!!! 854 | ras = np.array( 855 | [ 856 | 352.6844, 857 | 16.5571, 858 | 36.3138, 859 | 55.0070, 860 | 73.5382, 861 | 92.0096, 862 | 110.2559, 863 | 128.1156, 864 | 145.9071, 865 | 165.0475, 866 | 189.1247, 867 | 229.5885, 868 | 298.6671, 869 | 276.7169, 870 | 280.3985, 871 | 282.4427, 872 | 351.2381, 873 | 16.1103, 874 | 60.2026, 875 | 129.3867, 876 | 171.7951, 877 | 197.1008, 878 | 217.2879, 879 | 261.4516, 880 | 265.6098, 881 | 270.1381, 882 | 326.8525, 883 | 357.2944, 884 | 18.9190, 885 | 38.3564, 886 | 57.6357, 887 | 77.1891, 888 | 96.5996, 889 | 115.2951, 890 | 133.2035, 891 | 150.9497, 892 | 170.2540, 893 | 195.7176, 894 | 242.1981, 895 | 273.0766, 896 | 277.6209, 897 | 13.0140, 898 | 49.5260, 899 | 89.6066, 900 | 130.2960, 901 | 157.6997, 902 | 143.3807, 903 | 179.4254, 904 | 202.6424, 905 | 221.8575, 906 | 239.4257, 907 | 266.3618, 908 | 270.8126, 909 | 290.1210, 910 | 307.8655, 911 | 324.2778, 912 | 344.2275, 913 | 9.3118, 914 | 52.9755, 915 | 125.6742, 916 | 118.0446, 917 | 135.2412, 918 | 153.0613, 919 | 173.2653, 920 | 201.6239, 921 | 259.1702, 922 | 326.7691, 923 | 359.2829, 924 | 20.0449, 925 | 24.0414, 926 | 77.3449, 927 | 133.7631, 928 | 80.6709, 929 | 261.2194, 930 | 254.9290, 931 | 253.5335, 932 | 255.8590, 933 | 260.4232, 934 | 266.0595, 935 | 275.6978, 936 | 292.5709, 937 | 309.1299, 938 | 325.8933, 939 | 343.8717, 940 | 5.8776, 941 | 42.6741, 942 | 97.9629, 943 | 116.8315, 944 | 135.8409, 945 | 155.0705, 946 | 227.0409, 947 | 311.9633, 948 | 260.5165, 949 | 323.4700, 950 | 355.7011, 951 | 16.7112, 952 | 41.6581, 953 | 80.1656, 954 | 157.205, 955 | 180.5352, 956 | 209.0229, 957 | 249.1776, 958 | 297.5431, 959 | 335.4613, 960 | 2.5764, 961 | 25.0148, 962 | 45.8709, 963 | ], 964 | dtype=float, 965 | ) 966 | 967 | decs = np.array( 968 | [ 969 | -64.8531, 970 | -54.0160, 971 | -44.2590, 972 | -36.6420, 973 | -31.9349, 974 | -30.5839, 975 | -32.6344, 976 | -37.7370, 977 | -45.3044, 978 | -54.8165, 979 | -65.5369, 980 | -75.1256, 981 | -76.3281, 982 | 62.4756, 983 | 64.0671, 984 | 66.1422, 985 | 57.8456, 986 | 67.9575, 987 | 76.2343, 988 | 75.2520, 989 | 65.1924, 990 | 53.7434, 991 | 43.8074, 992 | 63.1181, 993 | 61.9383, 994 | 61.5637, 995 | -72.4265, 996 | -63.0056, 997 | -52.8296, 998 | -43.3178, 999 | -35.7835, 1000 | -31.3957, 1001 | -30.7848, 1002 | -33.7790, 1003 | -39.6871, 1004 | -47.7512, 1005 | -57.3725, 1006 | -67.8307, 1007 | -76.3969, 1008 | 61.7450, 1009 | 62.7640, 1010 | 6.3337, 1011 | 18.9737, 1012 | 24.1343, 1013 | 19.0181, 1014 | 10.0922, 1015 | 73.1125, 1016 | 62.1038, 1017 | 50.9532, 1018 | 41.7577, 1019 | 35.2333, 1020 | 61.8190, 1021 | 61.5761, 1022 | 32.6073, 1023 | 37.6464, 1024 | 46.3448, 1025 | 56.4121, 1026 | 67.6524, 1027 | 77.1746, 1028 | 77.3113, 1029 | -36.0902, 1030 | -42.2415, 1031 | -50.6996, 1032 | -60.8650, 1033 | -71.5724, 1034 | -78.7974, 1035 | -74.2796, 1036 | -64.2357, 1037 | -54.2315, 1038 | 9.2629, 1039 | 22.2220, 1040 | 16.6536, 1041 | 78.8333, 1042 | 72.3020, 1043 | 69.9395, 1044 | 66.8396, 1045 | 63.8557, 1046 | 61.5745, 1047 | 60.3042, 1048 | 32.2323, 1049 | 34.6859, 1050 | 39.7760, 1051 | 47.1247, 1052 | 56.2316, 1053 | 66.3525, 1054 | 75.8930, 1055 | -32.3927, 1056 | -35.7542, 1057 | -42.4981, 1058 | -51.7243, 1059 | -16.8742, 1060 | -17.1415, 1061 | -78.8295, 1062 | -74.8966, 1063 | -65.7252, 1064 | -55.9356, 1065 | -41.8452, 1066 | -31.0553, 1067 | -41.1581, 1068 | -51.9138, 1069 | -62.0323, 1070 | -68.7051, 1071 | -68.1075, 1072 | -60.7536, 1073 | -50.5513, 1074 | -40.1221, 1075 | -31.2349, 1076 | ], 1077 | dtype=float, 1078 | ) 1079 | 1080 | rolls = np.array( 1081 | [ 1082 | 222.1532, 1083 | 220.4335, 1084 | 213.0384, 1085 | 202.8302, 1086 | 191.0517, 1087 | 178.6367, 1088 | 166.4476, 1089 | 155.3091, 1090 | 145.9163, 1091 | 139.1724, 1092 | 138.0761, 1093 | 153.9773, 1094 | 198.9378, 1095 | 32.2329, 1096 | 55.4277, 1097 | 79.4699, 1098 | 41.9686, 1099 | 40.5453, 1100 | 19.6463, 1101 | 334.5689, 1102 | 317.9495, 1103 | 319.6992, 1104 | 327.4246, 1105 | 317.2624, 1106 | 339.5293, 1107 | 0.6038, 1108 | 214.5061, 1109 | 222.5216, 1110 | 219.7970, 1111 | 212.0441, 1112 | 201.2334, 1113 | 188.6263, 1114 | 175.5369, 1115 | 163.1916, 1116 | 152.4006, 1117 | 143.7306, 1118 | 138.1685, 1119 | 139.3519, 1120 | 161.5986, 1121 | 14.1539, 1122 | 37.2224, 1123 | 292.8009, 1124 | 284.9617, 1125 | 270.1557, 1126 | 255.0927, 1127 | 248.4063, 1128 | 327.1020, 1129 | 317.4166, 1130 | 321.3516, 1131 | 329.7340, 1132 | 339.8650, 1133 | 343.1429, 1134 | 3.6838, 1135 | 13.4565, 1136 | 24.5369, 1137 | 36.2524, 1138 | 44.0100, 1139 | 45.3615, 1140 | 26.5121, 1141 | 337.3244, 1142 | 162.2198, 1143 | 151.5884, 1144 | 142.7405, 1145 | 137.2810, 1146 | 140.7443, 1147 | 173.9147, 1148 | 217.4678, 1149 | 226.0975, 1150 | 222.7721, 1151 | 291.2985, 1152 | 274.9979, 1153 | 254.0304, 1154 | 8.0330, 1155 | 213.9191, 1156 | 247.4882, 1157 | 276.6424, 1158 | 302.3416, 1159 | 325.7085, 1160 | 347.5089, 1161 | 5.4938, 1162 | 17.1317, 1163 | 27.7999, 1164 | 37.0453, 1165 | 43.9159, 1166 | 45.8046, 1167 | 32.5811, 1168 | 175.9167, 1169 | 163.0201, 1170 | 151.2486, 1171 | 141.9459, 1172 | 254.2704, 1173 | 285.4229, 1174 | 174.8522, 1175 | 215.8459, 1176 | 225.9414, 1177 | 223.7615, 1178 | 210.3682, 1179 | 186.6394, 1180 | 215.9362, 1181 | 212.8651, 1182 | 217.7538, 1183 | 236.0704, 1184 | 263.1873, 1185 | 279.238, 1186 | 282.7413, 1187 | 279.1045, 1188 | 271.3363, 1189 | ], 1190 | dtype=float, 1191 | ) 1192 | 1193 | midtimes = np.array( 1194 | [ 1195 | 2458339.652778, 1196 | 2458368.593750, 1197 | 2458396.659722, 1198 | 2458424.548611, 1199 | 2458451.548611, 1200 | 2458478.104167, 1201 | 2458504.697917, 1202 | 2458530.256944, 1203 | 2458556.722222, 1204 | 2458582.760417, 1205 | 2458610.774306, 1206 | 2458640.031250, 1207 | 2458668.618056, 1208 | 2458697.336806, 1209 | 2458724.934028, 1210 | 2458751.649306, 1211 | 2458777.722222, 1212 | 2458803.440972, 1213 | 2458828.958333, 1214 | 2458856.388889, 1215 | 2458884.916667, 1216 | 2458913.565972, 1217 | 2458941.829861, 1218 | 2458969.263889, 1219 | 2458996.909722, 1220 | 2459023.107639, 1221 | 2459049.145833, 1222 | 2459075.166667, 1223 | 2459102.319444, 1224 | 2459130.201389, 1225 | 2459158.854167, 1226 | 2459186.940972, 1227 | 2459215.427083, 1228 | 2459241.979167, 1229 | 2459268.579861, 1230 | 2459295.301177, 1231 | 2459322.577780, 1232 | 2459349.854382, 1233 | 2459377.130985, 1234 | 2459404.407588, 1235 | 2459431.684191, 1236 | 2459458.960794, 1237 | 2459486.237397, 1238 | 2459513.514000, 1239 | 2459540.790602, 1240 | 2459568.067205, 1241 | 2459595.343808, 1242 | 2459622.620411, 1243 | 2459649.897014, 1244 | 2459677.173617, 1245 | 2459704.450219, 1246 | 2459731.726822, 1247 | 2459759.003425, 1248 | 2459786.280028, 1249 | 2459813.556631, 1250 | 2459840.833234, 1251 | 2459868.109837, 1252 | 2459895.386439, 1253 | 2459922.663042, 1254 | 2459949.939645, 1255 | 2459977.216248, 1256 | 2460004.492851, 1257 | 2460031.769454, 1258 | 2460059.046057, 1259 | 2460086.322659, 1260 | 2460113.599262, 1261 | 2460140.875865, 1262 | 2460168.152468, 1263 | 2460195.429071, 1264 | 2460220.5, 1265 | 2460246.5, 1266 | 2460272.5, 1267 | 2460299, 1268 | 2460326, 1269 | 2460353, 1270 | 2460381, 1271 | 2460409.5, 1272 | 2460437.5, 1273 | 2460465.5, 1274 | 2460493, 1275 | 2460519.5, 1276 | 2460545.5, 1277 | 2460571.5, 1278 | 2460597.0, 1279 | 2460622.5, 1280 | 2460649.0, 1281 | 2460676.0, 1282 | 2460703.5, 1283 | 2460732.0, 1284 | 2460760.5, 1285 | 2460788.5, 1286 | 2460816.0, 1287 | 2460842.5, 1288 | 2460868.5, 1289 | 2460894.5, 1290 | 2460920.5, 1291 | 2460961.0, 1292 | 2461017.0, 1293 | 2461059.5, 1294 | 2461087.0, 1295 | 2461113.5, 1296 | 2461139.0, 1297 | 2461164.5, 1298 | 2461191.0, 1299 | 2461218.5, 1300 | 2461247.0, 1301 | 2461276.0, 1302 | ], 1303 | dtype=float, 1304 | ) 1305 | 1306 | camSeps = np.array([36.0, 12.0, 12.0, 36.0], dtype=float) 1307 | 1308 | def __init__(self, trySector=None, fpgParmFileList=None, sectorOverrideFile=None): 1309 | # Convert S/C boresite pointings to ecliptic coords for each camera 1310 | 1311 | # if sectorOverrideFile is set read in the sector pointing overrides 1312 | # This can be used for mission planning or for speed ups 1313 | # by allowing the user to put in a custom list of sectors that they want 1314 | # to run over 1315 | if not sectorOverrideFile is None: 1316 | try: 1317 | dataBlock = np.genfromtxt( 1318 | sectorOverrideFile, dtype=["i4", "f8", "f8", "f8"] 1319 | ) 1320 | self.sectors = np.array(dataBlock["f0"]) 1321 | self.ras = np.array(dataBlock["f1"]) 1322 | self.decs = np.array(dataBlock["f2"]) 1323 | self.rolls = np.array(dataBlock["f3"]) 1324 | # just put something into midtimes at this point should never get used 1325 | # because aberration is not supported with sector override file 1326 | medmidtimes = np.median(self.midtimes) 1327 | self.midtimes = np.full_like(self.ras, medmidtimes) 1328 | except: 1329 | print("There was a problem reading the Sector Override File. Exiting!") 1330 | sys.exit(1) 1331 | 1332 | # If trySector is set only keep the single requested sector 1333 | if not trySector is None: 1334 | idx = np.where(self.sectors == trySector)[0] 1335 | # Check for condition where the requested sector is not in list! 1336 | if len(idx) == 0: 1337 | print( 1338 | "Could not find requested sector: {0:d} in the available sector pointing data".format( 1339 | trySector 1340 | ) 1341 | ) 1342 | sys.exit(1) 1343 | self.sectors = self.sectors[idx] 1344 | self.ras = self.ras[idx] 1345 | self.decs = self.decs[idx] 1346 | self.rolls = self.rolls[idx] 1347 | self.midtimes = self.midtimes[idx] 1348 | nPoints = len(self.sectors) 1349 | self.camRa = np.zeros((4, nPoints), dtype=float) 1350 | self.camDec = np.zeros((4, nPoints), dtype=float) 1351 | # Convert S/C boresite ra and dec to camera ra and dec 1352 | for iPnt in range(nPoints): 1353 | curra = self.ras[iPnt] 1354 | curdec = self.decs[iPnt] 1355 | curroll = self.rolls[iPnt] 1356 | camposangs = np.array( 1357 | [180.0 - curroll, 180.0 - curroll, 360.0 - curroll, 360.0 - curroll] 1358 | ) 1359 | camposangs = np.mod(camposangs, 360.0) 1360 | for iCam in range(4): 1361 | # Need to correct s/c roll to posang 1362 | pang = camposangs[iCam] 1363 | camra, camdec = get_radec_from_posangsep( 1364 | curra, curdec, pang, self.camSeps[iCam] 1365 | ) 1366 | self.camRa[iCam, iPnt] = camra 1367 | self.camDec[iCam, iPnt] = camdec 1368 | # Just for testing camera coords 1369 | # compare to published values 1370 | # print('{:d} {:d} {:f} {:f}'.format(self.sectors[iPnt],iCam+1,\ 1371 | # self.camRa[iCam,iPnt], self.camDec[iCam,iPnt])) 1372 | # For every pointing make a Levine pointing class object 1373 | self.fpgObjs = [] 1374 | fpg_file_list = None 1375 | if not fpgParmFileList is None: 1376 | fpg_file_list = fpgParmFileList 1377 | for iPnt in range(nPoints): 1378 | sc_ra_dec_roll = np.array( 1379 | [self.ras[iPnt], self.decs[iPnt], self.rolls[iPnt]] 1380 | ) 1381 | self.fpgObjs.append(Levine_FPG(sc_ra_dec_roll, fpg_file_list=fpg_file_list)) 1382 | 1383 | # Dump sector data out 1384 | # for i, curSec in enumerate(self.sectors): 1385 | # strout = '{0:d} {1:8.4f} {2:7.4f} {3:9.4f}'.format(curSec,\ 1386 | # self.ras[i], self.decs[i], self.rolls[i]) 1387 | # print(strout) 1388 | 1389 | 1390 | def get_radec_from_posangsep(ra, dec, pa, sep): 1391 | deg2rad = np.pi / 180.0 1392 | rad2deg = 180.0 / np.pi 1393 | twopi = 2.0 * np.pi 1394 | pidtwo = np.pi / 2.0 1395 | rar = ra * deg2rad 1396 | decr = dec * deg2rad 1397 | par = pa * deg2rad 1398 | sepr = sep * deg2rad 1399 | c = pidtwo - decr 1400 | bigB = par 1401 | a = sepr 1402 | b = np.arccos((np.cos(c) * np.cos(a) + np.sin(c) * np.sin(a) * np.cos(bigB))) 1403 | newdec = pidtwo - b 1404 | delalp = np.arccos( 1405 | np.min( 1406 | [ 1407 | (np.cos(sepr) - np.sin(decr) * np.sin(newdec)) 1408 | / (np.cos(decr) * np.cos(newdec)), 1409 | 1.0, 1410 | ] 1411 | ) 1412 | ) 1413 | if pa > 180.0: 1414 | newra = rar - delalp 1415 | else: 1416 | newra = rar + delalp 1417 | # print(pa, newra*rad2deg, rar*rad2deg, delalp*rad2deg) 1418 | newra = np.mod(newra, twopi) 1419 | return newra * rad2deg, newdec * rad2deg 1420 | 1421 | 1422 | class target_info: 1423 | def __init__(self): 1424 | self.ticid = int(0) 1425 | self.ra = 0.0 1426 | self.dec = 0.0 1427 | self.eclipLong = 0.0 1428 | self.eclipLat = 0.0 1429 | self.sectors = np.array([], dtype=int) 1430 | self.onSiliconFlag = np.array([], dtype=int) 1431 | self.possibleOnSiliconFlag = np.array([], dtype=int) 1432 | self.cameras = np.array([], dtype=int) 1433 | self.xpxs = np.array([], dtype=float) 1434 | self.ypxs = np.array([], dtype=float) 1435 | 1436 | 1437 | def make_target_objects(tic, ra, dec): 1438 | starList = [] 1439 | for i, curTic in enumerate(tic): 1440 | curRa = ra[i] 1441 | curDec = dec[i] 1442 | # instantiate target object 1443 | curTarg = target_info() 1444 | curTarg.ra = curRa 1445 | curTarg.dec = curDec 1446 | curTarg.ticid = tic[i] 1447 | # Convert ra and dec coords to ecliptic 1448 | planCoords = SkyCoord(curRa, curDec, unit="deg") 1449 | planEclipCoords = planCoords.transform_to(frame="barycentrictrueecliptic") 1450 | curTarg.eclipLat = planEclipCoords.lat.deg 1451 | curTarg.eclipLong = planEclipCoords.lon.deg 1452 | starList.append(curTarg) 1453 | return starList 1454 | 1455 | 1456 | def doRoughPosition(targinfo, scinfo): 1457 | # Return the combinations of sector and detectors that can possibly observe 1458 | # target 1459 | # go through each position in the spacecraft info class 1460 | tRa = targinfo.ra 1461 | tDec = targinfo.dec 1462 | FOVDeg = 17.68 1463 | nPoints = len(scinfo.sectors) 1464 | targCoords = SkyCoord(tRa, tDec, unit="deg", frame="icrs") 1465 | for iPnt in range(nPoints): 1466 | for iCam in range(4): 1467 | camRa = scinfo.camRa[iCam, iPnt] 1468 | camDec = scinfo.camDec[iCam, iPnt] 1469 | camCenter = SkyCoord( 1470 | camRa, np.max([-89.99999, camDec]), unit="deg", frame="icrs" 1471 | ) 1472 | posAngs = camCenter.position_angle(targCoords) 1473 | seps = camCenter.separation(targCoords) 1474 | # Check for potentially on silicon 1475 | if seps.deg < FOVDeg: 1476 | # Append potential pointing camera combo to targets list 1477 | targinfo.sectors = np.append(targinfo.sectors, scinfo.sectors[iPnt]) 1478 | targinfo.onSiliconFlag = np.append(targinfo.onSiliconFlag, 0) 1479 | targinfo.possibleOnSiliconFlag = np.append( 1480 | targinfo.possibleOnSiliconFlag, 1 1481 | ) 1482 | targinfo.cameras = np.append(targinfo.cameras, iCam + 1) 1483 | targinfo.xpxs = np.append(targinfo.xpxs, 0.0) 1484 | targinfo.ypxs = np.append(targinfo.ypxs, 0.0) 1485 | return targinfo 1486 | 1487 | 1488 | ## [Mast Query] 1489 | def mastQuery(request, proxy_uri=None): 1490 | host = "mast.stsci.edu" 1491 | # Grab Python Version 1492 | version = ".".join(map(str, sys.version_info[:3])) 1493 | 1494 | # Create Http Header Variables 1495 | headers = { 1496 | "Content-type": "application/x-www-form-urlencoded", 1497 | "Accept": "text/plain", 1498 | "User-agent": "python-requests/" + version, 1499 | } 1500 | 1501 | # Encoding the request as a json string 1502 | requestString = json.dumps(request) 1503 | requestString = urlencode(requestString) 1504 | 1505 | # opening the https connection 1506 | if None == proxy_uri: 1507 | conn = httplib.HTTPSConnection(host) 1508 | else: 1509 | port = 443 1510 | url = urlparse(proxy_uri) 1511 | conn = httplib.HTTPSConnection(url.hostname, url.port) 1512 | 1513 | if url.username and url.password: 1514 | auth = "%s:%s" % (url.username, url.password) 1515 | headers["Proxy-Authorization"] = "Basic " + str( 1516 | base64.b64encode(auth.encode()) 1517 | ).replace("b'", "").replace("'", "") 1518 | conn.set_tunnel(host, port, headers) 1519 | 1520 | # Making the query 1521 | conn.request("POST", "/api/v0/invoke", "request=" + requestString, headers) 1522 | 1523 | # Getting the response 1524 | resp = conn.getresponse() 1525 | head = resp.getheaders() 1526 | content = resp.read().decode("utf-8") 1527 | 1528 | # Close the https connection 1529 | conn.close() 1530 | 1531 | return head, content 1532 | 1533 | 1534 | ## [Mast Query] 1535 | 1536 | 1537 | def fileOutputHeader(fp, fpgParmFileList=None): 1538 | # output a header to the file 1539 | fp.write( 1540 | "# stars2px.py - Convert Target RA and Dec to TESS spacecraft pixel coordinates\n" 1541 | ) 1542 | fp.write("# Original starspx.c by Alan Levine (MIT Kavli Institute)\n") 1543 | fp.write("# Python translation by Christopher Burke (MIT Kavli Institute)\n") 1544 | fp.write("# Output columns Pipe Delimited; 16 header lines\n") 1545 | fp.write("# File Creation: {:}\n".format(datetime.datetime.now())) 1546 | if fpgParmFileList is None: 1547 | fp.write("# FPG Model Default\n") 1548 | else: 1549 | fp.write( 1550 | "# FPG Model {:s} {:s} {:s} {:s}\n".format( 1551 | fpgParmFileList[0], 1552 | fpgParmFileList[1], 1553 | fpgParmFileList[2], 1554 | fpgParmFileList[3], 1555 | ) 1556 | ) 1557 | fp.write("# 1 [int] Input TIC ID\n") 1558 | fp.write("# 2 [degree] Input or MAST TIC query target RA\n") 1559 | fp.write("# 3 [degree] Input or MAST TIC query target Dec\n") 1560 | fp.write("# 4 [degree] Ecliptic Longitude\n") 1561 | fp.write("# 5 [degree] Ecliptic Latitude\n") 1562 | fp.write("# 6 [int] - Observing Sector number for target\n") 1563 | fp.write("# 7 [int] - Camera number for target\n") 1564 | fp.write("# 8 [int] - Detector number for target\n") 1565 | fp.write("# 9 [float] - Column pixel location for target\n") 1566 | fp.write("# 10 [float] - Row pixel location for target\n") 1567 | 1568 | 1569 | def tess_stars2px_function_entry( 1570 | starIDs, 1571 | starRas, 1572 | starDecs, 1573 | trySector=None, 1574 | scInfo=None, 1575 | fpgParmFileList=None, 1576 | combinedFits=False, 1577 | noCollateral=False, 1578 | aberrate=False, 1579 | sectorOverrideFile=None, 1580 | ): 1581 | if scInfo == None: 1582 | # Instantiate Spacecraft position info 1583 | scinfo = TESS_Spacecraft_Pointing_Data( 1584 | trySector=trySector, 1585 | fpgParmFileList=fpgParmFileList, 1586 | sectorOverrideFile=sectorOverrideFile, 1587 | ) 1588 | else: 1589 | scinfo = scInfo 1590 | # Now make list of the star objects 1591 | starList = make_target_objects( 1592 | np.atleast_1d(starIDs), np.atleast_1d(starRas), np.atleast_1d(starDecs) 1593 | ) 1594 | # Make rough determination as to which pointing camera combos are worth 1595 | # Checking in detail and then do detailed checking 1596 | findAny = False 1597 | outID = np.array([-1], dtype=np.int64) 1598 | outEclipLong = np.array([-1.0], dtype=float) 1599 | outEclipLat = np.array([-1.0], dtype=float) 1600 | outSec = np.array([-1], dtype=int) 1601 | outCam = np.array([-1], dtype=int) 1602 | outCcd = np.array([-1], dtype=int) 1603 | outColPix = np.array([-1.0], dtype=float) 1604 | outRowPix = np.array([-1.0], dtype=float) 1605 | for i, curTarg in enumerate(starList): 1606 | for curSec in scinfo.sectors: 1607 | starRas = np.array([curTarg.ra]) 1608 | starDecs = np.array([curTarg.dec]) 1609 | idxSec = np.where(scinfo.sectors == curSec)[0][0] 1610 | # Apply an approximate aberration correction 1611 | if aberrate: 1612 | useTime = Time(scinfo.midtimes[idxSec], format="jd") 1613 | # Make coordinate object in ICRS coordinates 1614 | ccat = SkyCoord( 1615 | ra=starRas * u.deg, 1616 | dec=starDecs * u.deg, 1617 | obstime=useTime, 1618 | frame="icrs", 1619 | ) 1620 | # convert to Geocentric aberrated coordinates 1621 | # This is only an approximation to TESS 1622 | # because TESS orbits Earth and has 1623 | # velocity <=4km/s relative to Earth whereas Earth is 30km/s 1624 | cgcrs = ccat.transform_to("gcrs") 1625 | starRas = np.array(cgcrs.ra.degree) 1626 | starDecs = np.array(cgcrs.dec.degree) 1627 | 1628 | starInCam, starCcdNum, starFitsXs, starFitsYs, starCcdXs, starCcdYs = ( 1629 | scinfo.fpgObjs[idxSec].radec2pix(starRas, starDecs) 1630 | ) 1631 | for jj, cam in enumerate(starInCam): 1632 | # SPOC calibrated FFIs have 44 collateral pixels in x and are 1 based 1633 | xUse = starCcdXs[jj] + 45.0 1634 | yUse = starCcdYs[jj] + 1.0 1635 | xMin = 44.0 1636 | ymaxCoord = 2049 1637 | xmaxCoord = 2093 1638 | if combinedFits: 1639 | xUse = starFitsXs[jj] 1640 | yUse = starFitsYs[jj] 1641 | xmaxCoord = 4097 1642 | ymaxCoord = 4097 1643 | xMin = 0.0 1644 | if noCollateral: 1645 | xUse = starCcdXs[jj] 1646 | yUse = starCcdYs[jj] 1647 | xMin = 0.0 1648 | if xUse > xMin and yUse > 0 and xUse < xmaxCoord and yUse < ymaxCoord: 1649 | if findAny == False: 1650 | outID[0] = int(curTarg.ticid) 1651 | outEclipLong[0] = curTarg.eclipLong 1652 | outEclipLat[0] = curTarg.eclipLat 1653 | outSec[0] = curSec 1654 | outCam[0] = starInCam[jj] 1655 | outCcd[0] = starCcdNum[jj] 1656 | outColPix[0] = xUse 1657 | outRowPix[0] = yUse 1658 | findAny = True 1659 | else: 1660 | outID = np.append(outID, int(curTarg.ticid)) 1661 | outEclipLong = np.append(outEclipLong, curTarg.eclipLong) 1662 | outEclipLat = np.append(outEclipLat, curTarg.eclipLat) 1663 | outSec = np.append(outSec, curSec) 1664 | outCam = np.append(outCam, starInCam[jj]) 1665 | outCcd = np.append(outCcd, starCcdNum[jj]) 1666 | outColPix = np.append(outColPix, xUse) 1667 | outRowPix = np.append(outRowPix, yUse) 1668 | return ( 1669 | outID, 1670 | outEclipLong, 1671 | outEclipLat, 1672 | outSec, 1673 | outCam, 1674 | outCcd, 1675 | outColPix, 1676 | outRowPix, 1677 | scinfo, 1678 | ) 1679 | 1680 | 1681 | def tess_stars2px_reverse_function_entry( 1682 | trySector, 1683 | camera, 1684 | ccd, 1685 | colWant, 1686 | rowWant, 1687 | scInfo=None, 1688 | fpgParmFileList=None, 1689 | sectorOverrideFile=None, 1690 | ): 1691 | if scInfo == None: 1692 | # Instantiate Spacecraft position info 1693 | scinfo = TESS_Spacecraft_Pointing_Data( 1694 | trySector=trySector, 1695 | fpgParmFileList=fpgParmFileList, 1696 | sectorOverrideFile=sectorOverrideFile, 1697 | ) 1698 | else: 1699 | scinfo = scInfo 1700 | 1701 | idxSec = np.where(scinfo.sectors == trySector)[0][0] 1702 | starRa, starDec = scinfo.fpgObjs[idxSec].pix2radec_nocheck_single( 1703 | camera - 1, ccd - 1, [colWant - 45.0, rowWant - 1.0] 1704 | ) 1705 | return starRa, starDec, scinfo 1706 | 1707 | 1708 | if __name__ == "__main__": 1709 | # Parse the command line arguments 1710 | parser = argparse.ArgumentParser() 1711 | parser.add_argument( 1712 | "-t", 1713 | "--ticId", 1714 | type=int, 1715 | help="TIC Id [int] for MAST coordinate query. MUST Be Online For this option to Work!", 1716 | ) 1717 | parser.add_argument( 1718 | "-c", "--coord", type=float, nargs=2, help="RA and Dec of target [deg]" 1719 | ) 1720 | parser.add_argument( 1721 | "-f", 1722 | "--inputFile", 1723 | type=argparse.FileType("r"), 1724 | help="Filename for input Target TIC [int]; RA[deg]; Dec[dec]; in white space delimited text file Column 1, 2, and 3 respectively", 1725 | ) 1726 | parser.add_argument( 1727 | "-o", 1728 | "--outputFile", 1729 | type=argparse.FileType("w"), 1730 | help="Optional filename for output. Default is output to stdout ", 1731 | ) 1732 | parser.add_argument( 1733 | "-s", 1734 | "--sector", 1735 | type=int, 1736 | choices=range(1, max_sector + 1), 1737 | help="Search a single sector Number [int]", 1738 | ) 1739 | parser.add_argument( 1740 | "-x", 1741 | "--combinedFits", 1742 | action="store_true", 1743 | help="Output detector pixel coordinates for the 'Big' multi-detector combined fits file format", 1744 | ) 1745 | parser.add_argument( 1746 | "-xin", 1747 | "--noCollateral", 1748 | action="store_true", 1749 | help="Output detector pixel coordinates for an internal format where there are not leading collateral pixels and zero based", 1750 | ) 1751 | parser.add_argument( 1752 | "-fpg", 1753 | "--fpgParameterFiles", 1754 | nargs=4, 1755 | help="Instead of default focal plane geometry parameters, list the 4 filenames for the fpg files to use. Expects files in Al's format in camera numerical order", 1756 | ) 1757 | parser.add_argument( 1758 | "-r", 1759 | "--reverse", 1760 | nargs=5, 1761 | help="Do reverse search. Return RA Dec for a given pixel position. 5 parameters sector cam ccd colpix rowpix", 1762 | ) 1763 | parser.add_argument( 1764 | "-n", 1765 | "--name", 1766 | nargs=1, 1767 | type=str, 1768 | help="Search for a target by resolving its name with SESAME", 1769 | ) 1770 | parser.add_argument( 1771 | "-p", 1772 | "--proxy_uri", 1773 | nargs=1, 1774 | type=str, 1775 | help='Use proxy e.g."http://:@:" ', 1776 | ) 1777 | parser.add_argument( 1778 | "-a", 1779 | "--aberrate", 1780 | action="store_true", 1781 | help="Apply approximate aberration correction to input coordinates. Uses astropy GCRS coordinate frame to approximate TESS aberration", 1782 | ) 1783 | parser.add_argument( 1784 | "-sovr", 1785 | "--sector_override", 1786 | type=argparse.FileType("r"), 1787 | help="Filename for sector pointing overrides SectorNum [Int]; RA(deg) [Float]; Dec(deg) [Float]; Roll(deg) [Float]; in white space delimited text file Column 1, 2, 3, and 4 respectively", 1788 | ) 1789 | args = parser.parse_args() 1790 | 1791 | # DEBUG BLOCK for hard coding input parameters and testing 1792 | # class test_arg: 1793 | # def __init__(self): 1794 | # #self.ticId = 281541555 1795 | # self.ticId = None 1796 | # self.coord = None 1797 | # self.name = ['KIC 6443093'] 1798 | # self.coord = [330.6803807390524, 42.27777178] 1799 | # self.inputFile = None 1800 | # self.sector = None 1801 | # self.fpgParameterFiles = None 1802 | # self.outputFile = None 1803 | # self.combinedFits = False 1804 | # self.noCollateral = False 1805 | # self.reverse = None 1806 | # self.reverse = [2,1,2,2092.0,1.0] 1807 | # self.sectorOverrideFile = None 1808 | # args = test_arg() 1809 | 1810 | # At least one Mode -t -c -f -r -n must have been specified 1811 | if ( 1812 | (args.ticId is None) 1813 | and (args.coord is None) 1814 | and (args.inputFile is None) 1815 | and (args.reverse is None) 1816 | and (args.name is None) 1817 | ): 1818 | print("You must specify one and only one mode -t, -c, -f, -r, -n") 1819 | print("`python stars2px.py -h' for help") 1820 | sys.exit(1) 1821 | 1822 | # sector overrides are currently not allowed with the aberration flag 1823 | if (not (args.sector_override is None)) and args.aberrate: 1824 | print( 1825 | "Aberration flag (-a) is not supported with the sector override file input (-sovr)" 1826 | ) 1827 | sys.exit(1) 1828 | 1829 | # Check for reverse mode 1830 | if args.reverse is None: 1831 | # Do single coords first 1832 | if args.coord is not None and args.name is None: 1833 | nTarg = 1 1834 | starTics = np.array([0], dtype=np.int64) 1835 | starRas = np.array([args.coord[0]], dtype=float) 1836 | starDecs = np.array([args.coord[1]], dtype=float) 1837 | elif args.coord is None and args.name is not None: 1838 | nTarg = 1 1839 | starTics = np.array([0], dtype=np.int64) 1840 | 1841 | # Name resolve in try except for detecting problem 1842 | try: 1843 | coordinate = SkyCoord.from_name(args.name[0]) 1844 | print( 1845 | "Coordinates for {0}: ({1}, {2})".format( 1846 | args.name[0], coordinate.ra.degree, coordinate.dec.degree 1847 | ) 1848 | ) 1849 | starRas = np.array([coordinate.ra.degree], dtype=float) 1850 | starDecs = np.array([coordinate.dec.degree], dtype=float) 1851 | except: 1852 | print("Could not resolve: {0}".format(args.name[0])) 1853 | sys.exit(1) 1854 | else: 1855 | if not (args.inputFile is None): # Check for input file list next 1856 | # Read in star positions in input 1857 | # Now go through stars 1858 | starFile = args.inputFile 1859 | dataBlock = np.genfromtxt(starFile, dtype=["i4", "f8", "f8"]) 1860 | starTics = np.atleast_1d(dataBlock["f0"]) 1861 | starRas = np.atleast_1d(dataBlock["f1"]) 1862 | starDecs = np.atleast_1d(dataBlock["f2"]) 1863 | else: 1864 | # Must have requested MAST query with TIC ID 1865 | # Make a list of TICs using strings 1866 | starTics = np.array([args.ticId], dtype=np.int64) 1867 | ticStringList = ["{0:d}".format(x) for x in starTics] 1868 | # Setup mast query 1869 | request = { 1870 | "service": "Mast.Catalogs.Filtered.Tic", 1871 | "params": { 1872 | "columns": "*", 1873 | "filters": [{"paramName": "ID", "values": ticStringList}], 1874 | }, 1875 | "format": "json", 1876 | "removenullcolumns": True, 1877 | } 1878 | if args.proxy_uri is None: 1879 | headers, outString = mastQuery(request) 1880 | else: 1881 | headers, outString = mastQuery(request, args.proxy_uri[0]) 1882 | outObject = json.loads(outString) 1883 | starRas = np.array([x["ra"] for x in outObject["data"]]) 1884 | starDecs = np.array([x["dec"] for x in outObject["data"]]) 1885 | 1886 | trySector = None 1887 | if not (args.sector is None): 1888 | trySector = args.sector 1889 | fpgParmFileList = None 1890 | if not (args.fpgParameterFiles is None): 1891 | fpgParmFileList = [x for x in args.fpgParameterFiles] 1892 | 1893 | sectorOverrideFile = None 1894 | if not (args.sector_override is None): 1895 | sectorOverrideFile = args.sector_override 1896 | 1897 | # Instantiate Spacecraft position info 1898 | scinfo = TESS_Spacecraft_Pointing_Data( 1899 | trySector=trySector, 1900 | fpgParmFileList=fpgParmFileList, 1901 | sectorOverrideFile=sectorOverrideFile, 1902 | ) 1903 | # Open output file if requested 1904 | # if not (args.outputFile is None): 1905 | # fout = open(args.outputFile, 'w') 1906 | 1907 | # Add header to outputfile 1908 | if not (args.outputFile is None): 1909 | fileOutputHeader(args.outputFile, fpgParmFileList=fpgParmFileList) 1910 | else: 1911 | # add single line header to stdout 1912 | print( 1913 | "# TIC | RA | Dec | EclipticLong | EclipticLat | Sector | Camera | Ccd | ColPix | RowPix | EdgeWarn" 1914 | ) 1915 | # Now make list of the star objects 1916 | starList = make_target_objects(starTics, starRas, starDecs) 1917 | # print('Finished converting coords to ecliptic') 1918 | # Make rough determination as to which pointing camera combos are worth 1919 | # Checking in detail and then do detailed checking 1920 | findAny = False 1921 | for i, curTarg in enumerate(starList): 1922 | for curSec in scinfo.sectors: 1923 | starRas = np.array([curTarg.ra]) 1924 | starDecs = np.array([curTarg.dec]) 1925 | idxSec = np.where(scinfo.sectors == curSec)[0][0] 1926 | # Apply an approximate aberration correction 1927 | if args.aberrate: 1928 | useTime = Time(scinfo.midtimes[idxSec], format="jd") 1929 | # Make coordinate object in ICRS coordinates 1930 | ccat = SkyCoord( 1931 | ra=starRas * u.deg, 1932 | dec=starDecs * u.deg, 1933 | obstime=useTime, 1934 | frame="icrs", 1935 | ) 1936 | # convert to Geocentric aberrated coordinates 1937 | # This is only an approximation to TESS 1938 | # because TESS orbits Earth and has 1939 | # velocity <=4km/s relative to Earth whereas Earth is 30km/s 1940 | cgcrs = ccat.transform_to("gcrs") 1941 | starRas = np.array(cgcrs.ra.degree) 1942 | starDecs = np.array(cgcrs.dec.degree) 1943 | starInCam, starCcdNum, starFitsXs, starFitsYs, starCcdXs, starCcdYs = ( 1944 | scinfo.fpgObjs[idxSec].radec2pix(starRas, starDecs) 1945 | ) 1946 | for jj, cam in enumerate(starInCam): 1947 | # SPOC calibrated FFIs have 44 collateral pixels in x and are 1 based 1948 | xUse = starCcdXs[jj] + 45.0 1949 | yUse = starCcdYs[jj] + 1.0 1950 | xMin = 44.0 1951 | ymaxCoord = 2049 1952 | xmaxCoord = 2093 1953 | if args.combinedFits: 1954 | xUse = starFitsXs[jj] 1955 | yUse = starFitsYs[jj] 1956 | xmaxCoord = 4097 1957 | ymaxCoord = 4097 1958 | xMin = 0.0 1959 | if args.noCollateral: 1960 | xUse = starCcdXs[jj] 1961 | yUse = starCcdYs[jj] 1962 | xMin = 0.0 1963 | if ( 1964 | xUse > xMin 1965 | and yUse > 0 1966 | and xUse < xmaxCoord 1967 | and yUse < ymaxCoord 1968 | ): 1969 | findAny = True 1970 | edgeWarn = 0 1971 | edgePix = 6 1972 | if xUse <= (xMin + edgePix): 1973 | edgeWarn = 1 1974 | if xUse >= (xmaxCoord - edgePix): 1975 | edgeWarn = 1 1976 | if yUse <= edgePix: 1977 | edgeWarn = 1 1978 | if yUse >= (ymaxCoord - edgePix): 1979 | edgeWarn = 1 1980 | strout = "{:09d} | {:10.6f} | {:10.6f} | {:10.6f} | {:10.6f} | {:2d} | {:1d} | {:1d} | {:11.6f} | {:11.6f} | {:1d}".format( 1981 | curTarg.ticid, 1982 | curTarg.ra, 1983 | curTarg.dec, 1984 | curTarg.eclipLong, 1985 | curTarg.eclipLat, 1986 | curSec, 1987 | starInCam[jj], 1988 | starCcdNum[jj], 1989 | xUse, 1990 | yUse, 1991 | edgeWarn, 1992 | ) 1993 | if not (args.outputFile is None): 1994 | args.outputFile.write("{:s}\n".format(strout)) 1995 | else: 1996 | print(strout) 1997 | 1998 | if not findAny: 1999 | print("No Target/s were found to be on detectors") 2000 | # Do reverse mode 2001 | else: 2002 | trySector = int(args.reverse[0]) 2003 | fpgParmFileList = None 2004 | if not (args.fpgParameterFiles is None): 2005 | fpgParmFileList = [x for x in args.fpgParameterFiles] 2006 | 2007 | sectorOverrideFile = None 2008 | if not (args.sector_override is None): 2009 | sectorOverrideFile = args.sector_override 2010 | 2011 | # Instantiate Spacecraft position info 2012 | scinfo = TESS_Spacecraft_Pointing_Data( 2013 | trySector=trySector, 2014 | fpgParmFileList=fpgParmFileList, 2015 | sectorOverrideFile=sectorOverrideFile, 2016 | ) 2017 | # Camera center ra and dec 2018 | iCam = int(args.reverse[1]) - 1 2019 | iCcd = int(args.reverse[2]) - 1 2020 | colWnt = float(args.reverse[3]) 2021 | rowWnt = float(args.reverse[4]) 2022 | 2023 | # Lets try going direct route with inversion of codes 2024 | starCcdXs = colWnt - 45.0 2025 | starCcdYs = rowWnt - 1.0 2026 | idxSec = np.where(scinfo.sectors == trySector)[0][0] 2027 | ra_deg, dec_deg = scinfo.fpgObjs[idxSec].pix2radec_nocheck_single( 2028 | iCam, iCcd, [starCcdXs, starCcdYs] 2029 | ) 2030 | print(ra_deg, dec_deg) 2031 | -------------------------------------------------------------------------------- /wasm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(tess_stars2px VERSION 0.1 3 | DESCRIPTION "tess_stars2px cmake") 4 | 5 | set(CMAKE_BUILD_TYPE DEBUG) 6 | 7 | # add the source files needed for main executable 8 | add_executable(tess_stars2px 9 | src/tess_stars2px.c 10 | src/mat_ra3.c 11 | src/vec.c) 12 | 13 | # find the math library 14 | find_library(MATH_LIBRARY m) 15 | if(MATH_LIBRARY) 16 | # link target with math library 17 | target_link_libraries(tess_stars2px PUBLIC ${MATH_LIBRARY}) 18 | endif() 19 | 20 | # define header file path 21 | target_include_directories(tess_stars2px PRIVATE include) 22 | 23 | # Make sure the tess_stars2px function is exported along with main for javascript 24 | # visibility NOTE the leading '_' 25 | target_link_options(tess_stars2px PRIVATE -sEXPORTED_FUNCTIONS=['_main','_tess_stars2px']) 26 | 27 | # output wasm and js hopefully 28 | set(CMAKE_EXECUTABLE_SUFFIX ".html") 29 | 30 | install(FILES ${CMAKE_BINARY_DIR}/tess_stars2px.js ${CMAKE_BINARY_DIR}/tess_stars2px.wasm DESTINATION ${CMAKE_SOURCE_DIR}/public) 31 | -------------------------------------------------------------------------------- /wasm/README.md: -------------------------------------------------------------------------------- 1 | # Webassembly version of tess-point 2 | This is based off the original c code version that has been compiled with 3 | emscripten to make available to web browsers. This version along with 4 | the custom tailored javascript binding is available on my 5 | personal webpage. 6 | 7 | The build uses emscripten and cmake. In order to build one needs to install 8 | the emscripten sdk along with its prerequisites. In my personal experience using 9 | a mac I needed Xcode, git, and cmake. On the M1 processor I ran into some troubles 10 | with node.js version that came along with the emscripten sdk. I had to 11 | install the arm64 node.js version first then install emscripten, so it used my local 12 | version of node.js rather than bring another version. As another gotcha 13 | for M1 laptops, the suggested Anaconda python for mac that comes up by default 14 | is not the M1 (arm64) version that one needs. One has to select other versions 15 | for mac os to find the anaconda arm64 version. 16 | 17 | Prebuilt javascript and wasm are available in the public directory. If you want 18 | to rebuild them, then follow a typical cmake build process. 19 | The cmake instructions are in CMakeLists.txt, and emscripten takes care 20 | of 'properly' calling emcc as the build target. From inside the top wasm directory, 21 | issue the following commands to build. 22 | 23 | `emcmake cmake -S . -B build` 24 | 25 | `cd build` 26 | 27 | `make` 28 | 29 | I then move the resulting tess_stars2px.js and tess_stars2px.wasm into the 30 | public directory by issuing `make install` 31 | 32 | ### TODOS: 33 | 1. Convert input and output to javascript arraybuffers 34 | 2. Support multi target file upload by figuring out how to load a local file containing coordinate lists with javascript 35 | 3. Allow output to be saved to textfile 36 | -------------------------------------------------------------------------------- /wasm/include/mat_ra.h: -------------------------------------------------------------------------------- 1 | /* mat_ra2.h 2 | * Alan M. Levine 3 | * June 24, 1998 (from mat.h) 4 | * May 13, 2015 5 | * 6 | * Declarations for mat_ra2.c 7 | */ 8 | extern void matvec(double mat[3][3], double vin[3], double vout[3]); 9 | extern void matmat(double mata[3][3], double matb[3][3], double matc[3][3]); 10 | extern void rotm1(int nax, double angle, double mat[3][3]); 11 | extern void trans(double mat[3][3], double trmat[3][3]); 12 | extern void eulerm321(double euler[3], double rmat[3][3]); 13 | extern void mateuler313(double rmat[3][3], double euler[3]); 14 | extern void mateuler321(double rmat[3][3], double euler[3]); 15 | extern double **matrix(int r, int c); 16 | extern void free_matrix(double **m, int r); 17 | extern void print_matrix(double **m, int r, int c, FILE *fp); 18 | extern int **imatrix(int r, int c); 19 | extern void free_imatrix(int **m, int r); 20 | extern void print_imatrix(int **m, int r, int c, FILE *fp); 21 | extern int gaussj(double **a, int n, double **b, int m); 22 | -------------------------------------------------------------------------------- /wasm/include/mat_ra3.h: -------------------------------------------------------------------------------- 1 | /* mat_ra2.h 2 | * Alan M. Levine 3 | * June 24, 1998 (from mat.h) 4 | * May 13, 2015 5 | * 6 | * Declarations for mat_ra2.c 7 | */ 8 | 9 | extern void matvec(double mat[3][3], double vin[3], double vout[3]); 10 | extern void matmat(double mata[3][3], double matb[3][3], double matc[3][3]); 11 | extern void rotm1(int nax, double angle, double mat[3][3]); 12 | extern void trans(double mat[3][3], double trmat[3][3]); 13 | extern void eulerm321(double euler[3], double rmat[3][3]); 14 | extern void eulerm313(double euler[3], double rmat[3][3]); 15 | extern void eulerm323(double euler[3], double rmat[3][3]); 16 | extern void eulerm123(double euler[3], double rmat[3][3]); 17 | extern void eulerm213(double euler[3], double rmat[3][3]); 18 | extern void mateuler321(double rmat[3][3], double euler[3]); 19 | extern void mateuler313(double rmat[3][3], double euler[3]); 20 | extern void mateuler323(double rmat[3][3], double euler[3]); 21 | extern void mateuler123(double rmat[3][3], double euler[3]); 22 | extern void mateuler213(double rmat[3][3], double euler[3]); 23 | extern double **matrix(int r, int c); 24 | extern void free_matrix(double **m, int r); 25 | extern void print_matrix(double **m, int r, int c, FILE *fp); 26 | extern int **imatrix(int r, int c); 27 | extern void free_imatrix(int **m, int r); 28 | extern void print_imatrix(int **m, int r, int c, FILE *fp); 29 | extern int gaussj(double **a, int n, double **b, int m); 30 | -------------------------------------------------------------------------------- /wasm/include/vec.h: -------------------------------------------------------------------------------- 1 | /* vec.h 2 | * Alan M. Levine 3 | * March 13, 1996 4 | * 5 | * Declarations for vec.c 6 | * 7 | * ANSI-style declarations - April 16, 1998 8 | */ 9 | 10 | extern void dcross(double a[3], double b[3], double c[3]); 11 | extern void dcrsph(double vec[3], double *dra, double *ddec); 12 | extern void dnorm(double vec[3]); 13 | extern double dot(double a[3], double b[3]); 14 | extern void drotate(double vec[3], double axis[3], double angle, 15 | double rotvec[3]); 16 | extern void dsphcr(double ra, double dec, double vec[3]); 17 | extern void scale_vec(double factor, double vec1[3], double vec2[3]); 18 | -------------------------------------------------------------------------------- /wasm/public/tess_stars2px.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tessgi/tess-point/47eb8130ea932a18090c17347263e0c225fbb074/wasm/public/tess_stars2px.wasm -------------------------------------------------------------------------------- /wasm/src/mat_ra3.c: -------------------------------------------------------------------------------- 1 | /* mat_ra3.c 2 | * Alan M. Levine 3 | * March 28, 2017 (from mat_ra2.c) 4 | * 5 | * 3x3 and other matrix routines in C 6 | * 7 | * Declarations are in mat_ra.h 8 | * 9 | * June 23, 1998 - Matrix allocation and deallocation and Gauss-Jordan 10 | * solution of linear equations added 11 | * June 24, 1998 - ANSI-style declarations 12 | * July 31, 1998 - Add error return to gaussj() 13 | * March 5, 2008 - Add integer matrix allocation/deallocation 14 | * 15 | * May 13, 2015 - Make small change to mateuler313() 16 | * 17 | * March 28, 2017 - Fix description of mateuler321(). 18 | * Add euler313(), eulerm123(), mateuler123(). 19 | * Add euler323(), mateuler323(). 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include "vec.h" 26 | #include "mat_ra.h" 27 | 28 | #define SWAP(a,b) {double temp=(a);(a)=(b);(b)=(temp);} 29 | 30 | /******************************************************************************/ 31 | /* Matrix times a vector */ 32 | 33 | void matvec(double mat[3][3], double vin[3], double vout[3]) 34 | { 35 | int i,j; 36 | 37 | for(i=0;i<3;++i) 38 | { 39 | vout[i]=0.0; 40 | for(j=0;j<3;++j) 41 | vout[i] += mat[i][j]*vin[j]; 42 | } 43 | return; 44 | } 45 | 46 | /******************************************************************************/ 47 | /* Matrix times a matrix */ 48 | 49 | void matmat(double mata[3][3], double matb[3][3], double matc[3][3]) 50 | { 51 | int i,j,k; 52 | 53 | for(i=0;i<3;++i) 54 | { 55 | for(j=0;j<3;++j) 56 | { 57 | matc[i][j]=0.0; 58 | for(k=0;k<3;++k) 59 | matc[i][j] += mata[i][k]*matb[k][j]; 60 | } 61 | } 62 | return; 63 | } 64 | 65 | /******************************************************************************/ 66 | /* Construct a rotation matrix that will rotate the coordinate system about 67 | * one of the coordinates axes by an angle (radians). 68 | * 69 | * nax = 0, 1, or 2 70 | * 71 | * The rotation matrix multiplies vectors expressed in the original coordinate 72 | * system to give components in the new coordinate system. 73 | * 74 | * Alternatively, the matrix rotates the vector by -angle around the given axis. 75 | */ 76 | 77 | void rotm1(int nax, double angle, double mat[3][3]) 78 | { 79 | double s; 80 | int i,j,n1,n2,n3; 81 | 82 | n1 = nax; 83 | n2 = (n1+1) % 3; 84 | n3 = (n2+1) % 3; 85 | s = sin(angle); 86 | 87 | for(i=0;i<3;++i) 88 | for(j=0;j<3;++j) 89 | mat[i][j] = 0.0; 90 | 91 | mat[n1][n1] = 1.0; 92 | mat[n3][n3] = mat[n2][n2] = cos(angle); 93 | mat[n2][n3] = s; 94 | mat[n3][n2] = -s; 95 | 96 | return; 97 | } 98 | 99 | /******************************************************************************/ 100 | /* Construct transpose of a matrix */ 101 | 102 | void trans(double mat[3][3], double trmat[3][3]) 103 | { 104 | int i,j; 105 | for(i=0;i<3;++i) 106 | for(j=0;j<3;++j) 107 | trmat[i][j] = mat[j][i]; 108 | return; 109 | } 110 | 111 | /******************************************************************************/ 112 | /* Construct rotation matrix from 3-2-1 Euler angles. 113 | * 114 | * The Euler angles specify the orientation of a new coordinate system 115 | * relative to an old coordinate system. 116 | * 117 | * The rotation matrix multiplies vectors expressed in the original coordinate 118 | * system to give components in the new coordinate system. 119 | */ 120 | 121 | void eulerm321(double euler[3], double rmat[3][3]) 122 | { 123 | double mat1[3][3], mat2[3][3], mata[3][3]; 124 | 125 | rotm1(2,euler[0],mat1); 126 | rotm1(1,euler[1],mat2); 127 | matmat(mat2,mat1,mata); 128 | rotm1(0,euler[2],mat1); 129 | matmat(mat1,mata,rmat); 130 | 131 | return; 132 | } 133 | 134 | /******************************************************************************/ 135 | /* Construct rotation matrix from 3-1-3 Euler angles. 136 | * 137 | * The Euler angles specify the orientation of a new coordinate system 138 | * relative to an old coordinate system. 139 | * 140 | * The rotation matrix multiplies vectors expressed in the original coordinate 141 | * system to give components in the new coordinate system. 142 | */ 143 | 144 | void eulerm313(double euler[3], double rmat[3][3]) 145 | { 146 | double mat1[3][3], mat2[3][3], mata[3][3]; 147 | 148 | rotm1(2,euler[0],mat1); 149 | rotm1(0,euler[1],mat2); 150 | matmat(mat2,mat1,mata); 151 | rotm1(2,euler[2],mat1); 152 | matmat(mat1,mata,rmat); 153 | 154 | return; 155 | } 156 | 157 | /******************************************************************************/ 158 | /* Construct rotation matrix from 3-2-3 Euler angles. 159 | * 160 | * The Euler angles specify the orientation of a new coordinate system 161 | * relative to an old coordinate system. 162 | * 163 | * The rotation matrix multiplies vectors expressed in the original coordinate 164 | * system to give components in the new coordinate system. 165 | */ 166 | 167 | void eulerm323(double euler[3], double rmat[3][3]) 168 | { 169 | double mat1[3][3], mat2[3][3], mata[3][3]; 170 | 171 | rotm1(2,euler[0],mat1); 172 | rotm1(1,euler[1],mat2); 173 | matmat(mat2,mat1,mata); 174 | rotm1(2,euler[2],mat1); 175 | matmat(mat1,mata,rmat); 176 | 177 | return; 178 | } 179 | 180 | /******************************************************************************/ 181 | /* Construct rotation matrix from 1-2-3 Euler angles. 182 | * 183 | * The Euler angles specify the orientation of a new coordinate system 184 | * relative to an old coordinate system. 185 | * 186 | * The rotation matrix multiplies vectors expressed in the original coordinate 187 | * system to give components in the new coordinate system. 188 | */ 189 | 190 | void eulerm123(double euler[3], double rmat[3][3]) 191 | { 192 | double mat1[3][3], mat2[3][3], mata[3][3]; 193 | 194 | rotm1(0,euler[0],mat1); 195 | rotm1(1,euler[1],mat2); 196 | matmat(mat2,mat1,mata); 197 | rotm1(2,euler[2],mat1); 198 | matmat(mat1,mata,rmat); 199 | 200 | return; 201 | } 202 | 203 | /******************************************************************************/ 204 | /* Construct rotation matrix from 2-1-3 Euler angles. 205 | * 206 | * The Euler angles specify the orientation of a new coordinate system 207 | * relative to an old coordinate system. 208 | * 209 | * The rotation matrix multiplies vectors expressed in the original coordinate 210 | * system to give components in the new coordinate system. 211 | */ 212 | 213 | void eulerm213(double euler[3], double rmat[3][3]) 214 | { 215 | double mat1[3][3], mat2[3][3], mata[3][3]; 216 | 217 | rotm1(1,euler[0],mat1); 218 | rotm1(0,euler[1],mat2); 219 | matmat(mat2,mat1,mata); 220 | rotm1(2,euler[2],mat1); 221 | matmat(mat1,mata,rmat); 222 | 223 | return; 224 | } 225 | 226 | /******************************************************************************/ 227 | /* Construct 3-2-1 Euler angles from a rotation matrix 228 | * 229 | * The Euler angles specify the orientation of a new coordinate system 230 | * relative to an old coordinate system. 231 | * 232 | * The rotation matrix multiplies vectors expressed in the original coordinate 233 | * system to give components in the new coordinate system. 234 | * 235 | * Matrix form is from "Spacecraft Attitude Determination and Control", 236 | * ed. J. Wertz 237 | */ 238 | 239 | void mateuler321(double rmat[3][3], double euler[3]) 240 | { 241 | double xlong, xlat, zp[3], zi[3]; 242 | int i; 243 | 244 | for(i=0;i<3;++i) 245 | { 246 | zp[i] = rmat[0][i]; 247 | zi[i] = rmat[2-i][2]; 248 | } 249 | dcrsph(zp,&xlong,&xlat); 250 | euler[0] = xlong; 251 | if (euler[0] > M_PI) 252 | euler[0] -= 2.0*M_PI; 253 | if (euler[0] < -M_PI) 254 | euler[0] += 2.0*M_PI; 255 | euler[1] = -xlat; 256 | dcrsph(zi,&xlong,&xlat); 257 | euler[2] = xlong; 258 | if (euler[2] > M_PI) 259 | euler[2] -= 2.0*M_PI; 260 | if (euler[2] < -M_PI) 261 | euler[2] += 2.0*M_PI; 262 | 263 | return; 264 | } 265 | 266 | /******************************************************************************/ 267 | /* Construct 3-1-3 Euler angles from a rotation matrix 268 | * 269 | * The Euler angles specify the orientation of a new coordinate system 270 | * relative to an old coordinate system. 271 | * 272 | * The rotation matrix multiplies vectors expressed in the original coordinate 273 | * system to give components in the new coordinate system. 274 | */ 275 | 276 | void mateuler313(double rmat[3][3], double euler[3]) 277 | { 278 | double xlong, xlat, zp[3], zi[3]; 279 | int i; 280 | 281 | for(i=0;i<3;++i) 282 | { 283 | zp[i] = rmat[2][i]; 284 | zi[i] = rmat[i][2]; 285 | } 286 | dcrsph(zp,&xlong,&xlat); 287 | euler[0] = xlong + M_PI_2; 288 | if (euler[0] > M_PI) 289 | euler[0] -= 2.0*M_PI; 290 | if (euler[0] < -M_PI) 291 | euler[0] += 2.0*M_PI; 292 | euler[1] = M_PI_2 - xlat; 293 | dcrsph(zi,&xlong,&xlat); 294 | euler[2] = -xlong+M_PI_2; 295 | if (euler[2] > M_PI) 296 | euler[2] -= 2.0*M_PI; 297 | if (euler[2] < -M_PI) 298 | euler[2] += 2.0*M_PI; 299 | 300 | return; 301 | } 302 | 303 | /******************************************************************************/ 304 | /* Construct 3-2-3 Euler angles from a rotation matrix 305 | * 306 | * The Euler angles specify the orientation of a new coordinate system 307 | * relative to an old coordinate system. 308 | * 309 | * The rotation matrix multiplies vectors expressed in the original coordinate 310 | * system to give components in the new coordinate system. 311 | * 312 | * Matrix form is from "Spacecraft Attitude Determination and Control", 313 | * ed. J. Wertz 314 | */ 315 | 316 | void mateuler323(double rmat[3][3], double euler[3]) 317 | { 318 | double xlong, xlat, zp[3], zi[3]; 319 | int i; 320 | 321 | for(i=0;i<3;++i) 322 | { 323 | zp[i] = rmat[2][i]; 324 | zi[i] = rmat[i][2]; 325 | } 326 | dcrsph(zp,&xlong,&xlat); 327 | euler[0] = xlong; 328 | if (euler[0] > M_PI) 329 | euler[0] -= 2.0*M_PI; 330 | if (euler[0] < -M_PI) 331 | euler[0] += 2.0*M_PI; 332 | euler[1] = M_PI_2 - xlat; 333 | dcrsph(zi,&xlong,&xlat); 334 | euler[2] = -xlong+M_PI; 335 | if (euler[2] > M_PI) 336 | euler[2] -= 2.0*M_PI; 337 | if (euler[2] < -M_PI) 338 | euler[2] += 2.0*M_PI; 339 | 340 | return; 341 | } 342 | 343 | /******************************************************************************/ 344 | /* Construct 1-2-3 Euler angles from a rotation matrix 345 | * 346 | * The Euler angles specify the orientation of a new coordinate system 347 | * relative to an old coordinate system. 348 | * 349 | * The rotation matrix multiplies vectors expressed in the original coordinate 350 | * system to give components in the new coordinate system. 351 | * 352 | * Matrix form is from "Spacecraft Attitude Determination and Control", 353 | * ed. J. Wertz 354 | */ 355 | 356 | void mateuler123(double rmat[3][3], double euler[3]) 357 | { 358 | double xlong, xlat, zp[3], zi[3]; 359 | int i; 360 | 361 | for(i=0;i<3;++i) 362 | { 363 | zp[i] = rmat[2][2-i]; 364 | zi[i] = rmat[i][0]; 365 | } 366 | dcrsph(zp,&xlong,&xlat); 367 | euler[0] = -xlong; 368 | if (euler[0] > M_PI) 369 | euler[0] -= 2.0*M_PI; 370 | if (euler[0] < -M_PI) 371 | euler[0] += 2.0*M_PI; 372 | euler[1] = xlat; 373 | dcrsph(zi,&xlong,&xlat); 374 | euler[2] = -xlong; 375 | if (euler[2] > M_PI) 376 | euler[2] -= 2.0*M_PI; 377 | if (euler[2] < -M_PI) 378 | euler[2] += 2.0*M_PI; 379 | 380 | return; 381 | } 382 | 383 | /******************************************************************************/ 384 | /* Construct 2-1-3 Euler angles from a rotation matrix 385 | * 386 | * The Euler angles specify the orientation of a new coordinate system 387 | * relative to an old coordinate system. 388 | * 389 | * The rotation matrix multiplies vectors expressed in the original coordinate 390 | * system to give components in the new coordinate system. 391 | * 392 | * Matrix form is from "Spacecraft Attitude Determination and Control", 393 | * ed. J. Wertz 394 | */ 395 | 396 | void mateuler213(double rmat[3][3], double euler[3]) 397 | { 398 | double xlong, xlat, zp[3], zi[3]; 399 | int i; 400 | 401 | zp[0] = rmat[2][2]; 402 | zp[1] = rmat[2][0]; 403 | zp[2] = -rmat[2][1]; 404 | zi[0] = rmat[1][1]; 405 | zi[1] = rmat[0][1]; 406 | zi[2] = -rmat[2][1]; 407 | 408 | dcrsph(zp,&xlong,&xlat); 409 | euler[0] = xlong; 410 | if (euler[0] > M_PI) 411 | euler[0] -= 2.0*M_PI; 412 | if (euler[0] < -M_PI) 413 | euler[0] += 2.0*M_PI; 414 | euler[1] = xlat; 415 | if (euler[1] > M_PI) 416 | euler[1] -= 2.0*M_PI; 417 | if (euler[1] < -M_PI) 418 | euler[1] += 2.0*M_PI; 419 | dcrsph(zi,&xlong,&xlat); 420 | euler[2] = xlong; 421 | if (euler[2] > M_PI) 422 | euler[2] -= 2.0*M_PI; 423 | if (euler[2] < -M_PI) 424 | euler[2] += 2.0*M_PI; 425 | 426 | return; 427 | } 428 | 429 | /**************************************************************************/ 430 | /* matrix allocation/deallocation based on NRinC */ 431 | 432 | double **matrix(int r, int c) 433 | { 434 | int i; 435 | double **m; 436 | 437 | if (!(m=(double **) malloc((unsigned)(r)*sizeof(double*)))) 438 | fprintf(stderr, "allocation failure in matrix\n"); 439 | 440 | for(i=0;i=0;i--) 452 | free((char*)(m[i])); 453 | free((char*)(m)); 454 | } 455 | 456 | /**************************************************************************/ 457 | 458 | void print_matrix(double **m, int r, int c, FILE *fp) 459 | { 460 | int i, j; 461 | 462 | for (i=0;i=0;i--) 497 | free((char*)(m[i])); 498 | free((char*)(m)); 499 | } 500 | 501 | /**************************************************************************/ 502 | 503 | void print_imatrix(int **m, int r, int c, FILE *fp) 504 | { 505 | int i, j; 506 | 507 | for (i=0;i=big) { 535 | big=fa; 536 | irow=j; 537 | icol=k; 538 | } 539 | } else if (ipiv[k] >1) {fprintf(stderr,"GAUSSJ1: singular matrix\n"); 540 | return(1);} 541 | } 542 | ++(ipiv[icol]); 543 | if (irow != icol) { 544 | for (l=0;l=0;l--) 566 | { 567 | if (indxr[l] != indxc [l]) 568 | for (k=0; k 33 | #include 34 | #include 35 | #include 36 | #include "vec.h" 37 | #include "mat_ra3.h" 38 | 39 | // #define NSTAR 10000 40 | 41 | #define CCDWD_T 2048 42 | #define CCDHT_T 2058 43 | #define ROWA 44 44 | #define ROWB 44 45 | #define COLDK_T 20 46 | 47 | #define NCAM 4 // no. of cameras 48 | #define NCCD 4 // no. of CCDs per camera 49 | #define NOPTCON 6 50 | 51 | double eulcam[4][3], optcon[4][6], ccdxy0[4][4][2]; 52 | double pixsz[4][4][2], ccdang[4][4], ccdtilt[4][4][2]; 53 | double asymang[NCAM], asymfac[NCAM]; 54 | 55 | double ra_ab, dec_ab; 56 | int nst, icam; 57 | double rmat1[3][3], rmat2[3][3], rmat3[3][3], rmat4[3][3]; 58 | double ra_sc, dec_sc, roll_sc; 59 | 60 | double fpxlo[4], fpxhi[4], fpylo[4], fpyhi[4]; 61 | double cpxlo[4], cpxhi[4], cpylo[4], cpyhi[4]; 62 | double dtor; 63 | 64 | #define NSEC 69 65 | // Hard coded spacecraft ra, dec, and rolls 66 | double ras[NSEC] = {352.6844, 16.5571, 36.3138, 55.0070, 73.5382,\ 67 | 92.0096,110.2559,128.1156,145.9071,\ 68 | 165.0475,189.1247,229.5885,298.6671,\ 69 | 276.7169,280.3985,282.4427,351.2381,\ 70 | 16.1103, 60.2026,129.3867,171.7951,197.1008,\ 71 | 217.2879,261.4516,265.6098,270.1381,\ 72 | 326.8525,357.2944, 18.9190, 38.3564,\ 73 | 57.6357, 77.1891, 96.5996,115.2951,\ 74 | 133.2035,150.9497,170.2540,195.7176,\ 75 | 242.1981,\ 76 | 273.0766,277.6209, 13.0140, 49.5260,\ 77 | 89.6066,130.2960,157.6997,143.3807,\ 78 | 179.4254,202.6424,221.8575,239.4257,\ 79 | 266.3618,270.8126,290.1210,307.8655,\ 80 | 324.2778,344.2275, 9.3118, 52.9755,\ 81 | 125.6742,118.0446,135.2412,153.0613,\ 82 | 173.2653,201.6239,259.1702,326.7691,\ 83 | 359.2829, 20.0449}; 84 | double decs[NSEC] = {-64.8531,-54.0160,-44.2590,-36.6420,-31.9349,\ 85 | -30.5839,-32.6344,-37.7370,-45.3044,\ 86 | -54.8165,-65.5369,-75.1256,-76.3281,\ 87 | 62.4756, 64.0671, 66.1422, 57.8456,\ 88 | 67.9575, 76.2343, 75.2520, 65.1924, 53.7434,\ 89 | 43.8074, 63.1181, 61.9383, 61.5637,\ 90 | -72.4265,-63.0056,-52.8296,-43.3178,\ 91 | -35.7835,-31.3957,-30.7848,-33.7790,\ 92 | -39.6871,-47.7512,-57.3725,-67.8307,\ 93 | -76.3969,\ 94 | 61.7450, 62.7640, 6.3337, 18.9737,\ 95 | 24.1343, 19.0181, 10.0922, 73.1125,\ 96 | 62.1038, 50.9532, 41.7577, 35.2333,\ 97 | 61.8190, 61.5761, 32.6073, 37.6464,\ 98 | 46.3448, 56.4121, 67.6524, 77.1746,\ 99 | 77.3113,-36.0902,-42.2415,-50.6996,\ 100 | -60.8650,-71.5724,-78.7974,-74.2796,\ 101 | -64.2357,-54.2315}; 102 | double rolls[NSEC] = {222.1532,220.4335,213.0384,202.8302,191.0517,\ 103 | 178.6367,166.4476,155.3091,145.9163,\ 104 | 139.1724,138.0761,153.9773,198.9378,\ 105 | 32.2329, 55.4277, 79.4699, 41.9686,\ 106 | 40.5453, 19.6463,334.5689,317.9495,319.6992,\ 107 | 327.4246,317.2624,339.5293, 0.6038,\ 108 | 214.5061,222.5216,219.7970,212.0441,\ 109 | 201.2334,188.6263,175.5369,163.1916,\ 110 | 152.4006,143.7306,138.1685,139.3519,\ 111 | 161.5986,\ 112 | 14.1539, 37.2224,292.8009,284.9617,\ 113 | 270.1557,255.0927,248.4063,327.1020,\ 114 | 317.4166,321.3516,329.7340,339.8650,\ 115 | 343.1429, 3.6838, 13.4565, 24.5369,\ 116 | 36.2524, 44.0100, 45.3615, 26.5121,\ 117 | 337.3244,162.2198,151.5884,142.7405,\ 118 | 137.2810,140.7443,173.9147,217.4678,\ 119 | 226.0975,222.7721}; 120 | 121 | /******************************************************************************/ 122 | // All input angles are in degrees. 123 | // Focal plane geometry parameters are hardcoded 124 | // using rfpg5_c#kb.txt files for parameters 125 | // awk -v d=";" '{print $3 d}' rfpg5_c1kb.txt > temp.txt 126 | // paste -d' ' param_load_template.txt temp.txt 127 | // search replace icam with # of camera starting with 0 128 | void set_fpg_parameters () { 129 | // CAMERA 1 130 | eulcam[0][0] = 0.101588; 131 | eulcam[0][1] = -36.022035; 132 | eulcam[0][2] = 90.048315; 133 | optcon[0][0] = 145.948116; 134 | optcon[0][1] = 1.00000140; 135 | optcon[0][2] = 0.24779006; 136 | optcon[0][3] = -0.22681254; 137 | optcon[0][4] = 10.78243356; 138 | optcon[0][5] = -34.97817276; 139 | asymang[0] = 0.00000000; 140 | asymfac[0] = 1.00000000; 141 | ccdxy0[0][0][0] = 31.573417; 142 | ccdxy0[0][0][1] = 31.551637; 143 | pixsz[0][0][0] = 0.015000; 144 | pixsz[0][0][1] = 0.015000; 145 | ccdang[0][0] = 179.980833; 146 | ccdtilt[0][0][0] = 0.000000; 147 | ccdtilt[0][0][1] = 0.000000; 148 | ccdxy0[0][1][0] = -0.906060; 149 | ccdxy0[0][1][1] = 31.536148; 150 | pixsz[0][1][0] = 0.015000; 151 | pixsz[0][1][1] = 0.015000; 152 | ccdang[0][1] = 180.000000; 153 | ccdtilt[0][1][0] = 0.000000; 154 | ccdtilt[0][1][1] = 0.000000; 155 | ccdxy0[0][2][0] = -31.652818; 156 | ccdxy0[0][2][1] = -31.438350; 157 | pixsz[0][2][0] = 0.015000; 158 | pixsz[0][2][1] = 0.015000; 159 | ccdang[0][2] = -0.024851; 160 | ccdtilt[0][2][0] = 0.000000; 161 | ccdtilt[0][2][1] = 0.000000; 162 | ccdxy0[0][3][0] = 0.833161; 163 | ccdxy0[0][3][1] = -31.458180; 164 | pixsz[0][3][0] = 0.015000; 165 | pixsz[0][3][1] = 0.015000; 166 | ccdang[0][3] = 0.001488; 167 | ccdtilt[0][3][0] = 0.000000; 168 | ccdtilt[0][3][1] = 0.000000; 169 | // Camera 2 170 | eulcam[1][0] = -0.179412; 171 | eulcam[1][1] = -12.017260; 172 | eulcam[1][2] = 90.046500; 173 | optcon[1][0] = 145.989933; 174 | optcon[1][1] = 1.00000140; 175 | optcon[1][2] = 0.24069345; 176 | optcon[1][3] = 0.15391120; 177 | optcon[1][4] = 4.05433503; 178 | optcon[1][5] = 3.43136895; 179 | asymang[1] = 0.00000000; 180 | asymfac[1] = 1.00000000; 181 | ccdxy0[1][0][0] = 31.653635; 182 | ccdxy0[1][0][1] = 31.470291; 183 | pixsz[1][0][0] = 0.015000; 184 | pixsz[1][0][1] = 0.015000; 185 | ccdang[1][0] = 180.010890; 186 | ccdtilt[1][0][0] = 0.000000; 187 | ccdtilt[1][0][1] = 0.000000; 188 | ccdxy0[1][1][0] = -0.827405; 189 | ccdxy0[1][1][1] = 31.491388; 190 | pixsz[1][1][0] = 0.015000; 191 | pixsz[1][1][1] = 0.015000; 192 | ccdang[1][1] = 180.000000; 193 | ccdtilt[1][1][0] = 0.000000; 194 | ccdtilt[1][1][1] = 0.000000; 195 | ccdxy0[1][2][0] = -31.543794; 196 | ccdxy0[1][2][1] = -31.550699; 197 | pixsz[1][2][0] = 0.015000; 198 | pixsz[1][2][1] = 0.015000; 199 | ccdang[1][2] = -0.006624; 200 | ccdtilt[1][2][0] = 0.000000; 201 | ccdtilt[1][2][1] = 0.000000; 202 | ccdxy0[1][3][0] = 0.922834; 203 | ccdxy0[1][3][1] = -31.557268; 204 | pixsz[1][3][0] = 0.015000; 205 | pixsz[1][3][1] = 0.015000; 206 | ccdang[1][3] = -0.015464; 207 | ccdtilt[1][3][0] = 0.000000; 208 | ccdtilt[1][3][1] = 0.000000; 209 | // CAMERA 3 210 | eulcam[2][0] = 0.066596; 211 | eulcam[2][1] = 12.007750; 212 | eulcam[2][2] = -89.889085; 213 | optcon[2][0] = 146.006602; 214 | optcon[2][1] = 1.00000140; 215 | optcon[2][2] = 0.23452229; 216 | optcon[2][3] = 0.33552009; 217 | optcon[2][4] = 1.92009863; 218 | optcon[2][5] = 12.48880182; 219 | asymang[2] = 0.00000000; 220 | asymfac[2] = 1.00000000; 221 | ccdxy0[2][0][0] = 31.615486; 222 | ccdxy0[2][0][1] = 31.413644; 223 | pixsz[2][0][0] = 0.015000; 224 | pixsz[2][0][1] = 0.015000; 225 | ccdang[2][0] = 179.993948; 226 | ccdtilt[2][0][0] = 0.000000; 227 | ccdtilt[2][0][1] = 0.000000; 228 | ccdxy0[2][1][0] = -0.832993; 229 | ccdxy0[2][1][1] = 31.426621; 230 | pixsz[2][1][0] = 0.015000; 231 | pixsz[2][1][1] = 0.015000; 232 | ccdang[2][1] = 180.000000; 233 | ccdtilt[2][1][0] = 0.000000; 234 | ccdtilt[2][1][1] = 0.000000; 235 | ccdxy0[2][2][0] = -31.548296; 236 | ccdxy0[2][2][1] = -31.606976; 237 | pixsz[2][2][0] = 0.015000; 238 | pixsz[2][2][1] = 0.015000; 239 | ccdang[2][2] = 0.000298; 240 | ccdtilt[2][2][0] = 0.000000; 241 | ccdtilt[2][2][1] = 0.000000; 242 | ccdxy0[2][3][0] = 0.896018; 243 | ccdxy0[2][3][1] = -31.569542; 244 | pixsz[2][3][0] = 0.015000; 245 | pixsz[2][3][1] = 0.015000; 246 | ccdang[2][3] = -0.006464; 247 | ccdtilt[2][3][0] = 0.000000; 248 | ccdtilt[2][3][1] = 0.000000; 249 | // Camera 4 250 | eulcam[3][0] = 0.030756; 251 | eulcam[3][1] = 35.978116; 252 | eulcam[3][2] = -89.976802; 253 | optcon[3][0] = 146.039793; 254 | optcon[3][1] = 1.00000140; 255 | optcon[3][2] = 0.23920416; 256 | optcon[3][3] = 0.13349450; 257 | optcon[3][4] = 4.77768896; 258 | optcon[3][5] = -1.75114744; 259 | asymang[3] = 0.00000000; 260 | asymfac[3] = 1.00000000; 261 | ccdxy0[3][0][0] = 31.575820; 262 | ccdxy0[3][0][1] = 31.316510; 263 | pixsz[3][0][0] = 0.015000; 264 | pixsz[3][0][1] = 0.015000; 265 | ccdang[3][0] = 179.968217; 266 | ccdtilt[3][0][0] = 0.000000; 267 | ccdtilt[3][0][1] = 0.000000; 268 | ccdxy0[3][1][0] = -0.890877; 269 | ccdxy0[3][1][1] = 31.363511; 270 | pixsz[3][1][0] = 0.015000; 271 | pixsz[3][1][1] = 0.015000; 272 | ccdang[3][1] = 180.000000; 273 | ccdtilt[3][1][0] = 0.000000; 274 | ccdtilt[3][1][1] = 0.000000; 275 | ccdxy0[3][2][0] = -31.630470; 276 | ccdxy0[3][2][1] = -31.716942; 277 | pixsz[3][2][0] = 0.015000; 278 | pixsz[3][2][1] = 0.015000; 279 | ccdang[3][2] = -0.024359; 280 | ccdtilt[3][2][0] = 0.000000; 281 | ccdtilt[3][2][1] = 0.000000; 282 | ccdxy0[3][3][0] = 0.824159; 283 | ccdxy0[3][3][1] = -31.728751; 284 | pixsz[3][3][0] = 0.015000; 285 | pixsz[3][3][1] = 0.015000; 286 | ccdang[3][3] = -0.024280; 287 | ccdtilt[3][3][0] = 0.000000; 288 | ccdtilt[3][3][1] = 0.000000; 289 | } 290 | /******************************************************************************/ 291 | // Print a 3x3 matrix 292 | 293 | void prmat(double rm[3][3], FILE *fp) 294 | { 295 | int i, j; 296 | 297 | fprintf(fp,"\n"); 298 | for(i=0;i<3;++i) { 299 | fprintf(fp,"%d",i); 300 | for(j=0;j<3;++j) { 301 | fprintf(fp," %f",rm[i][j]); 302 | } 303 | fprintf(fp,"\n"); 304 | } 305 | fprintf(fp,"\n"); 306 | } 307 | 308 | /******************************************************************************/ 309 | // Multiply a 3-vector in celestial coordinates by rmat1 to get a 3-vector 310 | // in S/C coordinates. 311 | 312 | void sky_to_sc_mat(double radecroll[3]) 313 | { 314 | double xeul[3]; 315 | int i; 316 | 317 | xeul[0] = dtor*radecroll[0]; 318 | xeul[1] = (M_PI/2.0) - (dtor*radecroll[1]); 319 | xeul[2] = dtor*radecroll[2]; 320 | xeul[2] += M_PI; 321 | 322 | eulerm323(xeul,rmat1); 323 | } 324 | 325 | /******************************************************************************/ 326 | // Multiply a 3-vector in S/C coordinates by rmat2 to get a 3-vector 327 | // in camera no. icam coordinates. 328 | 329 | void sc_to_cam_mat(double euler[3]) 330 | { 331 | double xeul[3], angle, rm[3][3]; 332 | int i; 333 | 334 | for(i=0;i<3;++i) { 335 | xeul[i] = dtor*euler[i]; 336 | } 337 | 338 | eulerm323(xeul,rmat2); 339 | //prmat(rmat2,stderr); 340 | } 341 | 342 | /******************************************************************************/ 343 | /* return 1 if star is in the field of view of camera no. icam 344 | * return 0 otherwise 345 | */ 346 | 347 | int star_in_fov(double lngdeg, double latdeg) 348 | { 349 | int ifov; 350 | double latr, lngr, v[3]; 351 | 352 | lngr = dtor*lngdeg; 353 | latr = dtor*latdeg; 354 | 355 | ifov = 0; 356 | if(latdeg > 70.0) { 357 | dsphcr(lngr,latr,v); 358 | dnorm(v); 359 | // 12.0 -> 12.2 in the following 2 lines (June 16, 2018) 360 | if( (fabs(atan(v[0]/v[2])) <= (12.5*dtor)) && 361 | (fabs(atan(v[1]/v[2])) <= (12.5*dtor)) ) { 362 | ifov = 1; 363 | } 364 | } 365 | 366 | return(ifov); 367 | } 368 | 369 | /******************************************************************************/ 370 | // Find new coordinates after rotating coordinate system by angle_deg 371 | 372 | void xyrotate(double angle_deg, double xyin[2], double xyout[2]) 373 | { 374 | double ca, sa; 375 | 376 | ca = cos(dtor*angle_deg); 377 | sa = sin(dtor*angle_deg); 378 | xyout[0] = (ca*xyin[0]) + (sa*xyin[1]); 379 | xyout[1] = (-sa*xyin[0]) + (ca*xyin[1]); 380 | } 381 | 382 | /******************************************************************************/ 383 | // stretch predicted focal plane position by 'asymfac' parallel to 384 | // azimuthal angle 'asymang' 385 | 386 | void make_az_asym(double xyin[2], double xyout[2]) 387 | { 388 | double xyp[2], xypa[2]; 389 | 390 | xyrotate(asymang[icam],xyin,xyp); 391 | xypa[0] = asymfac[icam]*xyp[0]; 392 | xypa[1] = xyp[1]; 393 | xyrotate(-asymang[icam],xypa,xyout); 394 | } 395 | 396 | /******************************************************************************/ 397 | /* TSIG/AML optics model 398 | * 399 | * lng_deg, lat_deg are the spherical coordinates of the direction to the 400 | * star (degrees) 401 | * 402 | * xyfp[2] contains the x and y focal plane coordinates in mm 403 | */ 404 | 405 | void optics_fp(double lng_deg, double lat_deg, double xyfp[2]) 406 | { 407 | double thetar, tanth, cphi, sphi, ttsq, rfp0, rfp, xytmp[2]; 408 | int i; 409 | 410 | thetar = (M_PI/2.0) - (lat_deg*dtor); 411 | tanth = tan(thetar); 412 | cphi = cos(dtor*lng_deg); 413 | sphi = sin(dtor*lng_deg); 414 | rfp0 = optcon[icam][0]*tanth; 415 | rfp = 0.0; 416 | for(i=1;i= 0.0) { 461 | if(xya[1] >= 0.0) { 462 | iccd = 0; 463 | xyb[0] = xya[0] - ccdxy0[icam][iccd][0]; 464 | xyb[1] = xya[1] - ccdxy0[icam][iccd][1]; 465 | xyrotate(ccdang[icam][iccd],xyb,xyccd); 466 | ccdpx[0] = (xyccd[0]/pixsz[icam][iccd][0]) - 0.5; 467 | ccdpx[1] = (xyccd[1]/pixsz[icam][iccd][1]) - 0.5; 468 | fitpx[0] = (CCDWD_T - ccdpx[0]) + CCDWD_T + 2*ROWA + ROWB - 1.0; 469 | fitpx[1] = (CCDHT_T - ccdpx[1]) + CCDHT_T + 2*COLDK_T - 1.0; 470 | } 471 | else { 472 | iccd = 3; 473 | xyb[0] = xya[0] - ccdxy0[icam][iccd][0]; 474 | xyb[1] = xya[1] - ccdxy0[icam][iccd][1]; 475 | xyrotate(ccdang[icam][iccd],xyb,xyccd); 476 | ccdpx[0] = (xyccd[0]/pixsz[icam][iccd][0]) - 0.5; 477 | ccdpx[1] = (xyccd[1]/pixsz[icam][iccd][1]) - 0.5; 478 | fitpx[0] = (ccdpx[0]) + CCDWD_T + 2*ROWA + ROWB; 479 | fitpx[1] = (ccdpx[1]); 480 | } 481 | } 482 | else { 483 | if(xya[1] >= 0.0) { 484 | iccd = 1; 485 | xyb[0] = xya[0] - ccdxy0[icam][iccd][0]; 486 | xyb[1] = xya[1] - ccdxy0[icam][iccd][1]; 487 | xyrotate(ccdang[icam][iccd],xyb,xyccd); 488 | ccdpx[0] = (xyccd[0]/pixsz[icam][iccd][0]) - 0.5; 489 | ccdpx[1] = (xyccd[1]/pixsz[icam][iccd][1]) - 0.5; 490 | fitpx[0] = (CCDWD_T - ccdpx[0]) + ROWA - 1.0; 491 | fitpx[1] = (CCDHT_T - ccdpx[1]) + CCDHT_T + 2*COLDK_T - 1.0; 492 | } 493 | else { 494 | iccd = 2; 495 | xyb[0] = xya[0] - ccdxy0[icam][iccd][0]; 496 | xyb[1] = xya[1] - ccdxy0[icam][iccd][1]; 497 | xyrotate(ccdang[icam][iccd],xyb,xyccd); 498 | ccdpx[0] = (xyccd[0]/pixsz[icam][iccd][0]) - 0.5; 499 | ccdpx[1] = (xyccd[1]/pixsz[icam][iccd][1]) - 0.5; 500 | fitpx[0] = (ccdpx[0]) + ROWA; 501 | fitpx[1] = (ccdpx[1]); 502 | } 503 | } 504 | /* fprintf(stderr,"mm_to_pix(): %d %f %f %f %f %f %f %f %f\n", 505 | iccd,xy[0],xy[1],xyccd[0],xyccd[1],ccdpx[0],ccdpx[1],fitpx[0],fitpx[1]); 506 | */ 507 | return(iccd); 508 | } 509 | 510 | int ccdpx_to_outpx(double ccdpx[2], double napx[2], int *edgeWarn) 511 | { 512 | // onSil flag indicates whether star is actually on silicon 513 | // edgePix - stars within edgePix set edgeWarn flag == 1 514 | int onSil=0, edgePix=6; 515 | double xUse, yUse, ymaxCoord, xmaxCoord, xMin; 516 | *edgeWarn = 0; 517 | 518 | napx[0] = -1.0; 519 | napx[1] = -1.0; 520 | xUse = ccdpx[0] + 45.0; 521 | yUse = ccdpx[1] + 1.0; 522 | xMin = 44.0; 523 | ymaxCoord = 2049.0; 524 | xmaxCoord = 2093.0; 525 | if (xUse>xMin && yUse>0.0 && xUse=(xmaxCoord-edgePix)) { 531 | *edgeWarn=1; 532 | } 533 | if (yUse<=edgePix) { 534 | *edgeWarn=1; 535 | } 536 | if (yUse>=(ymaxCoord-edgePix)) { 537 | *edgeWarn=1; 538 | } 539 | napx[0] = xUse; 540 | napx[1] = yUse; 541 | } 542 | return(onSil); 543 | } 544 | /******************************************************************************/ 545 | 546 | void usage(const char *prog, int argc) 547 | { 548 | fprintf(stderr,"Usage: %s RA_star Dec_star in [degrees]\n",prog); 549 | exit(-1); 550 | } 551 | 552 | /******************************************************************************/ 553 | 554 | void tess_stars2px(double ra_ab, double dec_ab) 555 | { 556 | double radecroll[3], eulc[3], vstar[3], vcam[3], lng, lat, lngd, latd; 557 | double rdr[3]; 558 | double xyfp[2], ccdpx[2], fitpx[2], napx[2]; 559 | int iccd, i, jccd, kccd, j, edgeWarn, fndAny, iSec; 560 | 561 | 562 | dtor = M_PI/180.0; 563 | fndAny = 0; 564 | set_fpg_parameters(); 565 | 566 | // Start loop over sectors 567 | for (iSec=0;iSec 13 | #include "vec.h" 14 | #define PI 3.14159265358979323846 15 | #define DR (PI/180.0) 16 | 17 | /*********************************************************************/ 18 | /* Cross product of 2 3-vectors - c[3] = a[3] cross b[3] */ 19 | 20 | void dcross(double a[3], double b[3], double c[3]) 21 | { 22 | c[0] = a[1]*b[2]-a[2]*b[1]; 23 | c[1] = a[2]*b[0]-a[0]*b[2]; 24 | c[2] = a[0]*b[1]-a[1]*b[0]; 25 | 26 | return; 27 | } 28 | 29 | /*********************************************************************/ 30 | 31 | /* Cartesian vector to spherical coordinates conversion */ 32 | /* ra, dec are in radians */ 33 | void dcrsph(double vec[3], double *dra, double *ddec) 34 | { 35 | double len, ra, dec; 36 | 37 | ra=0.0; 38 | dec=0.0; 39 | len = sqrt(dot(vec,vec)); 40 | if (len > 0.0) 41 | { 42 | dec = asin(vec[2]/len); 43 | if ( (vec[0] != 0.0) || (vec[1] != 0.0) ) 44 | { 45 | ra = atan2(vec[1],vec[0]); 46 | if (ra < 0.0) 47 | ra += 2.0*PI; 48 | } 49 | } 50 | *dra = ra; 51 | *ddec = dec; 52 | return; 53 | } 54 | 55 | /*********************************************************************/ 56 | /* Normalize a 3-vector to be of unit length - if length is non-zero */ 57 | void dnorm(double vec[3]) 58 | { 59 | int i; 60 | double len; 61 | 62 | len = vec[0]*vec[0]+vec[1]*vec[1]+vec[2]*vec[2]; 63 | len = sqrt(len); 64 | if (len > 0.0) 65 | { 66 | for(i=0;i<3;i++) 67 | vec[i] = vec[i]/len; 68 | } 69 | return; 70 | } 71 | 72 | /*********************************************************************/ 73 | double dot(double a[3], double b[3]) 74 | { 75 | double dprod; 76 | dprod = a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; 77 | return(dprod); 78 | } 79 | 80 | /*********************************************************************/ 81 | /* Rotate a vector around an axis by a given angle (in radians) */ 82 | void drotate(double vec[3], double axis[3], double angle, double rotvec[3]) 83 | { 84 | double vecx,vecz; 85 | double cang,sang,unaxis[3],y[3],x[3]; 86 | int i; 87 | 88 | for(i=0;i<3;++i) 89 | unaxis[i] = axis[i]; 90 | dnorm(unaxis); 91 | 92 | cang = cos(angle); 93 | sang = sin(angle); 94 | dcross(unaxis,vec,y); 95 | dnorm(y); 96 | dcross(y,unaxis,x); 97 | dnorm(x); 98 | vecx = dot(vec,x); 99 | vecz = dot(vec,unaxis); 100 | for(i=0;i<3;++i) 101 | rotvec[i] = vecz*unaxis[i]+vecx*(cang*x[i]+sang*y[i]); 102 | 103 | return; 104 | } 105 | 106 | /*********************************************************************/ 107 | void dsphcr(double ra, double dec, double vec[3]) 108 | { 109 | double sinra,cosra,sindec,cosdec; 110 | 111 | sinra = sin(ra); 112 | cosra = cos(ra); 113 | sindec = sin(dec); 114 | cosdec = cos(dec); 115 | 116 | vec[0] = cosra*cosdec; 117 | vec[1] = sinra*cosdec; 118 | vec[2] = sindec; 119 | 120 | return; 121 | } 122 | 123 | /*********************************************************************/ 124 | /* vec2 = factor * vec1 */ 125 | 126 | void scale_vec(double factor, double vec1[3], double vec2[3]) 127 | { 128 | vec2[0] = factor*vec1[0]; 129 | vec2[1] = factor*vec1[1]; 130 | vec2[2] = factor*vec1[2]; 131 | } 132 | 133 | /*********************************************************************/ 134 | 135 | --------------------------------------------------------------------------------