├── .github └── workflows │ ├── publish-to-pypi.yml │ ├── publish-to-test-pypi.yml │ └── tests.yml ├── .gitignore ├── .readthedocs.yaml ├── AUTHORS.rst ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── basic_usage.rst ├── conf.py ├── images │ └── uvplot.png ├── index.rst ├── install.rst ├── io.rst ├── license.rst ├── requirements.txt └── uvtable.rst ├── pyproject.toml ├── setup.cfg ├── setup.py └── uvplot ├── __init__.py ├── _set_unique_version.py ├── constants.py ├── example.py ├── io.py ├── tests.py └── uvtable.py /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to TestPyPI and PyPI 2 | 3 | on: 4 | push: 5 | branches: 6 | # Push events only on master branch 7 | - master 8 | 9 | jobs: 10 | build-n-publish: 11 | name: Build and publish Python 🐍 distributions 📦 to TestPyPI and PyPI 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python 3.7 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: 3.7 19 | - name: Install pypa/build 20 | run: >- 21 | python -m 22 | pip install 23 | build 24 | --user 25 | - name: Build a binary wheel and a source tarball 26 | run: >- 27 | python -m 28 | build 29 | --sdist 30 | --wheel 31 | --outdir dist/ 32 | . 33 | - name: Publish distribution 📦 to Test PyPI 34 | if: startsWith(github.ref, 'refs/tags') 35 | uses: pypa/gh-action-pypi-publish@master 36 | with: 37 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 38 | repository_url: https://test.pypi.org/legacy/ 39 | - name: Publish distribution 📦 to PyPI 40 | if: startsWith(github.ref, 'refs/tags') 41 | uses: pypa/gh-action-pypi-publish@master 42 | with: 43 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/publish-to-test-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI 2 | 3 | # branch filtering: 4 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet 5 | 6 | on: 7 | push: 8 | branches: 9 | # Push events to all branches except master 10 | # https://stackoverflow.com/questions/57699839/github-actions-how-to-target-all-branches-except-master 11 | - '**' 12 | - '!master' 13 | 14 | jobs: 15 | build-n-publish: 16 | name: Build and publish Python 🐍 distributions 📦 to TestPyPI 17 | runs-on: ubuntu-20.04 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up Python 3.7 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: 3.7 25 | - name: Set a temporary unique version number 26 | # TestPyPI does not allow uploading two packages with same version number 27 | run: python uvplot/_set_unique_version.py 28 | - name: Install pypa/build 29 | run: >- 30 | python -m 31 | pip install 32 | build 33 | --user 34 | - name: Build a binary wheel and a source tarball 35 | run: >- 36 | python -m 37 | build 38 | --sdist 39 | --wheel 40 | --outdir dist/ 41 | . 42 | - name: Publish distribution 📦 to Test PyPI 43 | uses: pypa/gh-action-pypi-publish@master 44 | with: 45 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 46 | repository_url: https://test.pypi.org/legacy/ -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | # Run this workflow every time a new commit pushed to your repository 4 | on: push 5 | 6 | jobs: 7 | test: 8 | name: Run tests 9 | runs-on: ubuntu-20.04 10 | 11 | strategy: 12 | matrix: 13 | python-version: [3.6, 3.7, 3.8, 3.9] 14 | 15 | steps: 16 | # Checks out a copy of your repository on the ubuntu-latest machine 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Display Python version 24 | run: python -c "import sys; print(sys.version)" 25 | - name: Install dependencies 26 | run: pip install matplotlib numpy pytest sphinx 27 | - name: Install 28 | run: pip install . 29 | - name: Run unit tests 30 | run: py.test uvplot/tests.py -------------------------------------------------------------------------------- /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 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 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # JetBrains 104 | .idea/ 105 | 106 | # MacOS 107 | *.DS_Store 108 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | builder: html 11 | configuration: docs/conf.py 12 | 13 | ## Optionally build your docs in additional formats such as PDF 14 | #formats: 15 | # - pdf 16 | 17 | # Optionally set the version of Python and requirements required to build your docs 18 | python: 19 | version: 3.7 20 | install: 21 | - requirements: docs/requirements.txt 22 | - method: pip 23 | path: . 24 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | .. :authors: 2 | 3 | Author: 4 | - `Marco Tazzari (University of Cambridge) `_ 5 | 6 | Contributions to the code base: 7 | - `Patrick Cronin-Coltsmann `_ 8 | - `Grant Kennedy `_ 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | pypi_update: 2 | python setup.py sdist upload -r pypi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uvplot 2 | 3 | [![image](https://github.com/mtazzari/uvplot/actions/workflows/tests.yml/badge.svg)](https://github.com/mtazzari/uvplot/actions/workflows/tests.yml) 4 | [![image](https://img.shields.io/pypi/v/uvplot.svg)](https://pypi.python.org/pypi/uvplot) 5 | [![image](https://img.shields.io/github/release/mtazzari/uvplot/all.svg)](https://github.com/mtazzari/uvplot/releases) 6 | [![image](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) 7 | [![image](https://zenodo.org/badge/105298533.svg)](https://zenodo.org/badge/latestdoi/105298533) 8 | 9 | A simple Python package to make nice plots of deprojected interferometric 10 | visibilities, often called **uvplots** (see an example below). 11 | 12 | **uvplot** also [makes it easy](https://uvplot.readthedocs.io/en/latest/basic_usage.html#exporting-visibilities-from-ms-table-to-uvtable-ascii) 13 | to export visibilities from 14 | `MeasurementSets` to `uvtables`, a handy format for fitting the data 15 | (e.g., using [Galario](https://github.com/mtazzari/galario)). 16 | 17 | **uvplot** can be used as a standalone Python package (available on 18 | [PyPI](https://pypi.python.org/pypi/uvplot)) and also inside [NRAO CASA](https://casa.nrao.edu/) 6.x. 19 | It can be installed in a Python environment and in a CASA terminal with: 20 | ```bash 21 | pip install uvplot 22 | ``` 23 | 24 | An example uvplot made with [this simple code](https://uvplot.readthedocs.io/en/latest/basic_usage.html#plotting-visibilities): 25 |

26 | 27 |

28 | 29 | If you are interested, have feature requests, or encounter issues, 30 | consider creating an [Issue](https://github.com/mtazzari/uvplot/issues) 31 | or writing me an [email](mtazzari@ast.cam.ac.uk). I am happy to have your 32 | feedback! 33 | 34 | Check out the [documentation](https://uvplot.readthedocs.io/) and 35 | the [installation instructions](https://uvplot.readthedocs.io/en/latest/install.html). 36 | 37 | **uvplot** is used in a growing number of publications; 38 | [at this page](https://ui.adsabs.harvard.edu/#search/q=citations(bibcode%3A2017zndo...1003113T)%20&sort=date%20desc%2C%20bibcode%20desc&p_=0) you can find an 39 | updated list. 40 | 41 | If you use **uvplot** for your publication, please cite the [Zenodo 42 | reference](https://zenodo.org/badge/latestdoi/105298533): 43 | 44 | @software{uvplot_tazzari, 45 | author = {Marco Tazzari}, 46 | title = {mtazzari/uvplot}, 47 | month = oct, 48 | year = 2017, 49 | publisher = {Zenodo}, 50 | doi = {10.5281/zenodo.1003113}, 51 | url = {https://doi.org/10.5281/zenodo.1003113} 52 | } 53 | 54 | ## License 55 | 56 | **uvplot** is free software licensed under the LGPLv3 License. For more 57 | details see the LICENSE. 58 | 59 | © Copyright 2017-2021 Marco Tazzari and contributors. 60 | -------------------------------------------------------------------------------- /docs/basic_usage.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Basic Usage 3 | =========== 4 | 5 | Plotting visibilities 6 | --------------------- 7 | This is an example plot created with uvplot: 8 | 9 | .. image:: images/uvplot.png 10 | :width: 60 % 11 | :alt: example uv plot 12 | :align: center 13 | 14 | .. code-block:: py 15 | 16 | import numpy as np 17 | from uvplot import UVTable, arcsec 18 | from uvplot import COLUMNS_V0 # use uvplot >= 0.2.6 19 | 20 | wle = 0.88e-3 # Observing wavelength [m] 21 | 22 | dRA = 0.3 * arcsec # Delta Right Ascension offset [rad] 23 | dDec = 0.07 * arcsec # Delta Declination offset [rad] 24 | inc = np.radians(73.) # Inclination [rad] 25 | PA = np.radians(59) # Position Angle [rad] 26 | 27 | uvbin_size = 30e3 # uv-distance bin [wle] 28 | 29 | uv = UVTable(filename='uvtable.txt', wle=wle, columns=COLUMNS_V0) 30 | uv.apply_phase(dRA, dDec) 31 | uv.deproject(inc, PA) 32 | 33 | uv_mod = UVTable(filename='uvtable_mod.txt', wle=wle, columns=pluCOLUMNS_V0) 34 | uv_mod.apply_phase(dRA=dRA, dDec=dDec) 35 | uv_mod.deproject(inc=inc, PA=PA) 36 | 37 | axes = uv.plot(label='Data', uvbin_size=uvbin_size) 38 | uv_mod.plot(label='Model', uvbin_size=uvbin_size, axes=axes, yerr=False, linestyle='-', color='r') 39 | 40 | axes[0].figure.savefig("uvplot.png") 41 | 42 | From version v0.2.6 it is necessary to provide the `columns` parameter 43 | when reading an ASCII uvtable. The `columns` parameter can be specified 44 | either as a parameter to the `UVTable()` command, or as the **2nd** line 45 | in the ASCII file. The available `columns` formats are: 46 | 47 | .. code-block:: bash 48 | 49 | FORMAT COLUMNS COLUMNS_LINE (copy-paste as 2nd line in the ASCII file) 50 | COLUMNS_V0 ['u', 'v', 'Re', 'Im', 'weights'] '# Columns u v Re Im weights' 51 | COLUMNS_V1 ['u', 'v', 'Re', 'Im', 'weights', 'freqs', 'spws'] '# Columns u v Re Im weights freqs spws' 52 | COLUMNS_V2 ['u', 'v', 'V', 'weights', 'freqs', 'spws'] '# Columns u v V weights freqs spws' 53 | 54 | To import an ASCII uvtable with 5 columns with uvplot < 0.2.6: 55 | 56 | .. code-block:: py 57 | 58 | from uvplot import UVTable 59 | uvt = UVTable(filename='uvtable.txt', format='ascii') 60 | 61 | 62 | and with uvplot >= 0.2.6: 63 | 64 | .. code-block:: py 65 | 66 | from uvplot import UVTable 67 | from uvplot import COLUMNS_V0 # ['u', 'v', 'Re', 'Im', 'weights'] 68 | uvt = UVTable(filename='uvtable.txt', format='ascii', columns=COLUMNS_V0) 69 | 70 | 71 | Exporting visibilities from MS table to uvtable (ASCII) 72 | ------------------------------------------------------- 73 | Once installed |uvplot| inside CASA (see instructions above), 74 | it is possible to export the visibilities in `mstable.ms` to an ASCII table by executing these lines **from a CASA shell**: 75 | 76 | .. code-block:: py 77 | 78 | CASA <1>: from uvplot import export_uvtable 79 | CASA <2>: export_uvtable("uvtable.txt", tb, vis='mstable.ms') 80 | 81 | **Note**: it is strongly recommended to perform a CASA `split` command with `keepflags=False` before exporting the uvtable. This ensures that only valid visibilities are exported. 82 | 83 | The resulting `uvtable.txt` will contain `u, v` coordinates (in meters), `Re(V), Im(V)` visibility measurements (in Jansky), 84 | and `weights`. The table will also report the average wavelength (averaged among all selected spectral windows): 85 | 86 | .. code-block:: bash 87 | 88 | # Extracted from mstable.ms. 89 | # wavelength[m] = 0.00132940778422 90 | # Columns: u[m] v[m] Re(V)[Jy] Im(V)[Jy] weight 91 | -2.063619e+02 2.927104e+02 -1.453431e-02 -1.590934e-02 2.326950e+04 92 | 3.607948e+02 6.620900e+01 -1.680727e-02 1.124862e-02 3.624442e+04 93 | 5.752178e+02 -6.299933e+02 5.710317e-03 6.592049e-03 4.719500e+04 94 | -9.198434e+02 -1.374651e+03 1.313417e-03 4.299262e-03 4.259890e+04 95 | 9.623210e+01 -4.631573e+02 7.731462e-03 -8.803369e-03 4.801395e+04 96 | 9.348914e+01 -5.191096e+02 3.759772e-03 4.754967e-04 4.748304e+04 97 | 1.108410e+03 -1.396906e+03 3.222965e-03 -5.164917e-03 4.690977e+04 98 | [...] 99 | 100 | By default `export_uvtable` exports all channels in all spectral windows. However, it is also possible to specify which 101 | spectral windows and channels to export. More details are given in the documentation of the `export_uvtable() `_ function. 102 | 103 | **Note**: currently, `export_uvtable` only works for MS tables where all the spectral windows have **the same** number of channels (which, individually, can be larger than 1). 104 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # galario documentation build configuration file, created by 4 | # sphinx-quickstart on Wed May 24 13:32:58 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | from datetime import datetime 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('../../python')) 23 | import uvplot 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | "sphinx_rtd_theme", 34 | "sphinx_copybutton", 35 | 'sphinx.ext.mathjax', 36 | 'sphinx.ext.autodoc', 37 | 'sphinx.ext.napoleon', 38 | ] 39 | 40 | mathjax_path = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_HTML" 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | # templates_path = ['_templates'] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # source_suffix = ['.rst', '.md'] 48 | source_suffix = '.rst' 49 | 50 | # The encoding of source files. 51 | #source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = u'uvplot' 58 | authors = u'Marco Tazzari' 59 | copyright = '2017-%d, %s' % (datetime.now().year, authors) 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = str(uvplot.__version__) 67 | # The full version, including alpha/beta/rc tags. 68 | release = str(uvplot.__version__) 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # There are two options for replacing |today|: either, you set today to some 78 | # non-false value, then it is used: 79 | #today = '' 80 | # Else, today_fmt is used as the format for a strftime call. 81 | #today_fmt = '%B %d, %Y' 82 | 83 | # List of patterns, relative to source directory, that match files and 84 | # directories to ignore when looking for source files. 85 | # exclude_patterns = ['_build'] 86 | 87 | # The reST default role (used for this markup: `text`) to use for all 88 | # documents. 89 | #default_role = None 90 | 91 | # If true, '()' will be appended to :func: etc. cross-reference text. 92 | add_function_parentheses = True 93 | 94 | # If true, the current module name will be prepended to all description 95 | # unit titles (such as .. function::). 96 | #add_module_names = True 97 | 98 | # If true, sectionauthor and moduleauthor directives will be shown in the 99 | # output. They are ignored by default. 100 | #show_authors = False 101 | 102 | # The name of the Pygments (syntax highlighting) style to use. 103 | # pygments_style = 'friendly' 104 | 105 | # A list of ignored prefixes for module index sorting. 106 | #modindex_common_prefix = [] 107 | 108 | # If true, keep warnings as "system message" paragraphs in the built documents. 109 | #keep_warnings = False 110 | 111 | # If true, `todo` and `todoList` produce output, else they produce nothing. 112 | todo_include_todos = False 113 | 114 | # A string of reStructuredText that will be included at the beginning of every 115 | # source file that is read. 116 | rst_prolog = """ 117 | .. |uvplot| replace:: **uvplot** 118 | .. default-role:: code 119 | """ 120 | 121 | # -- Options for HTML output ---------------------------------------------- 122 | 123 | # The theme to use for HTML and HTML Help pages. See the documentation for 124 | # a list of builtin themes. 125 | html_theme = "sphinx_rtd_theme" 126 | 127 | # Theme options are theme-specific and customize the look and feel of a theme 128 | # further. For a list of options available for each theme, see the 129 | # documentation. 130 | # html_theme_options = { 131 | # 'githuburl': 'https://github.com/mtazzari/uvplot/', 132 | # 'bodyfont': '"Lucida Grande",Arial,sans-serif', 133 | # 'headfont': '"Lucida Grande",Arial,sans-serif', 134 | # 'codefont': 'monospace,sans-serif', 135 | # 'linkcolor': '#0072AA', 136 | # 'visitedlinkcolor': '#6363bb', 137 | # 'extrastyling': False, 138 | # } 139 | html_theme_options = { 140 | # 'analytics_id': 'UA-XXXXXXX-1', # Provided by Google in your dashboard 141 | # 'analytics_anonymize_ip': False, 142 | 'logo_only': False, 143 | 'display_version': True, 144 | 'prev_next_buttons_location': 'bottom', 145 | 'style_external_links': False, 146 | 'vcs_pageview_mode': '', 147 | # 'style_nav_header_background': 'white', 148 | # # Toc options 149 | 'collapse_navigation': True, 150 | 'sticky_navigation': True, 151 | 'navigation_depth': 4, 152 | 'includehidden': True, 153 | 'titles_only': False 154 | } 155 | # Add any paths that contain custom themes here, relative to this directory. 156 | # html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] 157 | 158 | # The name for this set of Sphinx documents. If None, it defaults to 159 | # " v documentation". 160 | #html_title = None 161 | 162 | # A shorter title for the navigation bar. Default is the same as html_title. 163 | #html_short_title = None 164 | 165 | # The name of an image file (relative to this directory) to place at the top 166 | # of the sidebar. 167 | #html_logo = None 168 | 169 | # The name of an image file (relative to this directory) to use as a favicon of 170 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 171 | # pixels large. 172 | #html_favicon = None 173 | 174 | # Add any paths that contain custom static files (such as style sheets) here, 175 | # relative to this directory. They are copied after the builtin static files, 176 | # so a file named "default.css" will overwrite the builtin "default.css". 177 | # html_static_path = ['_static'] 178 | 179 | # Add any extra paths that contain custom files (such as robots.txt or 180 | # .htaccess) here, relative to this directory. These files are copied 181 | # directly to the root of the documentation. 182 | #html_extra_path = [] 183 | 184 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 185 | # using the given strftime format. 186 | #html_last_updated_fmt = '%b %d, %Y' 187 | 188 | # If true, SmartyPants will be used to convert quotes and dashes to 189 | # typographically correct entities. 190 | #html_use_smartypants = True 191 | 192 | # Custom sidebar templates, maps document names to template names. 193 | #html_sidebars = {} 194 | 195 | # Additional templates that should be rendered to pages, maps page names to 196 | # template names. 197 | #html_additional_pages = {} 198 | 199 | # If false, no module index is generated. 200 | #html_domain_indices = True 201 | 202 | # If false, no index is generated. 203 | #html_use_index = True 204 | 205 | # If true, the index is split into individual pages for each letter. 206 | #html_split_index = False 207 | 208 | # If true, links to the reST sources are added to the pages. 209 | #html_show_sourcelink = True 210 | 211 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 212 | #html_show_sphinx = True 213 | 214 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 215 | #html_show_copyright = True 216 | 217 | # If true, an OpenSearch description file will be output, and all pages will 218 | # contain a tag referring to it. The value of this option must be the 219 | # base URL from which the finished HTML is served. 220 | #html_use_opensearch = '' 221 | 222 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 223 | #html_file_suffix = None 224 | 225 | # Language to be used for generating the HTML full-text search index. 226 | # Sphinx supports the following languages: 227 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 228 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 229 | #html_search_language = 'en' 230 | 231 | # A dictionary with options for the search language support, empty by default. 232 | # Now only 'ja' uses this config value 233 | #html_search_options = {'type': 'default'} 234 | 235 | # The name of a javascript file (relative to the configuration directory) that 236 | # implements a search results scorer. If empty, the default will be used. 237 | #html_search_scorer = 'scorer.js' 238 | 239 | # Output file base name for HTML help builder. 240 | htmlhelp_basename = 'uvplotdoc' 241 | 242 | # -- Options for LaTeX output --------------------------------------------- 243 | 244 | latex_elements = { 245 | # The paper size ('letterpaper' or 'a4paper'). 246 | #'papersize': 'letterpaper', 247 | 248 | # The font size ('10pt', '11pt' or '12pt'). 249 | #'pointsize': '10pt', 250 | 251 | # Additional stuff for the LaTeX preamble. 252 | #'preamble': '', 253 | 254 | # Latex figure (float) alignment 255 | #'figure_align': 'htbp', 256 | } 257 | 258 | # Grouping the document tree into LaTeX files. List of tuples 259 | # (source start file, target name, title, 260 | # authors, documentclass [howto, manual, or own class]). 261 | # latex_documents = [ 262 | # (master_doc, 'galario.tex', u'galario Documentation', 263 | # u'Marco Tazzari, Frederik Beaujean', 'manual'), 264 | # ] 265 | 266 | # The name of an image file (relative to this directory) to place at the top of 267 | # the title page. 268 | #latex_logo = None 269 | 270 | # For "manual" documents, if this is true, then toplevel headings are parts, 271 | # not chapters. 272 | #latex_use_parts = False 273 | 274 | # If true, show page references after internal links. 275 | #latex_show_pagerefs = False 276 | 277 | # If true, show URL addresses after external links. 278 | #latex_show_urls = False 279 | 280 | # Documents to append as an appendix to all manuals. 281 | #latex_appendices = [] 282 | 283 | # If false, no module index is generated. 284 | #latex_domain_indices = True 285 | 286 | 287 | # -- Options for manual page output --------------------------------------- 288 | 289 | # One entry per manual page. List of tuples 290 | # (source start file, name, description, authors, manual section). 291 | man_pages = [ 292 | (master_doc, 'uvplot', u'uvplot Documentation', 293 | [authors], 1) 294 | ] 295 | 296 | # If true, show URL addresses after external links. 297 | #man_show_urls = False 298 | 299 | 300 | # -- Options for Texinfo output ------------------------------------------- 301 | 302 | # Grouping the document tree into Texinfo files. List of tuples 303 | # (source start file, target name, title, authors, 304 | # dir menu entry, description, category) 305 | texinfo_documents = [ 306 | (master_doc, 'uvplot', u'uvplot Documentation', 307 | authors, 'uvplot', 'One line description of project.', 308 | 'Miscellaneous'), 309 | ] 310 | 311 | # Documents to append as an appendix to all manuals. 312 | #texinfo_appendices = [] 313 | 314 | # If false, no module index is generated. 315 | #texinfo_domain_indices = True 316 | 317 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 318 | #texinfo_show_urls = 'footnote' 319 | 320 | # If true, do not generate a @detailmenu in the "Top" node's menu. 321 | #texinfo_no_detailmenu = False 322 | -------------------------------------------------------------------------------- /docs/images/uvplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtazzari/uvplot/9a03579d70bd99f862ab2a38c3e07521546f751e/docs/images/uvplot.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. uvplot documentation master file, created by 2 | sphinx-quickstart on Wed May 24 13:32:58 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. _email: mtazzari@ast.cam.ac.uk 7 | 8 | =================== 9 | |uvplot| |version| 10 | =================== 11 | A simple Python package to make nice plots of deprojected interferometric 12 | visibilities, often called **uvplots** (see an example below). 13 | 14 | **uvplot** also `makes it easy `_ to export visibilities from 15 | :code:`MeasurementSet Tables` to :code:`uvtables`, a handy format for fitting the data 16 | (e.g., using `Galario `_). 17 | 18 | **uvplot** can be used as a standalone Python package (available on 19 | `PyPI `_) and also inside `NRAO CASA `_ 6.x. 20 | 21 | .. note:: |uvplot| can be installed easily from PyPI with `pip `_: 22 | 23 | .. code-block:: bash 24 | 25 | pip install uvplot 26 | 27 | More details on how to use |uvplot| in CASA can be found in the :doc:`Installation ` page. 28 | 29 | An example uvplot made with `this simple code `_: 30 | 31 | .. image:: images/uvplot.png 32 | :width: 700 px 33 | :alt: sketch origin upper 34 | 35 | If you are interested, have feature requests, or encounter issues, consider creating an `Issue `_ or writing me an `email`_. I am happy to have your feedback! 36 | 37 | |uvplot| is actively developed on `GitHub `_ 38 | and has already been employed in `these publications `_. 39 | 40 | Basic functionality of |uvplot|: see the :doc:`Basic Usage ` page. 41 | 42 | 43 | License and Attribution 44 | ----------------------- 45 | If you use |uvplot| for your publication, please cite the `Zenodo reference `_ :: 46 | 47 | @software{uvplot_tazzari, 48 | author = {Marco Tazzari}, 49 | title = {mtazzari/uvplot}, 50 | month = oct, 51 | year = 2017, 52 | publisher = {Zenodo}, 53 | doi = {10.5281/zenodo.1003113}, 54 | url = {https://doi.org/10.5281/zenodo.1003113} 55 | } 56 | 57 | 58 | |uvplot| is free software licensed under the LGPLv3 License. For more details see the LICENSE. 59 | 60 | © Copyright 2017-2021 Marco Tazzari and contributors. 61 | 62 | Documentation 63 | ------------- 64 | Check out the `documentation `_. 65 | 66 | Changelog 67 | --------- 68 | See the list of changes in all releases `here `_. 69 | 70 | 71 | Contributors 72 | ------------ 73 | .. include:: ../AUTHORS.rst 74 | 75 | .. Changelog 76 | --------- 77 | .. include:: ../CHANGELOG.rst 78 | 79 | Contents 80 | -------- 81 | .. toctree:: 82 | :numbered: 83 | :maxdepth: 2 84 | 85 | Home 86 | Installation 87 | Basic Usage 88 | UVTable class 89 | Export visibilities 90 | License 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | How to install |uvplot| 3 | ======================= 4 | 5 | Operating system 6 | ------------------- 7 | |uvplot| runs on Linux and Mac OS X. Windows is not supported. 8 | 9 | Quickest installation: using pip 10 | -------------------------------- 11 | 12 | By far the easiest way to install |uvplot| is via :code:`pip`: 13 | 14 | .. code-block :: bash 15 | 16 | pip install uvplot 17 | 18 | |uvplot| works in Python 3.6, 3.7, 3.8, and 3.9. 19 | 20 | Using uvplot inside CASA 21 | ------------------------ 22 | 23 | |uvplot| can be easily installed inside CASA 6.x with :code:`pip`: 24 | 25 | .. code-block :: bash 26 | 27 | CASA <1>: pip install uvplot 28 | 29 | .. note:: 30 | 31 | After installation it is recommended to restart the CASA terminal. 32 | 33 | |uvplot| installation in CASA 5.x and earlier is not supported, owing to the impossibility to install it with :code:`pip` or by other means. 34 | 35 | 36 | Updating uvplot 37 | --------------- 38 | In a normal Python environment as well as within a CASA terminal it is easy to update |uvplot| to the latest version: 39 | 40 | .. code-block :: bash 41 | 42 | pip install uvplot --upgrade -------------------------------------------------------------------------------- /docs/io.rst: -------------------------------------------------------------------------------- 1 | .. _exportuvtable: 2 | 3 | Exporting visibilities 4 | ====================== 5 | .. autofunction:: uvplot.io.export_uvtable 6 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | .. code-block:: text 5 | 6 | GNU LESSER GENERAL PUBLIC LICENSE 7 | Version 3, 29 June 2007 8 | 9 | Copyright (C) 2007 Free Software Foundation, Inc. 10 | Everyone is permitted to copy and distribute verbatim copies 11 | of this license document, but changing it is not allowed. 12 | 13 | 14 | This version of the GNU Lesser General Public License incorporates 15 | the terms and conditions of version 3 of the GNU General Public 16 | License, supplemented by the additional permissions listed below. 17 | 18 | 0. Additional Definitions. 19 | 20 | As used herein, "this License" refers to version 3 of the GNU Lesser 21 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 22 | General Public License. 23 | 24 | "The Library" refers to a covered work governed by this License, 25 | other than an Application or a Combined Work as defined below. 26 | 27 | An "Application" is any work that makes use of an interface provided 28 | by the Library, but which is not otherwise based on the Library. 29 | Defining a subclass of a class defined by the Library is deemed a mode 30 | of using an interface provided by the Library. 31 | 32 | A "Combined Work" is a work produced by combining or linking an 33 | Application with the Library. The particular version of the Library 34 | with which the Combined Work was made is also called the "Linked 35 | Version". 36 | 37 | The "Minimal Corresponding Source" for a Combined Work means the 38 | Corresponding Source for the Combined Work, excluding any source code 39 | for portions of the Combined Work that, considered in isolation, are 40 | based on the Application, and not on the Linked Version. 41 | 42 | The "Corresponding Application Code" for a Combined Work means the 43 | object code and/or source code for the Application, including any data 44 | and utility programs needed for reproducing the Combined Work from the 45 | Application, but excluding the System Libraries of the Combined Work. 46 | 47 | 1. Exception to Section 3 of the GNU GPL. 48 | 49 | You may convey a covered work under sections 3 and 4 of this License 50 | without being bound by section 3 of the GNU GPL. 51 | 52 | 2. Conveying Modified Versions. 53 | 54 | If you modify a copy of the Library, and, in your modifications, a 55 | facility refers to a function or data to be supplied by an Application 56 | that uses the facility (other than as an argument passed when the 57 | facility is invoked), then you may convey a copy of the modified 58 | version: 59 | 60 | a) under this License, provided that you make a good faith effort to 61 | ensure that, in the event an Application does not supply the 62 | function or data, the facility still operates, and performs 63 | whatever part of its purpose remains meaningful, or 64 | 65 | b) under the GNU GPL, with none of the additional permissions of 66 | this License applicable to that copy. 67 | 68 | 3. Object Code Incorporating Material from Library Header Files. 69 | 70 | The object code form of an Application may incorporate material from 71 | a header file that is part of the Library. You may convey such object 72 | code under terms of your choice, provided that, if the incorporated 73 | material is not limited to numerical parameters, data structure 74 | layouts and accessors, or small macros, inline functions and templates 75 | (ten or fewer lines in length), you do both of the following: 76 | 77 | a) Give prominent notice with each copy of the object code that the 78 | Library is used in it and that the Library and its use are 79 | covered by this License. 80 | 81 | b) Accompany the object code with a copy of the GNU GPL and this license 82 | document. 83 | 84 | 4. Combined Works. 85 | 86 | You may convey a Combined Work under terms of your choice that, 87 | taken together, effectively do not restrict modification of the 88 | portions of the Library contained in the Combined Work and reverse 89 | engineering for debugging such modifications, if you also do each of 90 | the following: 91 | 92 | a) Give prominent notice with each copy of the Combined Work that 93 | the Library is used in it and that the Library and its use are 94 | covered by this License. 95 | 96 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 97 | document. 98 | 99 | c) For a Combined Work that displays copyright notices during 100 | execution, include the copyright notice for the Library among 101 | these notices, as well as a reference directing the user to the 102 | copies of the GNU GPL and this license document. 103 | 104 | d) Do one of the following: 105 | 106 | 0) Convey the Minimal Corresponding Source under the terms of this 107 | License, and the Corresponding Application Code in a form 108 | suitable for, and under terms that permit, the user to 109 | recombine or relink the Application with a modified version of 110 | the Linked Version to produce a modified Combined Work, in the 111 | manner specified by section 6 of the GNU GPL for conveying 112 | Corresponding Source. 113 | 114 | 1) Use a suitable shared library mechanism for linking with the 115 | Library. A suitable mechanism is one that (a) uses at run time 116 | a copy of the Library already present on the user's computer 117 | system, and (b) will operate properly with a modified version 118 | of the Library that is interface-compatible with the Linked 119 | Version. 120 | 121 | e) Provide Installation Information, but only if you would otherwise 122 | be required to provide such information under section 6 of the 123 | GNU GPL, and only to the extent that such information is 124 | necessary to install and execute a modified version of the 125 | Combined Work produced by recombining or relinking the 126 | Application with a modified version of the Linked Version. (If 127 | you use option 4d0, the Installation Information must accompany 128 | the Minimal Corresponding Source and Corresponding Application 129 | Code. If you use option 4d1, you must provide the Installation 130 | Information in the manner specified by section 6 of the GNU GPL 131 | for conveying Corresponding Source.) 132 | 133 | 5. Combined Libraries. 134 | 135 | You may place library facilities that are a work based on the 136 | Library side by side in a single library together with other library 137 | facilities that are not Applications and are not covered by this 138 | License, and convey such a combined library under terms of your 139 | choice, if you do both of the following: 140 | 141 | a) Accompany the combined library with a copy of the same work based 142 | on the Library, uncombined with any other library facilities, 143 | conveyed under the terms of this License. 144 | 145 | b) Give prominent notice with the combined library that part of it 146 | is a work based on the Library, and explaining where to find the 147 | accompanying uncombined form of the same work. 148 | 149 | 6. Revised Versions of the GNU Lesser General Public License. 150 | 151 | The Free Software Foundation may publish revised and/or new versions 152 | of the GNU Lesser General Public License from time to time. Such new 153 | versions will be similar in spirit to the present version, but may 154 | differ in detail to address new problems or concerns. 155 | 156 | Each version is given a distinguishing version number. If the 157 | Library as you received it specifies that a certain numbered version 158 | of the GNU Lesser General Public License "or any later version" 159 | applies to it, you have the option of following the terms and 160 | conditions either of that published version or of any later version 161 | published by the Free Software Foundation. If the Library as you 162 | received it does not specify a version number of the GNU Lesser 163 | General Public License, you may choose any version of the GNU Lesser 164 | General Public License ever published by the Free Software Foundation. 165 | 166 | If the Library as you received it specifies that a proxy can decide 167 | whether future versions of the GNU Lesser General Public License shall 168 | apply, that proxy's public statement of acceptance of any version is 169 | permanent authorization for you to choose that version for the 170 | Library. 171 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-rtd-theme>=0.5.1 2 | sphinx-copybutton>=0.3.1 3 | -------------------------------------------------------------------------------- /docs/uvtable.rst: -------------------------------------------------------------------------------- 1 | .. _uvtable: 2 | 3 | The UVTable class 4 | ================= 5 | .. autoclass:: uvplot.UVTable 6 | :members: 7 | :undoc-members: 8 | :show-inheritance: 9 | :member-order: bysource -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | 3 | # Minimum requirements for the build system to execute. 4 | # PEP 508 specifications. 5 | requires = [ 6 | "setuptools>=42", 7 | "wheel" 8 | ] 9 | 10 | build-backend = "setuptools.build_meta" 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # to set version dynamically: https://github.com/pypa/setuptools/issues/1724#issuecomment-627241822 2 | 3 | [metadata] 4 | name = uvplot 5 | version = attr: uvplot.__version__ 6 | author = Marco Tazzari 7 | author_email = mtazzari@gmail.com 8 | description = Utilities for handling and plotting interferometric visibilities. 9 | long_description = file: README.md 10 | long_description_content_type = text/markdown 11 | license = LGPLv3 12 | license_file = LICENSE.txt 13 | include_package_data = True 14 | url = https://github.com/mtazzari/uvplot 15 | project_urls = 16 | Bug Tracker = https://github.com/mtazzari/uvplot/issues 17 | classifiers = 18 | Development Status :: 5 - Production/Stable 19 | License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) 20 | Intended Audience :: Developers 21 | Intended Audience :: Science/Research 22 | Operating System :: MacOS :: MacOS X 23 | Operating System :: POSIX :: Linux 24 | Programming Language :: Python :: 3.6 25 | Programming Language :: Python :: 3.7 26 | Programming Language :: Python :: 3.8 27 | Programming Language :: Python :: 3.9 28 | keywords = 29 | science 30 | astronomy 31 | interferometry 32 | 33 | [options] 34 | packages = uvplot 35 | 36 | # python_requires docs: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires 37 | python_requires = >=3.6 38 | 39 | # PEP 440 - pinning package versions: https://www.python.org/dev/peps/pep-0440/#compatible-release 40 | install_requires = 41 | numpy>=1.9 42 | matplotlib 43 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | from setuptools import setup 4 | 5 | # setup configuration is in `setup.cfg` 6 | setup() 7 | -------------------------------------------------------------------------------- /uvplot/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.2.11' 2 | 3 | import matplotlib 4 | 5 | # To avoid Runtime Error 6 | # RuntimeError: Python is not installed as a framework. The Mac OS X backend 7 | # will not be able to function correctly if Python is not installed as a framework. 8 | # See the Python documentation for more information on installing Python as a 9 | # framework on Mac OS X. Please either reinstall Python as a framework, or try 10 | # one of the other backends. 11 | # If you are using (Ana)Conda please install python.app and replace the use of 12 | # 'python' with 'pythonw'. 13 | # See 'Working with Matplotlib on OSX' in the Matplotlib FAQ for more information. 14 | # https://matplotlib.org/faq/osx_framework.html 15 | 16 | if matplotlib.get_backend().lower() == 'macosx': 17 | matplotlib.use('TkAgg') 18 | 19 | from .uvtable import UVTable, COLUMNS_V0, COLUMNS_V1, COLUMNS_V2 20 | from .io import export_uvtable 21 | from .constants import arcsec 22 | -------------------------------------------------------------------------------- /uvplot/_set_unique_version.py: -------------------------------------------------------------------------------- 1 | # This script makes the version number unique by adding a timestamp to the semantic version. 2 | # This is useful for testing the push to TestPyPI, which does not allow multiple uploads 3 | # with same version name. 4 | 5 | # initial plan was to add the branch name and commit sha 6 | # import subprocess 7 | # git_commit_sha = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).strip(b"\n") 8 | # git_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).strip(b"\n") 9 | 10 | # However, PEP440 forbids usage of hashes in version numbers 11 | # https://stackoverflow.com/questions/57912086/twine-upload-dist-get-an-error-is-an-invalid-value-for-version 12 | 13 | import time 14 | 15 | # read current version number 16 | with open("uvplot/__init__.py", "r") as f: 17 | lines = f.readlines() 18 | 19 | print(f"Detected version: {lines[0].encode()}") 20 | 21 | # replace version number, e.g. '0.2.10' into '0.2.10.' 22 | lines[0] = lines[0].replace("'\n", f".{int(time.time())}'\n") 23 | print(f"New version: {lines[0].encode()}") 24 | 25 | # save new version number 26 | with open("uvplot/__init__.py", "w") as g: 27 | g.writelines(lines) 28 | -------------------------------------------------------------------------------- /uvplot/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import (division, print_function, absolute_import, 5 | unicode_literals) 6 | 7 | arcsec = 4.84813681109536e-06 # radians 8 | clight = 2.99792458e+8 # [m/s] Speed of light 9 | 10 | -------------------------------------------------------------------------------- /uvplot/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import (division, print_function, absolute_import, 5 | unicode_literals) 6 | 7 | import numpy as np 8 | from uvplot import UVTable, arcsec 9 | 10 | wle = 0.88e-3 # Observing wavelength [m] 11 | 12 | dRA = 0.3 * arcsec # Delta Right Ascension offset [rad] 13 | dDec = 0.07 * arcsec # Delta Declination offset [rad] 14 | inc = np.radians(73.) # Inclination [rad] 15 | PA = np.radians(59) # Position Angle [rad] 16 | 17 | uvbin_size = 30e3 # uv-distance bin [wle] 18 | 19 | uv = UVTable(filename='uvtable.txt', wle=wle) 20 | uv.apply_phase(dRA, dDec) 21 | uv.deproject(inc, PA) 22 | 23 | uv_mod = UVTable(filename='uvtable_mod.txt', wle=wle) 24 | uv_mod.apply_phase(dRA=dRA, dDec=dDec) 25 | uv_mod.deproject(inc=inc, PA=PA) 26 | 27 | axes = uv.plot(label='Data', uvbin_size=uvbin_size) 28 | uv_mod.plot(label='Model', uvbin_size=uvbin_size, axes=axes, yerr=False, linestyle='-', color='r') 29 | 30 | axes[0].figure.savefig("uvplot.png") 31 | -------------------------------------------------------------------------------- /uvplot/io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import (division, print_function, absolute_import, 5 | unicode_literals) 6 | 7 | import numpy as np 8 | 9 | from .constants import clight 10 | 11 | __all__ = ["export_uvtable"] 12 | 13 | 14 | def export_uvtable(uvtable_filename, tb, vis="", split_args=None, split=None, channel='all', 15 | dualpol=True, fmt='%10.6e', datacolumn="CORRECTED_DATA", keep_tmp_ms=False, 16 | verbose=True): 17 | """ 18 | Export visibilities from an MS Table to a uvtable. Requires execution inside CASA. 19 | 20 | Currently the only uvtable format supported is ASCII. 21 | 22 | Typicall call signature:: 23 | 24 | export_uvtable('uvtable_new.txt', tb, channel='all', split=split, 25 | split_args={'vis': 'sample.ms', 'datacolumn': 'DATA', 'spw':'0,1'}, 26 | verbose=True) 27 | 28 | Parameters 29 | ---------- 30 | uvtable_filename : str 31 | Filename of the output uvtable, e.g. "uvtable.txt" 32 | tb : CASA `tb` object 33 | As tb parameter you **must** pass the tb object that is defined in the CASA shell. 34 | Since tb cannot be accessed outside CASA, export_uvtable('uvtable.txt', tb, ...) 35 | can be executed only inside CASA. 36 | vis : str, optional 37 | MS Table filename, e.g. mstable.ms 38 | split_args : dict, optional 39 | Default is None. If provided, perform a split before exporting the uvtable. 40 | The split_args dictionary is passed to the CASA::split task. 41 | The CASA::split task must be provided in input as split. 42 | split : optional 43 | CASA split task 44 | channel : str, optional 45 | If 'all', all channels are exported; if 'first' only the first channel of each spectral window (spw) is exported. 46 | Number of channels in each spw must be equal, otherwise you will get a CASA error, e.g.: 47 | `RuntimeError: ArrayColumn::getColumn cannot be done for column DATA; the array shapes vary: Table array conformance error` 48 | Default is 'all'. 49 | dualpol : bool, optional 50 | If the MS Table contains dual polarisation data. Default is True. 51 | fmt : str, optional 52 | Format of the output ASCII uvtable. 53 | datacolumn: str, optional 54 | Data column to be extracted, e.g. "DATA", "CORRECTED_DATA", "MODEL_DATA". 55 | keep_tmp_ms : bool, optional 56 | If True, keeps the temporary outputvis created by the split command. 57 | verbose : bool, optional 58 | If True, print informative messages. Default: True 59 | 60 | 61 | Note 62 | ---- 63 | By default, all the spws and all the channels of the `vis` MS table are exported. 64 | To export only the first channel in each spw, set channel='first'. 65 | 66 | To export only some spws provide split_args, e.g.:: 67 | 68 | split_args = {'vis': 'input.ms', 'outputvis': 'input_tmp.ms', spw: '1,2'} 69 | 70 | Flagged data can be removed from the .ms by providing `split` and the `split_args` dictionary, e.g.:: 71 | 72 | split_args = {'vis': 'input.ms', 'outputvis': 'input_tmp.ms', 'keepflags': False} 73 | 74 | If you try exporting visibilites from an MS table with spws with different number of channels, 75 | you will get a CASA error (see requirement for the `channel` parameter above). 76 | To avoid that, you can either use the CASA `split` task to create a new MS table with only spws 77 | with same number of channels. Alternatively (or additionally) you can channel average all the spws 78 | to the same number of channels. 79 | 80 | 81 | Example 82 | ------- 83 | From within CASA, to extract all the visibilities from an MS table:: 84 | 85 | export_uvtable('uvtable.txt', tb, vis='sample.ms', channel='all') 86 | 87 | where `tb` is the CASA tb object (to inspect it type `tb` in the CASA shell). 88 | For more information on `tb` see ``_ 89 | 90 | From within CASA, to extract the visibilities in spectral windows 0 and 2 use 91 | the `split_args` parameter and the CASA `split` task:: 92 | 93 | export_uvtable('uvtable.txt', tb, channel='all', split=split, 94 | split_args={'vis': 'sample.ms' , 'datacolumn': 'DATA', 'spw':'0,2'}) 95 | 96 | To perform these operations without running CASA interactively:: 97 | 98 | casa --nologger --nogui -c "from uvplot import export_uvtable; export_uvtable(...)" 99 | 100 | ensuring that the strings are inside the '..' characters and not the "..." one. 101 | 102 | """ 103 | if vis != "": 104 | MStb_name = vis 105 | 106 | if split_args: 107 | if split is None: 108 | raise RuntimeError("Missing split parameter: provide the CASA split object in input. " 109 | "See typical call signature.") 110 | if vis != "" and vis != split_args['vis']: 111 | # raise RuntimeError("extract_uvtable: either provide `vis` or `split_args` as input parameters, not both.") 112 | raise RuntimeError( 113 | "extract_uvtable: vis={} input parameter doesn't match with split_args['vis']={}".format( 114 | vis, split_args['vis'])) 115 | 116 | if not 'outputvis' in split_args.keys(): 117 | split_args.update(outputvis='mstable_tmp.ms') 118 | 119 | MStb_name = split_args['outputvis'] 120 | 121 | if verbose: 122 | print("Applying split. Creating temporary MS table {} from original MS table {}".format 123 | (MStb_name, split_args['vis'])) 124 | 125 | split(**split_args) 126 | 127 | # after splitting, data is put into the "DATA" column of the new ms 128 | if datacolumn != 'DATA' and verbose: 129 | print('datacolumn has been changed from "{}" to "DATA" ' 130 | 'in order to operate on the new ms'.format(datacolumn)) 131 | 132 | datacolumn = "DATA" 133 | 134 | else: 135 | if vis == "": 136 | raise RuntimeError \ 137 | ("Missing vis parameter: provide a valid MS table filename.") 138 | 139 | if verbose: 140 | print("Reading {}".format(MStb_name)) 141 | 142 | tb.open(MStb_name) 143 | 144 | # get coordinates 145 | uvw = tb.getcol("UVW") 146 | u, v, w = [uvw[i, :] for i in range(3)] 147 | 148 | # get weights 149 | weights_orig = tb.getcol("WEIGHT") 150 | 151 | # get visibilities 152 | tb_columns = tb.colnames() 153 | 154 | if datacolumn.upper() in tb_columns: 155 | data = tb.getcol(datacolumn) 156 | else: 157 | raise KeyError("datacolumn {} is not available.".format(datacolumn)) 158 | 159 | spw = tb.getcol("DATA_DESC_ID") 160 | nspw = len(np.unique(spw)) 161 | if nspw > 1: 162 | if split_args is None or \ 163 | (split_args is not None and 'spw' not in split_args.keys()): 164 | print( 165 | "Warning: the MS table {} has {} spectral windows. By default all of them are exported." 166 | " To choose which spws to export, provide split_args with the spw parameter.".format( 167 | MStb_name, nspw)) 168 | 169 | # decide whether we export first channel, or all 170 | if channel == 'first': 171 | nchan = 1 172 | ich = 0 173 | if verbose: 174 | print("Exporting the first channel in each spw.") 175 | elif channel == 'all': 176 | nchan = data.shape[1] 177 | ich = slice(0, nchan) 178 | u = np.tile(u, nchan) 179 | v = np.tile(v, nchan) 180 | if verbose: 181 | print("Exporting {} channels per spw.".format(nchan)) 182 | else: 183 | raise ValueError("Channel must be 'first' or 'all', not {}".format(channel)) 184 | 185 | if dualpol: 186 | # dual polarisation: extract the polarised visibilities and weights 187 | V_XX = data[0, ich, :].reshape(-1) 188 | V_YY = data[1, ich, :].reshape(-1) 189 | weights_XX = weights_orig[0, :] 190 | weights_YY = weights_orig[1, :] 191 | if nchan > 1: 192 | weights_XX = np.tile(weights_XX, nchan) 193 | weights_YY = np.tile(weights_YY, nchan) 194 | 195 | # compute weighted average of the visibilities and weights 196 | V = (V_XX * weights_XX + V_YY * weights_YY) / (weights_XX + weights_YY) 197 | weights = weights_XX + weights_YY 198 | 199 | else: 200 | # single polarisation 201 | V = data[0, ich, :].reshape(-1) 202 | weights = weights_orig 203 | if nchan > 1: 204 | weights = np.tile(weights, nchan) 205 | 206 | spw_path = tb.getkeyword('SPECTRAL_WINDOW').split()[-1] 207 | tb.close() 208 | 209 | # get the mean observing frequency 210 | tb.open(spw_path) 211 | freqs = tb.getcol('CHAN_FREQ') # [GHz] 212 | tb.close() 213 | 214 | wle = clight / freqs.mean() # [m] 215 | 216 | # export to file as ascii 217 | if verbose: 218 | print("Exporting visibilities to {}...".format(uvtable_filename), end='') 219 | 220 | np.savetxt(uvtable_filename, 221 | np.column_stack([u, v, V.real, V.imag, weights]), 222 | fmt=fmt, delimiter='\t', 223 | header='Extracted from {}.\nwavelength[m] = {}\nColumns:\tu[m]\tv[m]\tRe(V)[Jy]\tIm(V)[Jy]\tweight'.format( 224 | MStb_name, wle)) 225 | 226 | if verbose: 227 | print("done.") 228 | 229 | if split_args: 230 | if not keep_tmp_ms: 231 | from subprocess import call 232 | if verbose: 233 | print("Removing temporary MS table {}".format( 234 | split_args['outputvis'])) 235 | call("rm -rf {}".format(split_args['outputvis']), shell=True) 236 | -------------------------------------------------------------------------------- /uvplot/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import (division, print_function, absolute_import, 5 | unicode_literals) 6 | 7 | import numpy as np 8 | from numpy.testing import assert_allclose 9 | 10 | from uvplot import UVTable 11 | from .uvtable import COLUMNS_V0 12 | 13 | 14 | def create_sample_uvtable(uvtable_filename): 15 | u = np.random.randn(10000)*1e4 # m 16 | v = np.random.randn(10000)*1e4 # m 17 | re = np.random.randn(10000) # Jy 18 | im = np.random.randn(10000) # Jy 19 | w = np.random.rand(10000)*1e4 20 | 21 | wle = 0.88e-3 # m 22 | 23 | np.savetxt(uvtable_filename, np.stack((u, v, re, im, w), axis=-1)) 24 | 25 | return (u, v, re, im, w), wle 26 | 27 | 28 | def test_init_uvtable(): 29 | 30 | uvtable_filename = "/tmp/uvtable.txt" 31 | uvtable, wle = create_sample_uvtable(uvtable_filename) 32 | u, v, re, im, w = uvtable 33 | 34 | # test reading from file 35 | # format='uvtable' 36 | uvt_file = UVTable(filename=uvtable_filename, wle=wle, columns=COLUMNS_V0) 37 | 38 | # test providing uvtable tuple 39 | # takes u, v in units of observing wavelength 40 | uvt_uvtable1 = UVTable(uvtable=(u, v, re, im, w), wle=wle, columns=COLUMNS_V0) 41 | uvt_uvtable2 = UVTable(uvtable=(u/wle, v/wle, re, im, w), columns=COLUMNS_V0) 42 | 43 | reference = np.hypot(u/wle, v/wle) 44 | 45 | assert_allclose(uvt_uvtable1.uvdist, reference, atol=0, rtol=1e-16) 46 | assert_allclose(uvt_uvtable2.uvdist, reference, atol=0, rtol=1e-16) 47 | assert_allclose(uvt_file.uvdist, reference, atol=0, rtol=1e-16) 48 | 49 | 50 | def test_deproject(): 51 | 52 | uvtable_filename = "/tmp/uvtable.txt" 53 | uvtable, wle = create_sample_uvtable(uvtable_filename) 54 | 55 | uv = UVTable(filename=uvtable_filename, wle=wle, columns=COLUMNS_V0) 56 | 57 | inc = np.radians(30) 58 | uv_30 = uv.deproject(inc, inplace=False) 59 | 60 | assert_allclose(uv_30.u, uv.u*np.cos(inc)) 61 | 62 | uv.deproject(inc) # inplace=True by default 63 | assert_allclose(uv_30.u, uv.u) 64 | 65 | 66 | def test_uvcut(): 67 | 68 | uvtable_filename = "/tmp/uvtable.txt" 69 | uvtable, wle = create_sample_uvtable(uvtable_filename) 70 | 71 | uv = UVTable(filename=uvtable_filename, wle=wle, columns=COLUMNS_V0) 72 | uv.header = dict(test='test_header') 73 | 74 | maxuv = 5e3 75 | uvt = uv.uvcut(maxuv) 76 | 77 | # manual uvcut 78 | uvcut = uv.uvdist <= maxuv 79 | 80 | assert_allclose(uvt.u, uv.u[uvcut]) 81 | assert_allclose(uvt.v, uv.v[uvcut]) 82 | assert_allclose(uvt.re, uv.re[uvcut]) 83 | assert_allclose(uvt.im, uv.im[uvcut]) 84 | assert_allclose(uvt.weights, uv.weights[uvcut]) 85 | 86 | assert uv.header == uvt.header 87 | -------------------------------------------------------------------------------- /uvplot/uvtable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import (division, print_function, absolute_import, 5 | unicode_literals) 6 | 7 | import sys 8 | import json 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from matplotlib.gridspec import GridSpec 12 | from json.decoder import JSONDecodeError 13 | 14 | from .constants import clight 15 | 16 | __all__ = ["UVTable"] 17 | 18 | COLUMNS_V0 = ['u', 'v', 'Re', 'Im', 'weights'] 19 | COLUMNS_V1 = ['u', 'v', 'Re', 'Im', 'weights', 'freqs', 'spws'] 20 | COLUMNS_V2 = ['u', 'v', 'V', 'weights', 'freqs', 'spws'] 21 | COLUMNS_FORMATS = [COLUMNS_V0, COLUMNS_V1, COLUMNS_V2] 22 | 23 | UNITS_V0 = ['lambda', 'lambda', 'Jy', 'Jy', 'None'] 24 | UNITS_V1 = ['lambda', 'lambda', 'Jy', 'Jy', 'None', 'Hz', 'None'] 25 | UNITS_V2 = ['lambda', 'lambda', 'Jy', 'None', 'Hz', 'None'] 26 | UNITS_FORMATS = [UNITS_V0, UNITS_V1, UNITS_V2] 27 | 28 | 29 | def parse_columns(columns): 30 | return "# Columns\t{}".format(' '.join(columns)) 31 | 32 | 33 | COLUMNS_FORMATS_TEXT = "FORMAT\t\tCOLUMNS\t\t\t\t\t\t\tCOLUMNS_LINE " \ 34 | "(copy-paste as 2nd line in the ASCII file)\n" \ 35 | "COLUMNS_V0\t{0}\t\t\t'{1}'\n" \ 36 | "COLUMNS_V1\t{2}\t'{3}'\n" \ 37 | "COLUMNS_V2\t{4}\t\t'{5}'\n".format(COLUMNS_V0, parse_columns(COLUMNS_V0), 38 | COLUMNS_V1, parse_columns(COLUMNS_V1), 39 | COLUMNS_V2, parse_columns(COLUMNS_V2)) 40 | 41 | 42 | class UVTable(object): 43 | """ 44 | Create a UVTable object by importing the visibilities from ASCII file or from a uvtable. 45 | Provide either a `uvtable` or a `filename`, but not both of them. 46 | 47 | Parameters 48 | ---------- 49 | uvtable : array-like, float 50 | A list of 1d arrays (or a 2d array) with columns specified as in the `columns` parameter. 51 | filename : str 52 | Name of the ASCII file containing the uv table. 53 | format : str 54 | If a filename is provided, the format of the uvtable: 'ascii' or 'binary' for .npz files. 55 | columns : list 56 | Columns mapping. Choose one among COLUMNS_V0, COLUMNS_V1, and COLUMNS_V2 (see Notes). 57 | wle : float, optional 58 | Observing wavelength. Default is 1, i.e. the `(u,v)` coordinates are 59 | assumed to be expressed in units of wavelength. 60 | **units**: same as the u, v coordinates in the uv table. 61 | 62 | Notes 63 | ----- 64 | Inside the UVTable object the (u, v) coordinates are stored in units of `wle`. 65 | 66 | Columns formats: 67 | 68 | COLUMNS_V0 = ['u', 'v', 'Re', 'Im', 'weights'] 69 | 70 | COLUMNS_V1 = ['u', 'v', 'Re', 'Im', 'weights', 'freqs', 'spws'] 71 | 72 | COLUMNS_V2 = ['u', 'v', 'V', 'weights', 'freqs', 'spws'] 73 | 74 | """ 75 | def __init__(self, uvtable=None, filename=None, format='ascii', columns=None, wle=1., **kwargs): 76 | 77 | if filename and uvtable: 78 | raise ValueError("Cannot provide both filename and uvtable.") 79 | 80 | if not filename and not uvtable: 81 | raise ValueError("Provide at least a filename or a uvtable.") 82 | 83 | self.wle = wle 84 | self._u = None 85 | self._v = None 86 | self._re = None 87 | self._im = None 88 | self._weights = None 89 | self._freqs = None 90 | self._spws = None 91 | self.header = None 92 | self.columns = None 93 | 94 | if filename: 95 | format = format.upper() 96 | 97 | if format == 'ASCII': 98 | self.read_ascii_uvtable(filename, columns=columns) 99 | 100 | if format == 'BINARY': 101 | self.read_binary_uvtable(filename, columns=columns) 102 | 103 | if uvtable: 104 | self.import_uvtable(uvtable, columns=columns) 105 | self.header = kwargs.get('header', None) 106 | 107 | self.ndat = len(self.u) 108 | self._uvdist = None 109 | self.bin_uvdist = None 110 | 111 | if self._freqs is not None: 112 | self._freqs_avg = np.mean(self._freqs) 113 | self._freqs_wrt_avg = self._freqs / self._freqs_avg 114 | 115 | @property 116 | def u(self): 117 | """ 118 | u coordinate 119 | **units**: observing wavelength 120 | 121 | """ 122 | return self._u 123 | 124 | @u.setter 125 | def u(self, value): 126 | self._u = np.ascontiguousarray(value) 127 | 128 | @property 129 | def v(self): 130 | """ 131 | v coordinate 132 | **units**: observing wavelength 133 | 134 | """ 135 | return self._v 136 | 137 | @v.setter 138 | def v(self, value): 139 | self._v = np.ascontiguousarray(value) 140 | 141 | @property 142 | def u_m(self): 143 | """ u coordinate 144 | **units**: m 145 | 146 | """ 147 | return self.u / self.freqs * clight 148 | 149 | @property 150 | def v_m(self): 151 | """ 152 | v coordinate 153 | **units**: m 154 | 155 | """ 156 | return self.v / self.freqs * clight 157 | 158 | @property 159 | def re(self): 160 | """ Real part of the Visibilities 161 | **units**: Jy 162 | 163 | """ 164 | return self._re 165 | 166 | @re.setter 167 | def re(self, value): 168 | self._re = np.ascontiguousarray(value) 169 | 170 | @property 171 | def im(self): 172 | """ 173 | Imaginary part of the Visibilities 174 | **units**: Jy 175 | 176 | """ 177 | return self._im 178 | 179 | @im.setter 180 | def im(self, value): 181 | self._im = np.ascontiguousarray(value) 182 | 183 | @property 184 | def weights(self): 185 | """ 186 | Weights of the Visibilities 187 | **units**: 1/Jy^2 188 | 189 | """ 190 | return self._weights 191 | 192 | @weights.setter 193 | def weights(self, value): 194 | self._weights = np.ascontiguousarray(value) 195 | 196 | @property 197 | def freqs(self): 198 | """ 199 | Frequency of the Visibilities 200 | **units**: Hz 201 | 202 | """ 203 | return self._freqs 204 | 205 | @freqs.setter 206 | def freqs(self, value): 207 | self._freqs = np.ascontiguousarray(value) 208 | 209 | @property 210 | def freqs_avg(self): 211 | """ 212 | Average frequency of the Visibilities 213 | **units**: Hz 214 | 215 | """ 216 | return self._freqs_avg 217 | 218 | @property 219 | def freqs_wrt_avg(self): 220 | """ 221 | Frequency of the Visibilities normalised to the average frequency. 222 | **units**: pure no. 223 | 224 | """ 225 | return self._freqs_wrt_avg 226 | 227 | @property 228 | def spws(self): 229 | """ 230 | Spectral window ID of the visibilities 231 | 232 | """ 233 | return self._spws 234 | 235 | @spws.setter 236 | def spws(self, value): 237 | self._spws = np.ascontiguousarray(value) 238 | 239 | @property 240 | def V(self): 241 | """ 242 | Complex Visibilities 243 | **units**: Jy 244 | 245 | """ 246 | return self.re + 1j * self.im 247 | 248 | @property 249 | def uvdist(self): 250 | """ 251 | Uv-distance 252 | **units**: observing wavelength 253 | 254 | """ 255 | self._uvdist = np.hypot(self._u, self._v) 256 | 257 | return self._uvdist 258 | 259 | def import_uvtable(self, uvtable, columns): 260 | 261 | self.columns = columns 262 | 263 | Ncols = len(uvtable) 264 | 265 | assert Ncols == len(columns), "Expect {} columns ({}), but the uvtable list " \ 266 | "contains {} columns.".format(len(columns), columns, Ncols) 267 | 268 | if columns == COLUMNS_V0: 269 | self.u = uvtable[0] 270 | self.v = uvtable[1] 271 | self.re = uvtable[2] 272 | self.im = uvtable[3] 273 | self.weights = uvtable[4] 274 | 275 | elif columns == COLUMNS_V2: 276 | self.u = uvtable[0] 277 | self.v = uvtable[1] 278 | self.re = uvtable[2].real 279 | self.im = uvtable[2].imag 280 | self.weights = uvtable[3] 281 | self.freqs = uvtable[4] 282 | self.spws = uvtable[5] 283 | 284 | elif columns == COLUMNS_V1: 285 | self.u = uvtable[0] 286 | self.v = uvtable[1] 287 | self.re = uvtable[2] 288 | self.im = uvtable[3] 289 | self.weights = uvtable[4] 290 | self.freqs = uvtable[5] 291 | self.spws = uvtable[6] 292 | 293 | self.u = self.u / self.wle 294 | self.v = self.v / self.wle 295 | 296 | def read_ascii_uvtable(self, filename, columns=None, **kwargs): 297 | """ Read an ASCII uvtable """ 298 | verbose = kwargs.get('verbose', True) 299 | 300 | if verbose: 301 | print("Reading uvtable from {} ...".format(filename)) 302 | 303 | uvtable = np.require(np.loadtxt(filename, unpack=True), requirements='C') 304 | 305 | Ncols, Nuv_tot = uvtable.shape 306 | 307 | if not columns: 308 | # try to detect the columns line 309 | with open(filename, 'r') as f: 310 | line1 = f.readline() 311 | line2 = f.readline() 312 | 313 | try: 314 | header = json.loads(line1) 315 | columns = header['columns'] 316 | 317 | except JSONDecodeError: 318 | if verbose: 319 | print("Line 1 is not a dictionary containing the header.") 320 | print("Trying to parse Columns format from line 2 of the ASCII file.") 321 | 322 | try: 323 | if len(line2.split(':\t')) < 2: 324 | print("Header not found in the ASCII file, please provide the " 325 | "columns parameter when creating the UVTable object.") 326 | sys.exit(1) 327 | 328 | head, cols = line2.split(':\t') 329 | 330 | if line2[0] != '#' or 'COLUMNS' not in line2.upper(): 331 | print("Unable to find 'columns' line in ASCII file") 332 | print("Trying to assume COLUMNS_V0: {}".format(COLUMNS_V0)) 333 | columns = COLUMNS_V0 334 | else: 335 | columns = cols.split('\n')[0].split(' ') 336 | 337 | assert Ncols == len(columns), "Expect {} columns ({}), but the ASCII file " \ 338 | "contains {} columns.".format(len(columns), 339 | columns, Ncols) 340 | 341 | except AssertionError: 342 | print("Expect the second line of the ASCII file to contain the columns format") 343 | print("Alternatively, provide the columns parameter choosing from:") 344 | print(COLUMNS_FORMATS_TEXT) 345 | 346 | if columns not in COLUMNS_FORMATS: 347 | raise AssertionError( 348 | "Detected columns {} format is not among the valid formats" 349 | "Pease provide `columns` format choosing one of\n" 350 | " COLUMNS_V0: {}\n" 351 | " COLUMNS_V1: {}\n" 352 | " COLUMNS_V2: {}\n".format(columns, COLUMNS_V0, COLUMNS_V1, COLUMNS_V2)) 353 | 354 | print("Assuming column format: {}".format(columns)) 355 | 356 | self.import_uvtable(uvtable, columns) 357 | 358 | if verbose: 359 | print("Reading uvtable from {} ...done".format(filename)) 360 | 361 | def read_binary_uvtable(self, filename, columns=None, **kwargs): 362 | """ Read binary uvtable """ 363 | verbose = kwargs.get('verbose', True) 364 | 365 | if verbose: 366 | print("Reading uvtable from {} ...".format(filename), end='') 367 | 368 | loaded = np.load(filename, allow_pickle=True) 369 | 370 | self.header = loaded['header'].item() 371 | 372 | if columns: 373 | assert columns == self.header['columns'] 374 | else: 375 | columns = self.header['columns'] 376 | 377 | self.columns = columns 378 | 379 | if columns == COLUMNS_V0: 380 | self.u = loaded['u'] 381 | self.v = loaded['v'] 382 | self.re = loaded['Re'] 383 | self.im = loaded['Im'] 384 | self.weights = loaded['weights'] 385 | elif columns == COLUMNS_V2: 386 | self.u = loaded['u'] 387 | self.v = loaded['v'] 388 | self.re = loaded['V'].real 389 | self.im = loaded['V'].imag 390 | self.weights = loaded['weights'] 391 | self.freqs = loaded['freqs'] 392 | self.spws = loaded['spws'] 393 | 394 | if verbose: 395 | print("done") 396 | 397 | def save_ascii_uvtable(self, filename, **kwargs): 398 | """ Save ascii uvtable """ 399 | ascii_fmt = kwargs.get('ascii_fmt', '%10.6e') 400 | 401 | self.header.update(columns=COLUMNS_V1, units=UNITS_V2) 402 | 403 | np.savetxt(filename, 404 | np.column_stack([self.u, 405 | self.v, 406 | self.V.real, 407 | self.V.imag, 408 | self.weights, 409 | self.freqs, 410 | self.spws]), 411 | fmt=ascii_fmt, delimiter='\t', 412 | header=json.dumps(self.header)) 413 | 414 | def save_binary_uvtable(self, filename, **kwargs): 415 | """ Save binary uvtable """ 416 | compressed = kwargs.get('compressed', True) 417 | 418 | self.header.update(columns=COLUMNS_V2, units=UNITS_V1) 419 | 420 | if compressed: 421 | np_save_func = np.savez_compressed 422 | else: 423 | np_save_func = np.savez 424 | 425 | np_save_func(filename, 426 | u=self.u, v=self.v, V=self.V, weights=self.weights, 427 | freqs=self.freqs, spws=self.spws, 428 | header=self.header) 429 | 430 | def save(self, filename, export_fmt, **kwargs): 431 | """ Save uvtable """ 432 | verbose = kwargs.get('verbose', True) 433 | 434 | if verbose: 435 | print("Write uvtable to file {} ...".format(filename), end='') 436 | sys.stdout.flush() 437 | 438 | export_fmt = export_fmt.upper() 439 | 440 | if export_fmt == "ASCII": 441 | self.save_ascii_uvtable(filename, **kwargs) 442 | 443 | elif export_fmt == "BINARY": 444 | self.save_binary_uvtable(filename, **kwargs) 445 | 446 | if verbose: 447 | print("done") 448 | 449 | def uvbin(self, uvbin_size, **kwargs): 450 | """ 451 | Compute the intervals (bins) of the uv-distances given the size of the bin (bin_size_wle). 452 | 453 | Parameters 454 | ---------- 455 | bin_size_wle : float 456 | Bin size in units of the wavelength. 457 | 458 | Note 459 | ---- 460 | To compute the weights, we do not need to divide by the weight_corr factor since it cancels out when we compute 461 | 462 | """ 463 | self.nbins = np.ceil(self.uvdist.max() / uvbin_size).astype('int') 464 | self.bin_uvdist = np.zeros(self.nbins) 465 | self.bin_weights = np.zeros(self.nbins) 466 | self.bin_count = np.zeros(self.nbins, dtype='int') 467 | self.uv_intervals = [] 468 | 469 | self.uv_bin_edges = np.arange(self.nbins + 1, dtype='float64') * uvbin_size 470 | 471 | for i in range(self.nbins): 472 | uv_interval = np.where((self.uvdist >= self.uv_bin_edges[i]) & 473 | (self.uvdist < self.uv_bin_edges[i + 1])) 474 | self.bin_count[i] = len(uv_interval[0]) 475 | 476 | if self.bin_count[i] != 0: 477 | self.bin_uvdist[i] = self.uvdist[uv_interval].sum() / self.bin_count[i] 478 | self.bin_weights[i] = np.sum(self.weights[uv_interval]) 479 | else: 480 | self.bin_uvdist[i] = self.uv_bin_edges[i] + 0.5 * uvbin_size 481 | 482 | self.uv_intervals.append(uv_interval) 483 | 484 | self.bin_re, self.bin_re_err = self.bin_quantity(self.re, **kwargs) 485 | self.bin_im, self.bin_im_err = self.bin_quantity(self.im, **kwargs) 486 | 487 | def bin_quantity(self, x, use_std=False): 488 | """ 489 | Compute bins of the quantity x based on the intervals of the uv-distances of the current Uvtable. 490 | To compute the uv-distances use Uvtable.compute_uvdist() and to compute the intervals use Uvtable.compute_uv_intervals(). 491 | 492 | Parameters 493 | ---------- 494 | x : array-like 495 | Quantity to be binned. 496 | use_std : bool, optional 497 | If provided, the error on each bin is computed as the standard deviation divided by sqrt(npoints_per_bin). 498 | 499 | Returns 500 | ------- 501 | bin_x, bin_x_err : array-like 502 | Respectively the bins and the bins error of the quantity x. 503 | 504 | """ 505 | bin_x, bin_x_err = np.zeros(self.nbins), np.zeros(self.nbins) 506 | 507 | for i in range(self.nbins): 508 | 509 | if self.bin_count[i] != 0: 510 | bin_x[i] = np.sum(x[self.uv_intervals[i]] * self.weights[self.uv_intervals[i]]) / \ 511 | self.bin_weights[i] 512 | 513 | if use_std is True: 514 | bin_x_err[i] = np.std(x[self.uv_intervals[i]]) 515 | else: 516 | bin_x_err[i] = 1. / np.sqrt(self.bin_weights[i]) 517 | 518 | return bin_x, bin_x_err 519 | 520 | def apply_phase(self, dRA=0, dDec=0): 521 | """ 522 | Apply a phase change in the uv space, corresponding to a translation by 523 | angular offsets (dRA, dDec) in the plane of the sky. 524 | 525 | Parameters 526 | ---------- 527 | dRA : float, optional 528 | Offset along the Right Ascension direction. dRA>0 translates image towards East. 529 | **units**: rad 530 | dDec : float, optional 531 | Offset along the Declination direction. dDec>0 translates image towards North. 532 | **units**: rad 533 | 534 | """ 535 | if dRA == 0 and dDec == 0: 536 | return 537 | 538 | dRA *= 2. * np.pi 539 | dDec *= 2. * np.pi 540 | 541 | phi = self.u * dRA + self.v * dDec 542 | vis = (self._re + 1j * self._im) * (np.cos(phi) + 1j * np.sin(phi)) 543 | 544 | self._re = vis.real 545 | self._im = vis.imag 546 | 547 | @staticmethod 548 | def rotate(x, y, theta): 549 | """ 550 | Rotate `(x,y)` coordinates counter-clockwise by an angle `theta`. 551 | 552 | Parameters 553 | ---------- 554 | x : array-like, float 555 | X coordinates. 556 | y : array-like, float 557 | Y coordinates. 558 | theta : float 559 | Angle of rotation. 560 | **units**: rad 561 | 562 | Returns 563 | ------- 564 | x_r, y_r : array-like 565 | X and Y coordinates rotated by an angle `theta`. 566 | 567 | """ 568 | if theta == 0: 569 | return x, y 570 | 571 | cos_t = np.cos(theta) 572 | sin_t = np.sin(theta) 573 | 574 | x_r = x * cos_t - y * sin_t 575 | y_r = x * sin_t + y * cos_t 576 | 577 | return x_r, y_r 578 | 579 | def deproject(self, inc, PA=0, inplace=True): 580 | """ 581 | Deproject `(u,v)` coordinates. 582 | First, a rotation of a position angle `PA` is applied, and then a deprojection by inclination `inc`. 583 | 584 | Parameters 585 | ---------- 586 | inc : float 587 | Inclination. 588 | **units**: rad 589 | PA : float, optional 590 | Position Angle (rad). 591 | **units**: rad 592 | inplace : bool, optional 593 | By default, the `(u,v)` coordinates stored in the UVTable object are overwritten. 594 | If False, `deproject` returns a copy of the UVTable object with deprojected `(u,v)` coordinates. 595 | 596 | Returns 597 | ------- 598 | If inplace is True, a copy of the UVTable object, with deprojected `(u,v)` coordinates. 599 | 600 | """ 601 | cos_inc = np.cos(inc) 602 | 603 | # Rotation by -PA 604 | # Note: the right ascension is a reversed x axis, thus the anti-rotation 605 | # of a reversed PA Angle is the same of a direct rotation. 606 | u_deproj, v_deproj = self.rotate(self.u.copy(), self.v.copy(), PA) 607 | 608 | # Deprojection 609 | # Note u and v in the Fourier space, thus 610 | # instead of dividing by cos(), we must multiply 611 | u_deproj *= cos_inc 612 | 613 | if inplace: 614 | self.u = u_deproj 615 | self.v = v_deproj 616 | 617 | else: 618 | if self.columns == COLUMNS_V0: 619 | return UVTable(uvtable=[u_deproj, v_deproj, self.re, self.im, self.weights], 620 | columns=COLUMNS_V0, header=self.header) 621 | elif self.columns == COLUMNS_V1: 622 | return UVTable(uvtable=[u_deproj, v_deproj, self.re, self.im, self.weights, 623 | self.freqs, self.spws], 624 | columns=COLUMNS_V1, header=self.header) 625 | elif self.columns == COLUMNS_V2: 626 | return UVTable(uvtable=[u_deproj, v_deproj, self.V, self.weights, self.freqs, 627 | self.spws], 628 | columns=COLUMNS_V2, header=self.header) 629 | 630 | def uvcut(self, maxuv, verbose=False): 631 | """ 632 | Apply uv cut to a table. Consider only baselines shorter than maxuv. 633 | 634 | Parameters 635 | ---------- 636 | maxuv : float 637 | Maximum baseline to be considered. 638 | **units**: observing wavelength. 639 | verbose : bool, optional 640 | If true, print an informative message. 641 | 642 | Returns: 643 | u, v, w, re, im, w : ndarrays 644 | Visibilities. 645 | 646 | """ 647 | uvcut = self.uvdist <= maxuv 648 | 649 | if verbose: 650 | print("Consider only baselines up to {} klambda ({} out of {} uv-points)".format( 651 | maxuv / 1e3, np.count_nonzero(uvcut), self.ndat)) 652 | 653 | if self.columns == COLUMNS_V0: 654 | return UVTable(uvtable=[a[uvcut] for a in [self.u, self.v, self.re, self.im, 655 | self.weights]], 656 | columns=COLUMNS_V0, header=self.header) 657 | elif self.columns == COLUMNS_V1: 658 | return UVTable(uvtable=[a[uvcut] for a in [self.u, self.v, self.re, self.im, 659 | self.weights, self.freqs, self.spws]], 660 | columns=COLUMNS_V1, header=self.header) 661 | elif self.columns == COLUMNS_V2: 662 | return UVTable(uvtable=[a[uvcut] for a in [self.u, self.v, self.V, 663 | self.weights, self.freqs, self.spws]], 664 | columns=COLUMNS_V2, header=self.header) 665 | 666 | def plot(self, fig_filename=None, color='k', linestyle='.', label='', 667 | fontsize=18, linewidth=2.5, alpha=1., yerr=True, caption=None, axes=None, 668 | uvbin_size=0, vis_filename=None, verbose=True): 669 | """ 670 | Produce a uv plot. 671 | 672 | Parameters 673 | ---------- 674 | fig_filename : str, optional 675 | File name of the output plot. 676 | color : str, optional 677 | Line color. 678 | linestyle : str, optional 679 | Line style. 680 | label : str, optional 681 | Legend label. 682 | fontsize : int, optional 683 | Font size to be used in the text of the plot. 684 | linewidth : float, optional 685 | Line width of the errobar. 686 | alpha : float, optional 687 | Transparency of the errorbar. 688 | yerr : bool, optional 689 | If True, the y errors are shown. Default is True. 690 | caption : dict, optional 691 | Caption for the whole plot. Must be a dictionary with following keys: 692 | 'x' and 'y' (the coordinates of the caption in units of the plotted quantities), 693 | 'fontsize' (the fontsize to be used), 'text' (the caption text). 694 | axes : matplotlib.Axes, optional 695 | If provided, the plots are done in axes, otherwise a new figure is created. 696 | uvbin_size : float, optional 697 | Size of the uv-distance bin. 698 | **units**: observing wavelength. 699 | vis_filename : str, optional 700 | File name where to store the binned visibiltiies, e.g. "vis_binned.txt". 701 | verbose : bool, optional 702 | If True, print informative messages. 703 | 704 | Returns 705 | ------- 706 | axes : matplotlib.Axes 707 | The Real and Imaginary axes on which the uv plot is done. 708 | 709 | """ 710 | if not axes: 711 | fig = plt.figure(figsize=(6, 6)) 712 | gs = GridSpec(2, 1, height_ratios=[4, 1]) 713 | axes = plt.subplot(gs[0]), plt.subplot(gs[1]) 714 | 715 | assert len(axes) == 2 716 | ax_Re, ax_Im = axes 717 | 718 | if uvbin_size != 0: 719 | self.uvbin(uvbin_size) 720 | 721 | if self.bin_uvdist is None: 722 | raise AttributeError("Expected uv table with already binned data, or " 723 | "an input parameter uvbin_size != 0") 724 | 725 | uvbins = self.bin_uvdist / 1.e3 726 | 727 | mask = self.bin_count != 0 728 | 729 | if verbose: 730 | print("Masking {} uv bins".format(len(uvbins[~mask]))) 731 | 732 | ax_Re.errorbar(uvbins[mask], self.bin_re[mask], 733 | yerr=self.bin_re_err[mask] if yerr is True else None, 734 | fmt=linestyle, 735 | color=color, linewidth=linewidth, capsize=2.5, 736 | markersize=13, elinewidth=0., label=label, alpha=alpha) 737 | 738 | ax_Im.errorbar(uvbins[mask], self.bin_im[mask], 739 | yerr=self.bin_im_err[mask] if yerr is True else None, 740 | fmt=linestyle, 741 | color=color, linewidth=linewidth, capsize=2.5, 742 | markersize=13, elinewidth=0., label=label, alpha=alpha) 743 | 744 | ax_Im.set_xlabel(r'uv-distance (k$\lambda$)', fontweight='bold', 745 | fontsize=fontsize) 746 | ax_Re.set_ylabel('Re(V) (Jy)', fontweight='bold', fontsize=fontsize) 747 | ax_Im.set_ylabel('Im(V) (Jy)', fontweight='bold', fontsize=fontsize) 748 | ax_Re.yaxis.set_label_coords(-0.25, 0.5) 749 | ax_Im.yaxis.set_label_coords(-0.25, 0.5) 750 | 751 | ax_Re.tick_params(labelbottom=False) 752 | 753 | if vis_filename: 754 | np.savetxt(vis_filename, 755 | np.stack([uvbins[mask], 756 | self.bin_re[mask], self.bin_re_err[mask], 757 | self.bin_im[mask], self.bin_im_err[mask]], axis=-1), 758 | header="uv-distance\tRe(V)\te_Re(V)\tIm(V)\te_Im(V)\n" 759 | "(klambda)\t(Jy)\t(Jy)\t(Jy)\t(Jy)") 760 | if verbose: 761 | print("Visibilities written to file {}".format(vis_filename)) 762 | 763 | # ax[0].set_xlim(uvlim) 764 | # ax[1].set_xlim(uvlim) 765 | # ax[0].set_ylim(Jylims[0]) 766 | # ax[1].set_ylim(Jylims[1]) 767 | # ax_Re.set_xticklabels(["") 768 | # ax[1].set_yticklabels(Jyticks_im) 769 | 770 | if label: 771 | ax_Re.legend(fontsize=fontsize, frameon=False) 772 | 773 | if caption: 774 | caption_keys = ['x', 'y', 'text', 'fontsize'] 775 | if all(k in caption for k in caption_keys): 776 | pass 777 | else: 778 | raise KeyError( 779 | "Expected caption dict with following keys: {}, but got {}".format( 780 | caption_keys, caption.keys())) 781 | 782 | ax_Re.text(caption['x'], caption['y'], caption['text'], 783 | fontsize=caption['fontsize'], fontweight='bold') 784 | 785 | ax_Re.figure.subplots_adjust(left=0.25, right=0.97, hspace=0., bottom=0.15, top=0.98) 786 | 787 | if fig_filename: 788 | plt.savefig(fig_filename) 789 | else: 790 | return axes 791 | --------------------------------------------------------------------------------