radio-beam-0.3.3/0000755000175100001710000000000014025155267014323 5ustar runnerdocker00000000000000radio-beam-0.3.3/.github/0000755000175100001710000000000014025155267015663 5ustar runnerdocker00000000000000radio-beam-0.3.3/.github/workflows/0000755000175100001710000000000014025155267017720 5ustar runnerdocker00000000000000radio-beam-0.3.3/.github/workflows/main.yml0000644000175100001710000000400314025155256021362 0ustar runnerdocker00000000000000name: Run tests on: [push, pull_request] jobs: tests: name: ${{ matrix.name}} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest python-version: 3.7 name: Python 3.7 with minimal dependencies toxenv: py37-test - os: ubuntu-latest python-version: 3.7 name: Python 3.7 with all dependencies (except CASA) toxenv: py37-test-all - os: ubuntu-18.04 python-version: 3.6 name: Python 3.6 with minimal dependencies and CASA toxenv: py36-test-casa # - os: ubuntu-18.04 # python-version: 3.6 # name: Python 3.6, CASA, and dev versions of key dependencies # toxenv: py36-test-casa-dev - os: ubuntu-latest python-version: 3.8 name: Python 3.8, all dependencies, and dev versions of key dependencies toxenv: py38-test-dev - os: macos-latest python-version: 3.7 name: Python 3.7 with all dependencies, and dev versions of key dependencies (no CASA) on MacOS X toxenv: py37-test-all-dev - os: windows-latest python-version: 3.7 name: Python 3.7, all dependencies, and dev versions of key dependencies (no CASA) on Windows toxenv: py37-test-all-dev - os: ubuntu-latest python-version: 3.8 name: Documentation toxenv: build_docs steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install testing dependencies run: python -m pip install tox codecov - name: Run tests with ${{ matrix.name }} run: tox -v -e ${{ matrix.toxenv }} - name: Upload coverage to codecov uses: codecov/codecov-action@v1.0.13 with: file: ./coverage.xmlradio-beam-0.3.3/.github/workflows/publish.yml0000644000175100001710000000172714025155256022116 0ustar runnerdocker00000000000000name: Build and upload to PyPI on: [push, pull_request] jobs: build_sdist_and_wheel: name: Build source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 name: Install Python with: python-version: '3.7' - name: Install build run: python -m pip install build - name: Build sdist run: python -m build --sdist --wheel --outdir dist/ . - uses: actions/upload-artifact@v2 with: path: dist/* upload_pypi: name: Upload to PyPI needs: [build_sdist_and_wheel] runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') steps: - uses: actions/download-artifact@v2 with: name: artifact path: dist - uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.pypi_password }} radio-beam-0.3.3/.gitignore0000644000175100001710000000117614025155256016316 0ustar runnerdocker00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt pip-wheel-metadata/ # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation docs/_build/ docs/api *.fits # Other generated stuff */version.py */cython_version.pyradio-beam-0.3.3/.readthedocs.yml0000644000175100001710000000033414025155256017407 0ustar runnerdocker00000000000000version: 2 name: radio-beam build: image: latest # Install regular dependencies. python: version: 3.7 install: - method: pip path: . extra_requirements: - docs submodules: include: allradio-beam-0.3.3/CHANGES.rst0000644000175100001710000000173414025155256016130 0ustar runnerdocker000000000000000.3.2 (2021-03-19) ------------------ - Optimized the deconvolution operation to avoid extra unit conversions and creation of new `Beam` objects. (https://github.com/radio-astro-tools/radio-beam/pull/87) 0.3.1 (2019-02-20) ------------------ - Set mult/div for convolution/deconvolution in `Beam` and `Beams`. The `==` and `!=` operators also work with `Beams` now. (https://github.com/radio-astro-tools/radio-beam/pull/75) - Added common beam operations to `Beams`. (https://github.com/radio-astro-tools/radio-beam/pull/67) - Fix PA usage for plotting and kernel routines. (https://github.com/radio-astro-tools/radio-beam/pull/65) 0.2 (2017-10-25) ---------------- - Changed repo name to `radio-beam` from `radio_beam`. (https://github.com/radio-astro-tools/radio-beam/pull/59) - Enhancement: Added support for multiple beams through the `Beams` class. (https://github.com/radio-astro-tools/radio-beam/pull/51) 0.1 (2017-09-08) ---------------- First release radio-beam-0.3.3/LICENSE.rst0000644000175100001710000000273314025155256016142 0ustar runnerdocker00000000000000Copyright (c) 2016, radio-astro-tools developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. radio-beam-0.3.3/MANIFEST.in0000644000175100001710000000047414025155256016064 0ustar runnerdocker00000000000000include README.md include CHANGES.rst include LICENSE.rst include pyproject.toml include setup.cfg recursive-include *.pyx *.c *.pxd recursive-include docs * recursive-include licenses * recursive-include cextern * recursive-include scripts * prune build prune docs/_build prune docs/api global-exclude *.pyc *.o radio-beam-0.3.3/PKG-INFO0000644000175100001710000000064114025155267015421 0ustar runnerdocker00000000000000Metadata-Version: 2.1 Name: radio-beam Version: 0.3.3 Summary: Operations for radio astronomy beams with astropy Home-page: http://radio_beam.readthedocs.org Author: Adam Leroy, Adam Ginsburg, Erik Rosolowsky, Tom Robitaille, and Eric Koch Author-email: adam.g.ginsburg@gmail.com, koch.eric.w@gmail.com License: BSD Description: UNKNOWN Platform: UNKNOWN Provides-Extra: test Provides-Extra: docs Provides-Extra: all radio-beam-0.3.3/README.md0000644000175100001710000000105314025155256015577 0ustar runnerdocker00000000000000Radio Beam: Tools for Beam IO and Manipulation ============================================== Radio Beam is a simple toolkit for reading beam information from FITS headers and manipulating beams. Some example applications include: * Convolution and deconvolution * Unit conversion (Jy to/from K) [Basic Documentation](https://github.com/radio-astro-tools/radio_beam/blob/master/docs/index.rst) is available. [![Build Status](https://travis-ci.org/radio-astro-tools/radio_beam.svg?branch=master)](https://travis-ci.org/radio-astro-tools/radio_beam) radio-beam-0.3.3/docs/0000755000175100001710000000000014025155267015253 5ustar runnerdocker00000000000000radio-beam-0.3.3/docs/Makefile0000644000175100001710000001116414025155256016714 0ustar runnerdocker00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest #This is needed with git because git doesn't create a dir if it's empty $(shell [ -d "_static" ] || mkdir -p _static) help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR) -rm -rf api html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Astropy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Astropy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Astropy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Astropy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." radio-beam-0.3.3/docs/_templates/0000755000175100001710000000000014025155267017410 5ustar runnerdocker00000000000000radio-beam-0.3.3/docs/_templates/autosummary/0000755000175100001710000000000014025155267021776 5ustar runnerdocker00000000000000radio-beam-0.3.3/docs/_templates/autosummary/base.rst0000644000175100001710000000037214025155256023442 0ustar runnerdocker00000000000000{% extends "autosummary_core/base.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}radio-beam-0.3.3/docs/_templates/autosummary/class.rst0000644000175100001710000000037314025155256023636 0ustar runnerdocker00000000000000{% extends "autosummary_core/class.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}radio-beam-0.3.3/docs/_templates/autosummary/module.rst0000644000175100001710000000037414025155256024017 0ustar runnerdocker00000000000000{% extends "autosummary_core/module.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}radio-beam-0.3.3/docs/api.rst0000644000175100001710000000045014025155256016553 0ustar runnerdocker00000000000000API Documentation ================= .. automodapi:: radio_beam :no-inheritance-diagram: :inherited-members: .. automodapi:: radio_beam.commonbeam :no-inheritance-diagram: :no-inherited-members: .. automodapi:: radio_beam.utils :no-inheritance-diagram: :no-inherited-members: radio-beam-0.3.3/docs/commonbeam.rst0000644000175100001710000001407014025155256020122 0ustar runnerdocker00000000000000.. _com_beam: Finding the smallest common beam ================================ radio-beam implements an exact solution for sets of 2 beams and an approximate method---`the Khachiyan algorithm `_---for larger sets. The former case is straightforward to compute as the beams can be transformed into a space where the larger beam is circular to find the overlap area (`~radio_beam.commonbeam.common_2beams`). Our implementation borrows from the implementation in `CASA `_ (see `here `__). Note that CASA uses this method for sets of beams larger than 2 by iterating through the beams and comparing each beam to the largest beam from the previous iterations. However, this approach is not guaranteed to find the minimum enclosing beam. For sets of more than two beams, finding the smallest common beam is a convex optimization problem equivalent to finding the minimum enclosed ellipse for a set of ellipses centered on the origin (`Boyd & Vandenberghe `_, see `example `_ in Sec 8.4.1). To avoid having radio-beam depend on convex optimization libraries, we implement the Khachiyan algorithm as an approximate method for finding the minimum ellipse. This algorithm finds the minimum ellipse that encloses the convex hull of a set of points (`Khachiyan & Todd 1993 `_, `Todd & Yildirim 2005 `_). By sampling a points on the boundaries of the beams in the set, we create a set of points whose convex hull is used to find the common beam. Since the minimum ellipse method is approximate, some solutions for the common beam will be slightly underestimated and the solution cannot be deconvolved from the whole set of beams. To overcome this issue, a small `epsilon` correction factor is added to the ellipse edges to encourage a valid common beam solution. Since `epsilon` is added to all sides, this correction will at most increase the common beam area by :math:`(1+\epsilon)^2`. The default values of `epsilon` is :math:`5\times10^{-4}`, so this will have a very small effect on the size of the common beam. The implementation in radio-beam is adapted from `a generalized python implementation `_ and `the original matlab version `_ written by Nima Moshtagh (see accompanying paper `here `__). Could not find common beam to deconvolve all beams ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You may encounter the error "Could not find common beam to deconvolve all beams." This occurs because the Khachiyan algorithm has converged to within the allowed tolerance with a solution marginally *smaller* than the enclosing ellipse. To mitigate this issue, the default settings now enable the keyword `auto_increase_epsilon=True`, which allows for small increases in `epsilon` until the common beam solution can be deconvolved by all beams in the set. The solution for the common beam will be iterated until this point, or until (1) `max_iter` is reached (default is 10) or (2) `max_epsilon` is reached (default is 1e-3). These values appear to work well with different ALMA and VLA data, but may need to be changed for specific data cubes. **If you notice these default parameters do not work for your data, please raise an issue** `here `_. In the case this issue persists, there are a few ways it can be fixed: 1. **Changing the tolerance.** - The default tolerance for convergence of the Khachiyan algorithm (`~radio_beam.commonbeam.getMinVolEllipse`) is `tolerance=1e-5`. This tolerance can be changed in `~radio_beam.Beams.common_beam` by specifying a new tolerance. Convergence may be met by either increasing or decreasing the tolerance; it depends on having the algorithm not step within the minimum enclosing ellipse, leading to the error. Note that decreasing the tolerance by an order of magnitude will require an order of magnitude more iterations for the algorithm to converge. It will typically be faster to change `epsilon` (see below). 2. **Changing epsilon** - A second parameter `epsilon` controls the points sampled at the edges of the beams in the set (`~radio_beam.commonbeam.ellipse_edges`), which are used in the Khachiyan algorithm. `epsilon` is the fraction beyond the true edge of the ellipse that points will be sampled at. For example, the default value of `epsilon=1e-3` will sample points 0.1% larger than the edge of the ellipse. Increasing `epsilon` ensures that a valid common beam can be found, avoiding the tolerance issue, but will result in overestimating the common beam area. For most radio data sets, where the beam is oversampled by :math:`\sim 3--5` pixels, moderate increases in `epsilon` will increase the common beam area far less than a pixel area, making the overestimation negligible. 3. **Changing the `auto_increase_epsilon` keywords** - To avoid the manual guess-and-check, the `auto_increase_epsilon` can be made more lenient to encourage a valid solution. This can be achieved by (i) increasing the intial values of `epsilon` (equivalent to #2), (ii) decreasing the number of iterations (forces larger incremental steps in `epsilon`, or (iii) increasing `max_epsilon`. (i) and (ii) will both reduce the number of iterations making it quicker to test different keyword values. (iii) allows for the common beam solution to be moderately larger. As noted above, increasing `epsilon` allows for the common beam area to be overestimated *up to* :math:`(1+\epsilon)^2`. We recommend testing different values of tolerance to find convergence, and if the error persists, to then slowly increase epsilon until a valid common beam is found. radio-beam-0.3.3/docs/conf.py0000644000175100001710000001606014025155256016553 0ustar runnerdocker00000000000000# -*- coding: utf-8 -*- # Licensed under a 3-clause BSD style license - see LICENSE.rst # # Astropy documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this file. # # All configuration values have a default. Some values are defined in # the global Astropy configuration which is loaded here before anything else. # See astropy.sphinx.conf for which values are set there. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) # IMPORTANT: the above commented section was generated by sphinx-quickstart, but # is *NOT* appropriate for astropy or Astropy affiliated packages. It is left # commented out with this explanation to make it clear why this should not be # done. If the sys.path entry above is added, when the astropy.sphinx.conf # import occurs, it will import the *source* version of astropy instead of the # version installed (if invoked as "make html" or directly with sphinx), or the # version in the build directory (if "python setup.py build_sphinx" is used). # Thus, any C-extensions that are needed to build the documentation will *not* # be accessible, and the documentation will not build correctly. import os import sys import datetime from importlib import import_module try: from sphinx_astropy.conf.v1 import * # noqa except ImportError: print('ERROR: the documentation requires the sphinx-astropy package to be installed') sys.exit(1) # Get configuration information from setup.cfg from configparser import ConfigParser conf = ConfigParser() conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) setup_cfg = dict(conf.items('metadata')) # -- General configuration ---------------------------------------------------- # By default, highlight as Python 3. highlight_language = 'python3' # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.2' # To perform a Sphinx version check that needs to be more specific than # major.minor, call `check_sphinx_version("x.y.z")` here. # check_sphinx_version("1.2.1") # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns.append('_templates') # This is added to the end of RST files - a good place to put substitutions to # be used globally. rst_epilog += """ """ # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does project = setup_cfg['name'] author = setup_cfg['author'] copyright = '{0}, {1}'.format( datetime.datetime.now().year, setup_cfg['author']) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. from pkg_resources import get_distribution version = release = get_distribution(setup_cfg['name']).version # -- Options for HTML output -------------------------------------------------- # A NOTE ON HTML THEMES # The global astropy configuration uses a custom theme, 'bootstrap-astropy', # which is installed along with astropy. A different theme can be used or # the options for this theme can be modified by overriding some of the # variables set in the global configuration. The variables set in the # global configuration are listed below, commented out. # Add any paths that contain custom themes here, relative to this directory. # To use a different custom theme, add the directory containing the theme. #html_theme_path = [] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. To override the custom theme, set this to the # name of a builtin theme or the name of a custom theme in html_theme_path. #html_theme = None html_theme_options = { 'logotext1': 'packagename', # white, semi-bold 'logotext2': '', # orange, light 'logotext3': ':docs' # white, light } # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = '' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = '' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = '{0} v{1}'.format(project, release) # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- Options for LaTeX output ------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [('index', project + '.tex', project + u' Documentation', author, 'manual')] # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [('index', project.lower(), project + u' Documentation', [author], 1)] # -- Options for the edit_on_github extension --------------------------------- if eval(setup_cfg.get('edit_on_github')): extensions += ['sphinx_astropy.ext.edit_on_github'] versionmod = __import__(setup_cfg['package_name'] + '.version') edit_on_github_project = setup_cfg['github_project'] if versionmod.version.release: edit_on_github_branch = "v" + versionmod.version.version else: edit_on_github_branch = "master" edit_on_github_source_root = "" edit_on_github_doc_root = "docs" # -- Resolving issue number to links in changelog ----------------------------- github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project']) # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- # # nitpicky = True # nitpick_ignore = [] # # Some warnings are impossible to suppress, and you can list specific references # that should be ignored in a nitpick-exceptions file which should be inside # the docs/ directory. The format of the file should be: # # # # for example: # # py:class astropy.io.votable.tree.Element # py:class astropy.io.votable.tree.SimpleElement # py:class astropy.io.votable.tree.SimpleElementWithContent # # Uncomment the following lines to enable the exceptions: # # for line in open('nitpick-exceptions'): # if line.strip() == "" or line.startswith("#"): # continue # dtype, target = line.split(None, 1) # target = target.strip() # nitpick_ignore.append((dtype, six.u(target))) radio-beam-0.3.3/docs/convolution_kernels.rst0000644000175100001710000000521014025155256022103 0ustar runnerdocker00000000000000.. _convkernels: Making convolution kernels ========================== `~radio_beam.Beam` can produce two types of kernels: a Gaussian (`~radio_beam.Beam.as_kernel`) and a top-hat (`~radio_beam.Beam.as_tophat_kernel`). As an example, consider the elliptical beam:: >>> import astropy.units as u >>> from radio_beam import Beam >>> my_beam = Beam(3*u.arcsec, 1.5*u.arcsec, 60*u.deg) Gaussian ^^^^^^^^ `~radio_beam.Beam.as_kernel` will return an elliptical Gaussian kernel given the angular size of a pixel:: >>> pix_scale = 0.5 * u.arcsec >>> gauss_kern = my_beam.as_kernel(pix_scale) `gauss_kern` will be a `~radio_beam.beam.EllipticalGaussian2DKernel` object and has the same methods, attributes and keyword arguments as `Kernel2D `__ in astropy's convolution package. These keyword arguments can be passed to `~radio_beam.Beam.as_kernel`. See the `astropy documentation `_ for more information on convolution kernels. Top-Hat ^^^^^^^ `~radio_beam.Beam.as_tophat_kernel` returns an elliptical top-hat kernel scales to have the same area as a Gaussian kernel within the FWHM. Similar to the Gaussian kernel, only the pixel scale needs to be given:: >>> tophat_kern = my_beam.as_tophat_kernel(pix_scale) `tophat_kern` is a `~radio_beam.beam.EllipticalTophat2DKernel` object, also derived from `Kernel2D `__ in astropy's convolution package. Keyword arguments can be passed to `~radio_beam.Beam.as_tophat_kernel`. The values in the kernel are normalized to unity, and it is suitable for convolution. However, the top-hat kernel is also useful for masking purposes, in which case a boolean version of the kernel is useful. To make a boolean version, we need to access the array in the kernel object and look for non-zero values:: >>> tophat_kern_bool = tophat_kern.array > 0 `tophat_kern_bool` is suitable for use with morphological operations, such as those in `scipy.ndimage `_. Convolution kernels from multiple beams ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From a `~radio_beam.Beams` object, a convolution kernel can be made for each beam in the set by slicing:: >>> from radio_beam import Beams >>> from astropy.io import fits >>> bin_hdu = fits.open('file.fits')[1] # doctest: +SKIP >>> beams = Beams.from_fits_bintable(bin_hdu) # doctest: +SKIP >>> beams[0].as_kernel(pix_scale) # doctest: +SKIP radio-beam-0.3.3/docs/index.rst0000644000175100001710000000436714025155256017124 0ustar runnerdocker00000000000000Radio Beam ========== A tool for manipulating and utilizing two dimensional gaussian beams within the `astropy `__ framework. Examples -------- Read a beam from a fits header:: >>> from radio_beam import Beam >>> from astropy.io import fits >>> header = fits.getheader('file.fits') # doctest: +SKIP >>> my_beam = Beam.from_fits_header(header) # doctest: +SKIP >>> print(my_beam) # doctest: +SKIP Beam: BMAJ=0.038652855902928 arcsec BMIN=0.032841067761183604 arcsec BPA=32.29655838013 deg Create a beam from scratch:: >>> from astropy import units as u >>> my_beam = Beam(0.5*u.arcsec) Use a beam for Jy -> K conversion:: >>> (1*u.Jy).to(u.K, u.brightness_temperature(25*u.GHz, my_beam)) # doctest: +FLOAT_CMP Convolve with another beam:: >>> my_asymmetric_beam = Beam(0.75*u.arcsec, 0.25*u.arcsec, 0*u.deg) >>> my_other_asymmetric_beam = Beam(0.75*u.arcsec, 0.25*u.arcsec, 90*u.deg) >>> my_asymmetric_beam.convolve(my_other_asymmetric_beam) # doctest: +SKIP Beam: BMAJ=0.790569415042 arcsec BMIN=0.790569415042 arcsec BPA=45.0 deg Deconvolve another beam:: >>> my_big_beam = Beam(1.0*u.arcsec, 1.0*u.arcsec, 0*u.deg) >>> my_little_beam = Beam(0.5*u.arcsec, 0.5*u.arcsec, 0*u.deg) >>> my_big_beam.deconvolve(my_little_beam) # doctest: +SKIP Beam: BMAJ=0.866025403784 arcsec BMIN=0.866025403784 arcsec BPA=0.0 deg Read a table of beams:: >>> from radio_beam import Beams >>> from astropy.io import fits >>> bin_hdu = fits.open('file.fits')[1] # doctest: +SKIP >>> beams = Beams.from_fits_bintable(bin_hdu) # doctest: +SKIP Create a table of beams:: >>> my_beams = Beams([1.5, 1.3] * u.arcsec, [1., 1.2] * u.arcsec, [0, 50] * u.deg) Find the largest beam in the set:: >>> my_beams.largest_beam() Beam: BMAJ=1.3 arcsec BMIN=1.2 arcsec BPA=50.0 deg Find the smallest common beam for the set (see :ref:`here ` for more on common beams):: >>> my_beams.common_beam() # doctest: +SKIP Beam: BMAJ=1.50671729431 arcsec BMIN=1.25695643792 arcsec BPA=6.69089813778 deg Getting started ^^^^^^^^^^^^^^^ .. toctree:: :maxdepth: 2 install.rst commonbeam.rst convolution_kernels.rst api.rst radio-beam-0.3.3/docs/install.rst0000644000175100001710000000346514025155256017461 0ustar runnerdocker00000000000000Installing ``radio-beam`` ============================ Requirements ------------ This package has the following dependencies: * `Python `_ 2.7 or later (Python 3.x is supported) * `Numpy `_ 1.8 or later * `Astropy `__ 1.0 or later * `six `__ Installation ------------ To install the latest stable release, you can type:: pip install radio-beam or you can download the latest tar file from `PyPI `_ and install it using:: python setup.py install Developer version ----------------- If you want to install the latest developer version of the radio-beam code, you can do so from the git repository:: git clone https://github.com/radio-astro-tools/radio-beam.git cd radio-beam python setup.py install You may need to add the ``--user`` option to the last line `if you do not have root access `_. You can also install the latest developer version in a single line with pip:: pip install git+https://github.com/radio-astro-tools/radio-beam.git Installing into CASA -------------------- Installing packages in CASA is fairly straightforward. The process is described `here `_. In short, you can do the following: First, we need to make sure `pip `__ is installed. Start up CASA as normal, and type:: CASA <1>: from setuptools.command import easy_install CASA <2>: easy_install.main(['--user', 'pip']) Now, quit CASA and re-open it, then type the following to install ``radio-beam``:: CASA <1>: import pip CASA <2>: pip.main(['install', 'radio-beam', '--user']) radio-beam-0.3.3/docs/make.bat0000644000175100001710000001064114025155256016660 0ustar runnerdocker00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Astropy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Astropy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end radio-beam-0.3.3/docs/nitpick-exceptions0000644000175100001710000000005614025155256021015 0ustar runnerdocker00000000000000py:obj radio_beam,commonbeam.transform_ellipseradio-beam-0.3.3/docs/rtd-pip-requirements0000644000175100001710000000022414025155256021272 0ustar runnerdocker00000000000000-e git+http://github.com/astropy/astropy-helpers.git#egg=astropy_helpers numpy=1.15 Cython -e git+http://github.com/astropy/astropy.git#egg=astropy radio-beam-0.3.3/pyproject.toml0000644000175100001710000000020414025155256017231 0ustar runnerdocker00000000000000[build-system] requires = ["setuptools", "setuptools_scm", "wheel"] build-backend = 'setuptools.build_meta' radio-beam-0.3.3/radio_beam/0000755000175100001710000000000014025155267016405 5ustar runnerdocker00000000000000radio-beam-0.3.3/radio_beam/__init__.py0000644000175100001710000000062714025155256020521 0ustar runnerdocker00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from ._astropy_init import __version__, test from pkg_resources import get_distribution, DistributionNotFound from .beam import (Beam, EllipticalGaussian2DKernel, EllipticalTophat2DKernel) from .multiple_beams import Beams __all__ = ['Beam', 'EllipticalTophat2DKernel', 'EllipticalGaussian2DKernel', 'Beams'] radio-beam-0.3.3/radio_beam/_astropy_init.py0000644000175100001710000000340714025155256021644 0ustar runnerdocker00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ['__version__'] # this indicates whether or not we are in the package's setup.py try: _ASTROPY_SETUP_ except NameError: import builtins builtins._ASTROPY_SETUP_ = False try: from .version import version as __version__ except ImportError: __version__ = '' if not _ASTROPY_SETUP_: # noqa import os from warnings import warn from astropy.config.configuration import ( update_default_config, ConfigurationDefaultMissingError, ConfigurationDefaultMissingWarning) # Create the test function for self test from astropy.tests.runner import TestRunner test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) test.__test__ = False __all__ += ['test'] # add these here so we only need to cleanup the namespace at the end config_dir = None if not os.environ.get('ASTROPY_SKIP_CONFIG_UPDATE', False): config_dir = os.path.dirname(__file__) config_template = os.path.join(config_dir, __package__ + ".cfg") if os.path.isfile(config_template): try: update_default_config( __package__, config_dir, version=__version__) except TypeError as orig_error: try: update_default_config(__package__, config_dir) except ConfigurationDefaultMissingError as e: wmsg = (e.args[0] + " Cannot install default profile. If you are " "importing from source, this is expected.") warn(ConfigurationDefaultMissingWarning(wmsg)) del e except Exception: raise orig_error radio-beam-0.3.3/radio_beam/beam.py0000644000175100001710000006524714025155256017677 0ustar runnerdocker00000000000000import six from astropy import units as u from astropy.io import fits from astropy import constants from astropy import wcs import numpy as np import warnings # Imports for the custom kernels from astropy.modeling.models import Ellipse2D, Gaussian2D from astropy.convolution import Kernel2D from astropy.convolution.kernels import _round_up_to_odd_integer from .utils import deconvolve_optimized, convolve, RadioBeamDeprecationWarning # Conversion between a twod Gaussian FWHM**2 and effective area FWHM_TO_AREA = 2*np.pi/(8*np.log(2)) SIGMA_TO_FWHM = np.sqrt(8*np.log(2)) class NoBeamException(Exception): pass def _to_area(major,minor): return (major * minor * FWHM_TO_AREA).to(u.sr) unit_format = {u.deg: r'\\circ', u.arcsec: "''", u.arcmin: "'"} class Beam(u.Quantity): """ An object to handle single radio beams. """ def __new__(cls, major=None, minor=None, pa=None, area=None, default_unit=u.arcsec, meta=None): """ Create a new Gaussian beam Parameters ---------- major : :class:`~astropy.units.Quantity` with angular equivalency The FWHM major axis minor : :class:`~astropy.units.Quantity` with angular equivalency The FWHM minor axis pa : :class:`~astropy.units.Quantity` with angular equivalency The beam position angle area : :class:`~astropy.units.Quantity` with steradian equivalency The area of the beam. This is an alternative to specifying the major/minor/PA, and will create those values assuming a circular Gaussian beam. default_unit : :class:`~astropy.units.Unit` The unit to impose on major, minor if they are specified as floats """ # improve to some kwargs magic later # error checking # ... given an area make a round beam assuming it is Gaussian if area is not None: if major is not None: raise ValueError("Can only specify one of {major,minor,pa} " "and {area}") rad = np.sqrt(area/(2*np.pi)) * u.deg major = rad * SIGMA_TO_FWHM minor = rad * SIGMA_TO_FWHM pa = 0.0 * u.deg # give specified values priority if major is not None: if u.deg.is_equivalent(major): major = major else: warnings.warn("Assuming major axis has been specified in degrees") major = major * u.deg if minor is not None: if u.deg.is_equivalent(minor): minor = minor else: warnings.warn("Assuming minor axis has been specified in degrees") minor = minor * u.deg if pa is not None: if u.deg.is_equivalent(pa): pa = pa else: warnings.warn("Assuming position angle has been specified in degrees") pa = pa * u.deg else: pa = 0.0 * u.deg # some sensible defaults if minor is None: minor = major if minor > major: raise ValueError("Minor axis greater than major axis.") self = super(Beam, cls).__new__(cls, _to_area(major,minor).value, u.sr) self._major = major self._minor = minor self._pa = pa self.default_unit = default_unit if meta is None: self.meta = {} elif isinstance(meta, dict): self.meta = meta else: raise TypeError("metadata must be a dictionary") return self @classmethod def from_fits_bintable(cls, bintable, tolerance=0.01): """ Instantiate a single beam from a bintable from a CASA-produced image HDU. The beams in the BinTableHDU will be averaged to form a single beam. Parameters ---------- bintable : fits.BinTableHDU The table data containing the beam information tolerance : float The fractional tolerance on the beam size to include when averaging to a single beam Returns ------- beam : Beam A new beam object that is the average of the table beams """ from astropy.stats import circmean bmaj = bintable.data['BMAJ'] bmin = bintable.data['BMIN'] bpa = bintable.data['BPA'] if np.any(np.isnan(bmaj) | np.isnan(bmin) | np.isnan(bpa)): raise ValueError("NaN beam encountered.") for par in (bmin,bmaj): par_mean = par.mean() if (par.max() > par_mean*(1+tolerance)) or (par.min() restoration: 1.34841 by 0.830715 (arcsec) # at pa 82.8827 (deg) casaline = None for line in hdr['HISTORY']: if ('restoration' in line) and ('arcsec' in line): casaline = line #assert precedence for CASA style over AIPS # this is a dubious choice if casaline is not None: bmaj = float(casaline.split()[2]) * u.arcsec bmin = float(casaline.split()[4]) * u.arcsec bpa = float(casaline.split()[8]) * u.deg return cls(major=bmaj, minor=bmin, pa=bpa) elif aipsline is not None: bmaj = float(aipsline.split()[3]) * u.deg bmin = float(aipsline.split()[5]) * u.deg bpa = float(aipsline.split()[7]) * u.deg return cls(major=bmaj, minor=bmin, pa=bpa) else: return None @classmethod def from_casa_image(cls, imagename): ''' Instantiate beam from a CASA image. ** Must be run in a CASA environment! ** Parameters ---------- imagename : str Name of CASA image. ''' try: import casac except ImportError: raise ImportError("Could not import CASA (casac) and therefore" " cannot read CASA .image files") ia.open(imagename) beam_props = ia.restoringbeam() ia.close() beam_keys = ["major", "minor", "positionangle"] if not all([True for key in beam_keys if key in beam_props]): raise ValueError("The image does not contain complete beam " "information. Check the output of " "ia.restoringbeam().") major = beam_props["major"]["value"] * \ u.Unit(beam_props["major"]["unit"]) minor = beam_props["minor"]["value"] * \ u.Unit(beam_props["minor"]["unit"]) pa = beam_props["positionangle"]["value"] * \ u.Unit(beam_props["positionangle"]["unit"]) return cls(major=major, minor=minor, pa=pa) def attach_to_header(self, header, copy=True): ''' Attach the beam information to the provided header. Parameters ---------- header : astropy.io.fits.header.Header Header to add/update beam info. copy : bool, optional Returns a copy of the inputted header with the beam information. Returns ------- copy_header : astropy.io.fits.header.Header Copy of the input header with the updated beam info when `copy=True`. ''' if copy: header = header.copy() header.update(self.to_header_keywords()) return header def __repr__(self): return "Beam: BMAJ={0} BMIN={1} BPA={2}".format(self.major.to(self.default_unit),self.minor.to(self.default_unit),self.pa.to(u.deg)) def __repr_html__(self): return "Beam: BMAJ={0} BMIN={1} BPA={2}".format(self.major.to(self.default_unit),self.minor.to(self.default_unit),self.pa.to(u.deg)) def _repr_latex_(self): return "Beam: BMAJ=${0}^{{{fmt}}}$ BMIN=${1}^{{{fmt}}}$ BPA=${2}^\\circ$".format(self.major.to(self.default_unit).value, self.minor.to(self.default_unit).value, self.pa.to(u.deg).value, fmt = unit_format[self.default_unit]) def __str__(self): return self.__repr__() def convolve(self, other): """ Convolve one beam with another. Parameters ---------- other : `Beam` The beam to convolve with Returns ------- new_beam : `Beam` The convolved Beam """ new_major, new_minor, new_pa = convolve(self, other) return Beam(major=new_major, minor=new_minor, pa=new_pa) def __mul__(self, other): return self.convolve(other) # Does division do the same? Or what? Doesn't have to be defined. def __sub__(self, other): warnings.warn("Subtraction-as-deconvolution is deprecated. " "Use division instead.", RadioBeamDeprecationWarning) return self.deconvolve(other) def __truediv__(self, other): return self.deconvolve(other) def deconvolve(self, other, failure_returns_pointlike=False): """ Deconvolve a beam from another Parameters ---------- other : `Beam` The beam to deconvolve from this beam failure_returns_pointlike : bool Option to return a pointlike beam (i.e., one with major=minor=0) if the second beam is larger than the first. Otherwise, a ValueError will be raised Returns ------- new_beam : `Beam` The convolved Beam Raises ------ failure : ValueError If the second beam is larger than the first, the default behavior is to raise an exception. This can be overridden with failure_returns_pointlike """ new_major, new_minor, new_pa = deconvolve_optimized(self.to_header_keywords(), other.to_header_keywords(), failure_returns_pointlike=failure_returns_pointlike) # Keep the units from before new_major = (new_major * u.deg).to(self.major.unit) new_minor = (new_minor * u.deg).to(self.minor.unit) new_pa = (new_pa * u.rad).to(self.pa.unit) return Beam(major=new_major, minor=new_minor, pa=new_pa) def __eq__(self, other): # Catch floating point issues atol_deg = 1e-12 * u.deg this_pa = self.pa.to(u.deg) % (180.0 * u.deg) other_pa = other.pa.to(u.deg) % (180.0 * u.deg) if self.iscircular(): equal_pa = True else: equal_pa = True if np.abs(this_pa - other_pa) < atol_deg else False equal_maj = np.abs(self.major - other.major) < atol_deg equal_min = np.abs(self.minor - other.minor) < atol_deg if equal_maj and equal_min and equal_pa: return True else: return False def __ne__(self, other): return not self.__eq__(other) # Is it astropy convention to access properties through methods? @property def sr(self): return _to_area(self.major,self.minor) @property def major(self): """ Beam FWHM Major Axis """ return self._major @property def minor(self): """ Beam FWHM Minor Axis """ return self._minor @property def pa(self): return self._pa @property def isfinite(self): return ((self.major > 0) & (self.minor > 0) & np.isfinite(self.major) & np.isfinite(self.minor) & np.isfinite(self.pa)) def iscircular(self, rtol=1e-6): frac_diff = (self.major - self.minor).to(u.deg) / self.major.to(u.deg) return frac_diff <= rtol def beam_projected_area(self, distance): """ Return the beam area in pc^2 (or equivalent) given a distance """ return self.sr*(distance**2)/u.sr def jtok_equiv(self, freq): ''' Return conversion function between Jy/beam to K at the specified frequency. The function can be used with the usual astropy.units conversion: >>> beam = Beam.from_fits_header("header.fits") # doctest: +SKIP >>> (1.0*u.Jy).to(u.K, beam.jtok_equiv(1.4*u.GHz)) # doctest: +SKIP Parameters ---------- freq : astropy.units.quantity.Quantity Frequency to calculate conversion. Returns ------- u.brightness_temperature ''' if not isinstance(freq, u.quantity.Quantity): raise TypeError("freq must be a Quantity object. " "Try 'freq*u.Hz' or another equivalent unit.") try: return u.brightness_temperature(beam_area=self.sr, frequency=freq) except TypeError: # old astropy used ordered arguments return u.brightness_temperature(self.sr, freq) def jtok(self, freq, value=1.0*u.Jy): """ Return the conversion for the given value between Jy/beam to K at the specified frequency. Unlike :meth:`jtok_equiv`, the output is the numerical value that converts the units, without any attached unit. Parameters ---------- freq : astropy.units.quantity.Quantity Frequency to calculate conversion. value : astropy.units.quantity.Quantity Value (in Jy or an equivalent unit) to convert to K. Returns ------- value : float Value converted to K. """ return value.to(u.K, self.jtok_equiv(freq)) @property def beamarea_equiv(self): return u.beam_angular_area(self.sr) def ellipse_to_plot(self, xcen, ycen, pixscale): """ Return a matplotlib ellipse for plotting Parameters ---------- xcen : int Center pixel in the x-direction. ycen : int Center pixel in the y-direction. pixscale : `~astropy.units.Quantity` Conversion from degrees to pixels. Returns ------- ~matplotlib.patches.Ellipse Ellipse patch object centered on the given pixel coordinates. """ from matplotlib.patches import Ellipse return Ellipse((xcen, ycen), width=(self.major.to(u.deg) / pixscale).to(u.dimensionless_unscaled).value, height=(self.minor.to(u.deg) / pixscale).to(u.dimensionless_unscaled).value, # PA is 90 deg offset from x-y axes by convention # (it is angle from NCP) angle=(self.pa+90*u.deg).to(u.deg).value) def as_kernel(self, pixscale, **kwargs): """ Returns an elliptical Gaussian kernel of the beam. .. warning:: This method is not aware of any misalignment between pixel and world coordinates. Parameters ---------- pixscale : `~astropy.units.Quantity` Conversion from angular to pixel size. kwargs : passed to EllipticalGaussian2DKernel """ # do something here involving matrices # need to rotate the kernel into the wcs pixel space, kinda... # at the least, need to rescale the kernel axes into pixels stddev_maj = (self.major.to(u.deg)/(pixscale.to(u.deg) * SIGMA_TO_FWHM)).decompose() stddev_min = (self.minor.to(u.deg)/(pixscale.to(u.deg) * SIGMA_TO_FWHM)).decompose() # position angle is defined as CCW from north # "angle" is conventionally defined as CCW from "west". # Therefore, add 90 degrees angle = (90*u.deg+self.pa).to(u.radian).value, return EllipticalGaussian2DKernel(stddev_maj.value, stddev_min.value, angle, **kwargs) def as_tophat_kernel(self, pixscale, **kwargs): ''' Returns an elliptical Tophat kernel of the beam. The area has been scaled to match the 2D Gaussian area: .. math:: \\begin{array}{ll} A_{\\mathrm{Gauss}} = 2\\pi\\sigma_{\\mathrm{Gauss}}^{2} A_{\\mathrm{Tophat}} = \\pi\\sigma_{\\mathrm{Tophat}}^{2} \\sigma_{\\mathrm{Tophat}} = \\sqrt{2}\\sigma_{\\mathrm{Gauss}} \\end{array} .. warning:: This method is not aware of any misalignment between pixel and world coordinates. Parameters ---------- pixscale : float deg -> pixels **kwargs : passed to EllipticalTophat2DKernel ''' # Based on Gaussian to Tophat area conversion # A_gaussian = 2 * pi * sigma^2 / (sqrt(8*log(2))^2 # A_tophat = pi * r^2 # pi r^2 = 2 * pi * sigma^2 / (sqrt(8*log(2))^2 # r = sqrt(2)/sqrt(8*log(2)) * sigma gauss_to_top = np.sqrt(2) maj_eff = gauss_to_top * self.major.to(u.deg) / \ (pixscale * SIGMA_TO_FWHM) min_eff = gauss_to_top * self.minor.to(u.deg) / \ (pixscale * SIGMA_TO_FWHM) # position angle is defined as CCW from north # "angle" is conventionally defined as CCW from "west". # Therefore, add 90 degrees angle = (90*u.deg+self.pa).to(u.radian).value, return EllipticalTophat2DKernel(maj_eff.value, min_eff.value, angle, **kwargs) def to_header_keywords(self): return {'BMAJ': self.major.to(u.deg).value, 'BMIN': self.minor.to(u.deg).value, 'BPA': self.pa.to(u.deg).value, } # Beam.__doc__ = Beam.__doc__ + Beam.__new__.__doc__ def mywcs_to_platescale(mywcs): pix_area = wcs.utils.proj_plane_pixel_area(mywcs) return pix_area**0.5 class EllipticalGaussian2DKernel(Kernel2D): """ 2D Elliptical Gaussian filter kernel. The Gaussian filter is a filter with great smoothing properties. It is isotropic and does not produce artifacts. Parameters ---------- stddev_maj : float Standard deviation of the Gaussian kernel in direction 1 stddev_min : float Standard deviation of the Gaussian kernel in direction 1 position_angle : float Position angle of the elliptical gaussian x_size : odd int, optional Size in x direction of the kernel array. Default = support_scaling * stddev. y_size : odd int, optional Size in y direction of the kernel array. Default = support_scaling * stddev. support_scaling : int The amount to scale the stddev to determine the size of the kernel mode : str, optional One of the following discretization modes: * 'center' (default) Discretize model by taking the value at the center of the bin. * 'linear_interp' Discretize model by performing a bilinear interpolation between the values at the corners of the bin. * 'oversample' Discretize model by taking the average on an oversampled grid. * 'integrate' Discretize model by integrating the model over the bin. factor : number, optional Factor of oversampling. Default factor = 10. See Also -------- Box2DKernel, Tophat2DKernel, MexicanHat2DKernel, Ring2DKernel, TrapezoidDisk2DKernel, AiryDisk2DKernel, Gaussian2DKernel, EllipticalTophat2DKernel Examples -------- Kernel response: .. plot:: :include-source: import matplotlib.pyplot as plt from radio_beam import EllipticalGaussian2DKernel gaussian_2D_kernel = EllipticalGaussian2DKernel(10, 5, np.pi/4) plt.imshow(gaussian_2D_kernel, interpolation='none', origin='lower') plt.xlabel('x [pixels]') plt.ylabel('y [pixels]') plt.colorbar() plt.show() """ _separable = True _is_bool = False def __init__(self, stddev_maj, stddev_min, position_angle, support_scaling=8, **kwargs): self._model = Gaussian2D(1. / (2 * np.pi * stddev_maj * stddev_min), 0, 0, x_stddev=stddev_maj, y_stddev=stddev_min, theta=position_angle) try: from astropy.modeling.utils import ellipse_extent except ImportError: raise NotImplementedError("EllipticalGaussian2DKernel requires" " astropy 1.1b1 or greater.") max_extent = \ np.max(ellipse_extent(stddev_maj, stddev_min, position_angle)) self._default_size = \ _round_up_to_odd_integer(support_scaling * 2 * max_extent) super(EllipticalGaussian2DKernel, self).__init__(**kwargs) self._truncation = np.abs(1. - 1 / self._array.sum()) class EllipticalTophat2DKernel(Kernel2D): """ 2D Elliptical Tophat filter kernel. The Tophat filter can produce artifacts when applied repeatedly on the same data. Parameters ---------- stddev_maj : float Standard deviation of the Gaussian kernel in direction 1 stddev_min : float Standard deviation of the Gaussian kernel in direction 1 position_angle : float Position angle of the elliptical gaussian x_size : odd int, optional Size in x direction of the kernel array. Default = support_scaling * stddev. y_size : odd int, optional Size in y direction of the kernel array. Default = support_scaling * stddev. support_scaling : int The amount to scale the stddev to determine the size of the kernel mode : str, optional One of the following discretization modes: * 'center' (default) Discretize model by taking the value at the center of the bin. * 'linear_interp' Discretize model by performing a bilinear interpolation between the values at the corners of the bin. * 'oversample' Discretize model by taking the average on an oversampled grid. * 'integrate' Discretize model by integrating the model over the bin. factor : number, optional Factor of oversampling. Default factor = 10. See Also -------- Box2DKernel, Tophat2DKernel, MexicanHat2DKernel, Ring2DKernel, TrapezoidDisk2DKernel, AiryDisk2DKernel, Gaussian2DKernel, EllipticalGaussian2DKernel Examples -------- Kernel response: .. plot:: :include-source: import matplotlib.pyplot as plt from radio_beam import EllipticalTophat2DKernel tophat_2D_kernel = EllipticalTophat2DKernel(10, 5, np.pi/4) plt.imshow(tophat_2D_kernel, interpolation='none', origin='lower') plt.xlabel('x [pixels]') plt.ylabel('y [pixels]') plt.colorbar() plt.show() """ _is_bool = True def __init__(self, stddev_maj, stddev_min, position_angle, support_scaling=1, **kwargs): self._model = Ellipse2D(1. / (np.pi * stddev_maj * stddev_min), 0, 0, stddev_maj, stddev_min, position_angle) try: from astropy.modeling.utils import ellipse_extent except ImportError: raise NotImplementedError("EllipticalTophat2DKernel requires" " astropy 1.1b1 or greater.") max_extent = \ np.max(ellipse_extent(stddev_maj, stddev_min, position_angle)) self._default_size = \ _round_up_to_odd_integer(support_scaling * 2 * max_extent) super(EllipticalTophat2DKernel, self).__init__(**kwargs) self._truncation = 0 radio-beam-0.3.3/radio_beam/commonbeam.py0000644000175100001710000004775214025155256021111 0ustar runnerdocker00000000000000 import numpy as np import astropy.units as u try: from scipy import optimize as opt from scipy.spatial import ConvexHull HAS_SCIPY = True except ImportError: HAS_SCIPY = False from .beam import Beam from .utils import BeamError, transform_ellipse, deconvolve_optimized __all__ = ['commonbeam', 'common_2beams', 'getMinVolEllipse', 'common_manybeams_mve'] def commonbeam(beams, method='pts', **method_kwargs): ''' Use analytic method if there are only two beams. Otherwise use constrained optimization to find the common beam. ''' if beams.size == 1: return beams[0] elif fits_in_largest(beams): return beams.largest_beam() else: if beams.size == 2: try: return common_2beams(beams) # Sometimes this method can fail. Use the many beam solution in # this case except (ValueError, BeamError): pass if method == 'pts': return common_manybeams_mve(beams, **method_kwargs) elif method == 'opt': return common_manybeams_opt(beams, **method_kwargs) else: raise ValueError("method must be 'pts' or 'opt'.") def common_2beams(beams, check_deconvolution=True): ''' Find a common beam from a `Beams` object with 2 beams. This function is based on the CASA implementation `ia.commonbeam`. Note that the solution is only valid for 2 beams. Parameters ---------- beams : `~radio_beam.Beams` Beams object with 2 beams. Returns ------- common_beam : `~radio_beam.Beam` The smallest common beam in the set of beams. ''' # This code is based on the implementation in CASA: # https://open-bitbucket.nrao.edu/projects/CASA/repos/casa/browse/code/imageanalysis/ImageAnalysis/CasaImageBeamSet.cc if beams.size != 2: raise BeamError("This method is only valid for two beams.") if (~beams.isfinite).all(): raise BeamError("All beams in the object are invalid.") large_beam = beams.largest_beam() large_major = large_beam.major.to(u.arcsec) large_minor = large_beam.minor.to(u.arcsec) if beams.argmax() == 0: small_beam = beams[1] else: small_beam = beams[0] small_major = small_beam.major.to(u.arcsec) small_minor = small_beam.minor.to(u.arcsec) # Case where they're already equal if small_beam == large_beam: return large_beam deconv_beam = large_beam.deconvolve(small_beam, failure_returns_pointlike=True) # Larger beam can be deconvolved. It is already the smallest common beam if deconv_beam.isfinite: return large_beam # If the smaller beam is a circle, the minor axis is the circle radius if small_beam.iscircular(): common_beam = Beam(large_beam.major, small_beam.major, large_beam.pa) return common_beam # Wrap angle about 0 to pi. pa_diff = ((small_beam.pa.to(u.rad).value - large_beam.pa.to(u.rad).value + np.pi / 2. + np.pi) % np.pi - np.pi / 2.) * u.rad # If the difference is pi / 2, the larger major is set to the # new major and the minor is the other major. if np.isclose(np.abs(pa_diff).value, np.pi / 2.): larger_major = large_beam.major >= small_beam.major major = large_major if larger_major else small_major minor = small_major if larger_major else small_major pa = large_beam.pa if larger_major else small_beam.pa conv_beam = Beam(major=major, minor=minor, pa=pa) return conv_beam else: # Transform to coordinates where large_beam is circular major_comb = np.sqrt(large_major * small_major) p = major_comb / large_major q = major_comb / large_minor # Transform beam into the same coordinates, and rotate so its # major axis is along the x axis. trans_major_sc, trans_minor_sc, trans_pa_sc = \ transform_ellipse(small_major, small_minor, pa_diff, p, q) # The transformed minor axis is major_comb, as defined in CASA trans_minor_sc = major_comb # Return beam to the original coordinates, still rotated with # the major along the x axis trans_major_unsc, trans_minor_unsc, trans_pa_unsc = \ transform_ellipse(trans_major_sc, trans_minor_sc, trans_pa_sc, 1 / p, 1 / q) # Lastly, rotate the PA to the enclosing ellipse trans_major = trans_major_unsc.to(u.arcsec) trans_minor = trans_minor_unsc.to(u.arcsec) trans_pa = trans_pa_unsc + large_beam.pa # The minor axis becomes an issue when checking against the smaller # beam from deconvolution. Adding a tiny fraction makes the deconvolved # beam JUST larger than zero (~1e-7). epsilon = 100 * np.finfo(trans_major.dtype).eps * trans_major.unit trans_beam = Beam(major=trans_major + epsilon, minor=trans_minor + epsilon, pa=trans_pa) if check_deconvolution: # Ensure this beam can now be deconvolved deconv_large_beam = \ trans_beam.deconvolve(large_beam, failure_returns_pointlike=True) deconv_prob_beam = \ trans_beam.deconvolve(small_beam, failure_returns_pointlike=True) if not deconv_large_beam.isfinite or not deconv_prob_beam.isfinite: raise BeamError("Failed to find common beam that both beams can " "be deconvolved by.") # Taken from CASA implementation, but by adding epsilon, this shouldn't # be needed # Scale the enclosing ellipse by a small factor until it does. # can_deconv = False # num = 0 # while not can_deconv: # deconv_large_beam = \ # trans_beam.deconvolve(large_beam, # failure_returns_pointlike=True) # deconv_prob_beam = \ # trans_beam.deconvolve(small_beam, # failure_returns_pointlike=True) # if (not deconv_large_beam.isfinite or not deconv_prob_beam.isfinite): # scale_factor = 1.001 # trans_beam = Beam(major=trans_major * scale_factor, # minor=trans_minor * scale_factor, # pa=trans_pa) # else: # can_deconv = True # if num == 10: # break # num += 1 common_beam = trans_beam return common_beam def boundingcircle(bmaj, bmin, bpa): thisone = np.argmax(bmaj) # PA really shouldn't matter here. But the minimization performed better # in some cases with a non-zero PA. Presumably this is b/c the PA of the # common beam is affected more by the beam with the largest major axis. return bmaj[thisone], bmaj[thisone], bpa[thisone] def PtoA(bmaj, bmin, bpa): ''' Express the ellipse parameters into `center-form `_. ''' A = np.zeros((2, 2)) A[0, 0] = np.cos(bpa)**2 / bmaj**2 + np.sin(bpa)**2 / bmin**2 A[1, 0] = np.cos(bpa) * np.sin(bpa) * (1 / bmaj**2 - 1 / bmin**2) A[0, 1] = A[1, 0] A[1, 1] = np.sin(bpa)**2 / bmaj**2 + np.cos(bpa)**2 / bmin**2 return A def BinsideA(B, A): try: np.linalg.cholesky(B - A) return True except np.linalg.LinAlgError: return False def myobjective_regularized(p, bmajvec, bminvec, bpavec): # Force bmaj > bmin if p[0] < p[1]: return 1e30 # We can safely assume the common major axis is at most the # largest major axis in the set if (p[0] <= bmajvec).any(): return 1e30 A = PtoA(*p) test = np.zeros_like(bmajvec) for idx, (bmx, bmn, bp) in enumerate(zip(bmajvec, bminvec, bpavec)): test[idx] = BinsideA(PtoA(bmx, bmn, bp), A) obj = 1 / np.linalg.det(A) if np.all(test): return obj else: return obj * 1e30 def common_manybeams_opt(beams, p0=None, opt_method='Nelder-Mead', optdict={'maxiter': 5000, 'ftol': 1e-14, 'maxfev': 5000}, verbose=False, brute=False, brute_steps=40): ''' Optimize the common beam solution by maximizing the determinant of the common beam. ..note:: This method is experimental and requires further testing. Parameters ---------- beams : `~radio_beam.Beams` Beams object. p0 : tuple, optional Initial guess parameters (`major, minor, pa`). opt_method : str, optional Optimization method to use. See `~scipy.optimize.minimize`. The default of Nelder-Mead is the only method we have had some success with. optdict : dict, optional Dictionary parameters passed to `~scipy.optimize.minimize`. verbose : bool, optional Print the full output from `~scipy.optimize.minimize`. brute : bool, optional Use `~scipy.optimize.brute` to find the optimal solution. brute_steps : int, optional Number of positions to sample in each parameter (3). Returns ------- com_beam : `~radio_beam.Beam` Common beam. ''' raise NotImplementedError("This method is not fully tested. Remove this " "line for testing purposes.") if not HAS_SCIPY: raise ImportError("common_manybeams_opt requires scipy.optimize.") bmaj = beams.major.value bmin = beams.minor.value bpa = beams.pa.to(u.rad).value if p0 is None: p0 = boundingcircle(bmaj, bmin, bpa) # It seems to help to make the initial guess slightly larger p0 = (1.1 * p0[0], 1.1 * p0[1], p0[2]) if brute: maj_range = [beams.major.max(), 1.5 * beams.major.max()] maj_step = (maj_range[1] - maj_range[0]) / brute_steps min_range = [beams.minor.min(), 1.5 * beams.major.max()] min_step = (min_range[1] - min_range[0]) / brute_steps rranges = (slice(maj_range[0], maj_range[1], maj_step), slice(min_range[0], min_range[1], min_step), slice(0, 179.9, 180. / brute_steps)) result = opt.brute(myobjective_regularized, rranges, args=(bmaj, bmin, bpa), full_output=True, finish=opt.fmin) params = result[0] else: result = opt.minimize(myobjective_regularized, p0, method=opt_method, args=(bmaj, bmin, bpa), options=optdict, tol=1e-14) params = result.x if verbose: print(result.viewitems()) if not result.success: raise Warning("Optimization failed") com_beam = Beam(params[0] * beams.major.unit, params[1] * beams.major.unit, (params[2] % np.pi) * u.rad) # Test if it deconvolves all if not fits_in_largest(beams, com_beam): raise BeamError("Could not find common beam to deconvolve all beams.") return com_beam def fits_in_largest(beams, large_beam=None): ''' Test if all beams can be deconvolved by the largest beam ''' if large_beam is None: large_beam = beams.largest_beam() large_hdr_keywords = large_beam.to_header_keywords() majors = beams.major.to(u.deg).value minors = beams.minor.to(u.deg).value pas = beams.pa.to(u.deg).value # Catch differences below << 1 microarsec = 2.8e10 # This is the same limit used for checking equal beams in Beam.__eq__ atol_limit = 1e-12 for major, minor, pa in zip(majors, minors, pas): equal = abs(large_hdr_keywords['BMAJ'] - major) < atol_limit equal = equal and (abs(large_hdr_keywords['BMIN'] - minor) < atol_limit) # Check if the beam is circular # This checks for fractional changes below 1e-6 between the major and minor. # Same limit used in Beam.__eq__ iscircular = (major - minor) / minor < 1e-6 # position angle only matters if the beam is asymmetric if not iscircular: equal = equal and (abs(((large_hdr_keywords['BPA'] % np.pi) - (pa % np.pi))) < atol_limit) if equal: continue out = deconvolve_optimized(large_hdr_keywords, {'BMAJ': major, 'BMIN': minor, 'BPA': pa}, failure_returns_pointlike=True) if np.any([ax == 0. for ax in out[:2]]): return False return True def getMinVolEllipse(P, tolerance=1e-5, maxiter=1e5): """ Use the Khachiyan Algorithm to compute that minimum volume ellipsoid. For the purposes of finding a common beam, there is an added check that requires the center to be within the tolerance range. Adapted code from: https://github.com/minillinim/ellipsoid/blob/master/ellipsoid.py That implementation relies on the original work by Nima Moshtagh: http://www.mathworks.com/matlabcentral/fileexchange/9542 and an alternate python version from: http://cctbx.sourceforge.net/current/python/scitbx.math.minimum_covering_ellipsoid.html Parameters ---------- P : `~numpy.ndarray` Points to compute solution. tolerance : float, optional Allowed error range in the Khachiyan Algorithm. Decreasing the tolerance by an order of magnitude requires an order of magnitude more iterations to converge. maxiter : int, optional Maximum iterations. Returns ------- center : `~numpy.ndarray` Center point of the ellipse. Is required to be smaller than the tolerance. radii : `~numpy.ndarray` Radii of the ellipse. rotation : `~numpy.ndarray` Rotation matrix of the ellipse. """ N, d = P.shape d = float(d) # Q will be our working array Q = np.vstack([np.copy(P.T), np.ones(N)]) QT = Q.T # initializations err = 1.0 u = np.ones(N) / N # Khachiyan Algorithm i = 0 while err > tolerance: V = np.dot(Q, np.dot(np.diag(u), QT)) # M the diagonal vector of an NxN matrix M = np.diag(np.dot(QT, np.dot(np.linalg.inv(V), Q))) j = np.argmax(M) maximum = M[j] step_size = (maximum - d - 1.0) / ((d + 1.0) * (maximum - 1.0)) new_u = (1.0 - step_size) * u err = np.linalg.norm(new_u - u) if err <= tolerance: break new_u[j] += step_size u = new_u i += 1 if i == maxiter: raise ValueError("Reached maximum iterations without converging." " Try increasing the tolerance.") # center of the ellipse center = np.atleast_2d(np.dot(P.T, u)) # For our purposes, the centre should always be very small center_square = np.outer(center, center) if not (center_square < tolerance**2).any(): raise ValueError("The solved centre ({0}) is larger than the tolerance" " ({1}). Check the input data.".format(center, tolerance)) # the A matrix for the ellipse A = np.linalg.inv(np.dot(P.T, np.dot(np.diag(u), P)) - center_square) / d # ellip_vals = np.dot(P - center, np.dot(A, (P - center).T)) # assert (ellip_vals <= 1. + tolerance).all() # Get the values we'd like to return U, s, rotation = np.linalg.svd(A) radii = 1.0 / np.sqrt(s) radii *= 1. + tolerance return center, radii, rotation def ellipse_edges(beam, npts=300, epsilon=1e-3): ''' Return the edge points of the beam. Parameters ---------- beam : `~radio_beam.Beam` Beam object. npts : int, optional Number of samples. epsilon : float Increase the radii of the ellipse by 1 + epsilon. This is to ensure that `getMinVolEllipse` returns a marginally deconvolvable beam to within the error tolerance. Returns ------- pts : `~numpy.ndarray` The x, y coordinates of the ellipse edge. ''' bpa = beam.pa.to(u.rad).value major = beam.major.to(u.deg).value * (1. + epsilon) minor = beam.minor.to(u.deg).value * (1. + epsilon) phi = np.linspace(0, 2 * np.pi, npts) x = major * np.cos(phi) y = minor * np.sin(phi) xr = x * np.cos(bpa) - y * np.sin(bpa) yr = x * np.sin(bpa) + y * np.cos(bpa) pts = np.vstack([xr, yr]) return pts def common_manybeams_mve(beams, tolerance=1e-4, nsamps=200, epsilon=5e-4, auto_increase_epsilon=True, max_epsilon=1e-3, max_iter=10): ''' Calculate a common beam size using the Khachiyan Algorithm to find the minimum enclosing ellipse from all beam edges. Parameters ---------- beams : `~radio_beam.Beams` Beams object. tolerance : float, optional Allowed error range in the Khachiyan Algorithm. Decreasing the tolerance by an order of magnitude requires an order of magnitude more iterations to converge. nsamps : int, optional Number of edge points to sample from each beam. epsilon : float, optional Increase the radii of each beam by a factor of 1 + epsilon to ensure the common beam can marginally be deconvolved for all beams. Small deviations result from the finite sampling of points and the choice of the tolerance. auto_increase_epsilon : bool, optional Re-run the algorithm when the solution cannot quite be deconvolved from from all the beams. When `True`, epsilon is slightly increased with each iteration until the common beam can be deconvolved from all beams. Default is `True`. max_epsilon : float, optional Maximum epsilon value that is acceptable. Reached with `max_iter`. Default is `1e-3`. max_iter : int, optional Maximum number of times to increase epsilon to try finding a valid common beam solution. Returns ------- com_beam : `~radio_beam.Beam` The common beam for all beams in the set. ''' if not HAS_SCIPY: raise ImportError("common_manybeams_mve requires scipy.optimize.") step = 1 while True: pts = [] for beam in beams: pts.append(ellipse_edges(beam, nsamps, epsilon=epsilon)) all_pts = np.hstack(pts).T # Now find the outer edges of the convex hull. hull = ConvexHull(all_pts) edge_pts = all_pts[hull.vertices] center, radii, rotation = \ getMinVolEllipse(edge_pts, tolerance=tolerance) # The rotation matrix is coming out as: # ((sin theta, cos theta) # (cos theta, - sin theta)) pa = np.arctan2(- rotation[0, 0], rotation[1, 0]) * u.rad if pa.value == -np.pi or pa.value == np.pi: pa = 0.0 * u.rad com_beam = Beam(major=radii.max() * u.deg, minor=radii.min() * u.deg, pa=pa) # If common beam is just slightly smaller than one of the beams, # we increase epsilon to encourage a solution marginally larger # so all beams can be convolved. if auto_increase_epsilon: if not fits_in_largest(beams, com_beam): # Increase epsilon and run again epsilon += (step + 1) * (max_epsilon - epsilon) / max_iter step += 1 if step == max_iter + 1: raise BeamError("Could not increase epsilon to find" " common beam.") continue else: break else: break if not fits_in_largest(beams, com_beam): raise BeamError("Could not find common beam to deconvolve all beams.") return com_beam radio-beam-0.3.3/radio_beam/conftest.py0000644000175100001710000000426314025155256020607 0ustar runnerdocker00000000000000# this contains imports plugins that configure py.test for astropy tests. # by importing them here in conftest.py they are discoverable by py.test # no matter how it is invoked within the source tree. from __future__ import print_function, absolute_import, division import os from distutils.version import LooseVersion # Import casatools and casatasks here if available as they can otherwise # cause a segfault if imported later on during tests. try: import casatools import casatasks except ImportError: pass from astropy.version import version as astropy_version if astropy_version < '3.0': # With older versions of Astropy, we actually need to import the pytest # plugins themselves in order to make them discoverable by pytest. from astropy.tests.pytest_plugins import * else: # As of Astropy 3.0, the pytest plugins provided by Astropy are # automatically made available when Astropy is installed. This means it's # not necessary to import them here, but we still need to import global # variables that are used for configuration. from astropy.tests.plugins.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS from astropy.tests.helper import enable_deprecations_as_exceptions ## Uncomment the following line to treat all DeprecationWarnings as ## exceptions # enable_deprecations_as_exceptions() ## Uncomment and customize the following lines to add/remove entries ## from the list of packages for which version numbers are displayed ## when running the tests # try: # PYTEST_HEADER_MODULES['Astropy'] = 'astropy' # PYTEST_HEADER_MODULES['scikit-image'] = 'skimage' # del PYTEST_HEADER_MODULES['h5py'] # except NameError: # needed to support Astropy < 1.0 # pass ## Uncomment the following lines to display the version number of the ## package rather than the version number of Astropy in the top line when ## running the tests. # import os # ## This is to figure out the affiliated package version, rather than ## using Astropy's # from . import version # # try: # packagename = os.path.basename(os.path.dirname(__file__)) # TESTED_VERSIONS[packagename] = version.version # except NameError: # Needed to support Astropy <= 1.0.0 # pass radio-beam-0.3.3/radio_beam/multiple_beams.py0000644000175100001710000003713714025155256021772 0ustar runnerdocker00000000000000 from astropy import units as u from astropy.io import fits from astropy import constants from astropy import wcs import numpy as np import warnings from .beam import Beam, _to_area, SIGMA_TO_FWHM from .commonbeam import commonbeam from .utils import InvalidBeamOperationError class Beams(u.Quantity): """ An object to handle a set of radio beams for a data cube. """ def __new__(cls, major=None, minor=None, pa=None, areas=None, default_unit=u.arcsec, meta=None, beams=None): """ Create a new set of Gaussian beams Parameters ---------- major : :class:`~astropy.units.Quantity` with angular equivalency The FWHM major axes minor : :class:`~astropy.units.Quantity` with angular equivalency The FWHM minor axes pa : :class:`~astropy.units.Quantity` with angular equivalency The beam position angles area : :class:`~astropy.units.Quantity` with steradian equivalency The area of the beams. This is an alternative to specifying the major/minor/PA, and will create those values assuming a circular Gaussian beam. default_unit : :class:`~astropy.units.Unit` The unit to impose on major, minor if they are specified as floats beams : List of :class:`~radio_beam.Beam` objects List of individual `Beam` objects. The resulting `Beams` object will have major and minor axes in degrees. """ # improve to some kwargs magic later # error checking if beams is not None: major = [beam.major.to(u.deg).value for beam in beams] * u.deg minor = [beam.minor.to(u.deg).value for beam in beams] * u.deg pa = [beam.pa.to(u.deg).value for beam in beams] * u.deg # ... given an area make a round beam assuming it is Gaussian if areas is not None: rad = np.sqrt(areas / (2 * np.pi)) * u.deg major = rad * SIGMA_TO_FWHM minor = rad * SIGMA_TO_FWHM pa = np.zeros_like(areas) * u.deg # give specified values priority if major is not None: if u.deg.is_equivalent(major.unit): pass else: warnings.warn("Assuming major axes has been specified in degrees") major = major * u.deg if minor is not None: if u.deg.is_equivalent(minor.unit): pass else: warnings.warn("Assuming minor axes has been specified in degrees") minor = minor * u.deg if pa is not None: if len(pa) != len(major): raise ValueError("Number of position angles must match number of major axis lengths") if u.deg.is_equivalent(pa.unit): pass else: warnings.warn("Assuming position angles has been specified in degrees") pa = pa * u.deg else: pa = np.zeros_like(major.value) * u.deg # some sensible defaults if minor is None: minor = major elif len(minor) != len(major): raise ValueError("Minor and major axes must have same number of values") if np.any(minor > major): raise ValueError("Minor axis greater than major axis.") self = super(Beams, cls).__new__(cls, value=_to_area(major, minor).value, unit=u.sr) self.major = major self.minor = minor self.pa = pa self.default_unit = default_unit if meta is None: self.meta = [{}]*len(self) else: self.meta = meta return self @property def meta(self): return self._meta @meta.setter def meta(self, value): if len(value) == len(self): self._meta = value else: raise TypeError("metadata must be a list of dictionaries") def __len__(self): return len(self.major) @property def isfinite(self): return ((self.major > 0) & (self.minor > 0) & np.isfinite(self.major) & np.isfinite(self.minor) & np.isfinite(self.pa)) def __getslice__(self, start, stop, increment=None): return self.__getitem__(slice(start, stop, increment)) def __getitem__(self, view): if isinstance(view, (int, np.int64)): return Beam(major=self.major[view], minor=self.minor[view], pa=self.pa[view], meta=self.meta[view]) elif isinstance(view, slice): return Beams(major=self.major[view], minor=self.minor[view], pa=self.pa[view], meta=self.meta[view]) elif isinstance(view, np.ndarray): if view.dtype.name != 'bool': raise ValueError("If using an array to index beams, it must " "be a boolean array.") return Beams(major=self.major[view], minor=self.minor[view], pa=self.pa[view], meta=[x for ii,x in zip(view, self.meta) if ii]) else: raise ValueError("Invalid slice") def __array_finalize__(self, obj): # If our unit is not set and obj has a valid one, use it. if self._unit is None: unit = getattr(obj, '_unit', None) if unit is not None: self._set_unit(unit) if isinstance(obj, Beams): # Multiplication and division should change the area, # but not the PA or major/minor ratio self.major = obj.major self.minor = obj.minor self.pa = obj.pa self.meta = obj.meta # Copy info if the original had `info` defined. Because of the way the # DataInfo works, `'info' in obj.__dict__` is False until the # `info` attribute is accessed or set. Note that `obj` can be an # ndarray which doesn't have a `__dict__`. if 'info' in getattr(obj, '__dict__', ()): self.info = obj.info @property def sr(self): return _to_area(self.major, self.minor) @classmethod def from_fits_bintable(cls, bintable): """ Instantiate a Beams list from a bintable from a CASA-produced image HDU. Parameters ---------- bintable : fits.BinTableHDU The table data containing the beam information Returns ------- beams : Beams A new Beams object """ major = u.Quantity(bintable.data['BMAJ'], u.arcsec) minor = u.Quantity(bintable.data['BMIN'], u.arcsec) pa = u.Quantity(bintable.data['BPA'], u.deg) meta = [{key: row[key] for key in bintable.columns.names if key not in ('BMAJ', 'BPA', 'BMIN')} for row in bintable.data] return cls(major=major, minor=minor, pa=pa, meta=meta) @classmethod def from_casa_image(cls, imagename): ''' Instantiate beams from a CASA image. Cannot currently handle beams for different polarizations. ** Must be run in a CASA environment! ** Parameters ---------- imagename : str Name of CASA image. ''' try: from casatools import image as iatool ia = iatool() except ImportError: raise ImportError("Could not import CASA and therefore" " cannot read CASA .image files") ia.open(imagename) beam_props = ia.restoringbeam() ia.close() nchans = beam_props['nChannels'] # Assuming there is always a 0th channel... maj_unit = u.Unit(beam_props['beams']['*0']['*0']['major']['unit']) min_unit = u.Unit(beam_props['beams']['*0']['*0']['minor']['unit']) pa_unit = u.Unit(beam_props['beams']['*0']['*0']['positionangle']['unit']) major = np.empty((nchans)) * maj_unit minor = np.empty((nchans)) * min_unit pa = np.empty((nchans)) * pa_unit for chan in range(nchans): chan_name = '*{}'.format(chan) chanbeam_props = beam_props['beams'][chan_name]['*0'] # Can CASA have mixes of units between channels? Let's test just # in case assert maj_unit == u.Unit(chanbeam_props['major']['unit']) assert min_unit == u.Unit(chanbeam_props['minor']['unit']) assert pa_unit == u.Unit(chanbeam_props['positionangle']['unit']) major[chan] = chanbeam_props['major']['value'] * maj_unit minor[chan] = chanbeam_props['minor']['value'] * min_unit pa[chan] = chanbeam_props['positionangle']['value'] * pa_unit return cls(major=major, minor=minor, pa=pa) def average_beam(self, includemask=None, raise_for_nan=True): """ Average the beam major, minor, and PA attributes. This is usually a dumb thing to do! """ warnings.warn("Do not use the average beam for convolution! Use the" " smallest common beam from `Beams.common_beam()`.") from astropy.stats import circmean if includemask is None: includemask = self.isfinite else: includemask = np.logical_and(includemask, self.isfinite) new_beam = Beam(major=self.major[includemask].mean(), minor=self.minor[includemask].mean(), pa=circmean(self.pa[includemask], weights=(self.major / self.minor)[includemask])) if raise_for_nan and np.any(np.isnan(new_beam)): raise ValueError("NaNs after averaging. This is a bug.") return new_beam def largest_beam(self, includemask=None): """ Returns the largest beam (by area) in a list of beams. """ if includemask is None: includemask = self.isfinite else: includemask = np.logical_and(includemask, self.isfinite) largest_idx = (self.major * self.minor)[includemask].argmax() new_beam = Beam(major=self.major[includemask][largest_idx], minor=self.minor[includemask][largest_idx], pa=self.pa[includemask][largest_idx]) return new_beam def smallest_beam(self, includemask=None): """ Returns the smallest beam (by area) in a list of beams. """ if includemask is None: includemask = self.isfinite else: includemask = np.logical_and(includemask, self.isfinite) largest_idx = (self.major * self.minor)[includemask].argmin() new_beam = Beam(major=self.major[includemask][largest_idx], minor=self.minor[includemask][largest_idx], pa=self.pa[includemask][largest_idx]) return new_beam def extrema_beams(self, includemask=None): return [self.smallest_beam(includemask), self.largest_beam(includemask)] def common_beam(self, includemask=None, method='pts', **kwargs): ''' Return the smallest common beam size. For set of two beams, the solution is solved analytically. All larger sets solve for the minimum volume ellipse using the `Khachiyan Algorithm `_, where the convex hull of the set of ellipse edges is used to find the boundaries of the set. Since the minimum ellipse method is approximate, some solutions for the common beam will be slightly underestimated and the solution cannot be deconvolved from the whole set of beams. To overcome this issue, a small `epsilon` correction factor is added to the ellipse edges to encourage a valid common beam solution. Since `epsilon` is added to all sides, this correction will at most increase the common beam size by :math:`2\times(1+\epsilon)`. The default values of `epsilon` is :math:`5\times10^{-4}`, so this will have a very small effect on the size of the common beam. In some cases, `epsilon` must be increased to find a valid common beam solution. The algorithm does this by default (set by `auto_increase_epsilon=True`), and will incrementally increase `epsilon` until the common beam can be deconvolved from all beams, or until either(1) `max_iter` is reached (default is 10) or (2) `max_epsilon` is reached (default is 1e-3). In practice, we find these settings work well for different ALMA or VLA data, but these `kwargs` may need to be changed for discrepant cases. Parameters ---------- includemask : `~numpy.ndarray`, optional Boolean mask. method : {'pts'}, optional Many beam method. Only `pts` is currently available. kwargs : Passed to `~radio_beam.commonbeam`. ''' return commonbeam(self if includemask is None else self[includemask], method=method, **kwargs) def __iter__(self): for i in range(len(self)): yield self[i] def __mul__(self, other): # Other must be a single beam. Assume multiplying is convolving # as set of beams with a given beam if not isinstance(other, Beam): raise InvalidBeamOperationError("Multiplication is defined as a " "convolution of the set of beams " "with a given beam. Must be " "multiplied with a Beam object.") return Beams(beams=[beam * other for beam in self]) def __truediv__(self, other): # Other must be a single beam. Assume dividing is deconvolving # as set of beams with a given beam if not isinstance(other, Beam): raise InvalidBeamOperationError("Division is defined as a " "deconvolution of the set of beams" " with a given beam. Must be " "divided by a Beam object.") return Beams(beams=[beam / other for beam in self]) def __add__(self, other): raise InvalidBeamOperationError("Addition of a set of Beams " "is not defined.") def __sub__(self, other): raise InvalidBeamOperationError("Addition of a set of Beams " "is not defined.") def __eq__(self, other): # other should be a single beam, or a another Beams object if isinstance(other, Beam): return np.array([beam == other for beam in self]) elif isinstance(other, Beams): # These should have the same size. if not self.size == other.size: raise InvalidBeamOperationError("Beams objects must have the " "same shape to test " "equality.") return np.all([beam == other_beam for beam, other_beam in zip(self, other)]) else: raise InvalidBeamOperationError("Must test equality with a Beam" " or Beams object.") def __ne__(self, other): eq_out = self.__eq__(other) # If other is a Beam, will get array back if isinstance(eq_out, np.ndarray): return ~eq_out # If other is a Beams, will get boolean back else: return not eq_out radio-beam-0.3.3/radio_beam/tests/0000755000175100001710000000000014025155267017547 5ustar runnerdocker00000000000000radio-beam-0.3.3/radio_beam/tests/__init__.py0000644000175100001710000000017114025155256021655 0ustar runnerdocker00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This packages contains affiliated package tests. """ radio-beam-0.3.3/radio_beam/tests/data/0000755000175100001710000000000014025155267020460 5ustar runnerdocker00000000000000radio-beam-0.3.3/radio_beam/tests/data/NGC0925.bima.mmom0.fits.gz0000644000175100001710000000565314025155256024657 0ustar runnerdocker00000000000000‹7ºÌVNGC0925.bima.mmom0.fitsíks¢LÇ¿J¿KfÇKCƒ\ž‡­BC3ÞVIvò’(1Ì X€I|>ýv£à 庻µ›î©Š¨øóôésο˜tû£ž€šš`ê:¯®·ðAà‚Û®>~`83Û%}€vWužá‰˜gxž±3#0@°^šÉ”]¨?»p†1ÏY-^L¸¯ÙZ˜Žo¹ŽŸl_Oü8ë3óµa{2V#j´D"IÏißa1ÞÖ‰yˆ$FB<a1ž®õ´Ig8RÀÕ½ªwÆšöã*?e×:7ZOg6öÕÙ†6¶§Õ!—ŸÜãpÙòÓX5Ü_¶ïIí1ÕGGi!ïj¬ÖëõIwPÞìÖ¾ªüÇÆþk Uø­.þBÿ…¼+×ùÅöA¸ß_¾ ÿPì¿C,迈Ç78±¬}Ä!ïêIë ë½É¸¼ÿ¸*û‹ýW)û¯Jñ_È»šèÃÖ’Rþ»Qu­Ž‹4æ1’$Õ!ªCIg‘ŒÌˆ ”>Ö&úíXûǶ¿ Ï »í/S$ß´^oÒQ{ľæmøR©þ¶ûêyŒê‹Ô’$$0ˆp½BùÉí~w°Çã9N%Vâ¦ïq¢uîÕAÄc9I¬†//íÇAWí»zxn¶5µßøÑoN ûð Ç qjA‡¼Ä bö ÛZGíÜu ÄòåÆ÷VOæC â¥õª÷Ïî~& Äso¢k#P)Oë[×€¨¤^öH} í»z7mwjëR㡆{Ïû[$ßFjøÎ׸Ë·x1¼Ä‰Åâ×?•¤pÈ#ù+2‰gúf8 S¨»I¼wË/,Éáh‰‡cû, ì8Ö­œº$æâ]&Ãÿ¶?ᕎcÞpß)o8ìü#¼ãÝöÔ»ód9eøùä ¯ªxŽx‡ÌßáSÞ«mÌß [YåEáùx¹0 ”ºÿfà²|ÍçcÞçÄ»e~ êKçDœpŠ`/X/–mkðjÙ&8ç‘“ñÀs«9^Âò±­¹Ž >~ÔÀu¸ü¿¿oø`ãyº‹'Ï{ýÃúáz3ës†×-‘ûƒ„)y[ï“ÄS,aþö•O>ÌÔØªq5¾Öª 5±&Õ˜Ë1x+<'_½7ÁZiq­dÊé˜öé5 yÛñ;CσaØ6NáÎ ³ÀNÏæcÞvrœ œ„$Ahm6y‘…)Å/×6fû‰ÅˆÚ›„¾‹Bü|÷íÛ=yÖl{MÈCN„ѦÈå¯/Gýí/”.ôw»gžú'ü_׿׼¨3sfNv—|¨->Þ¤ŽF½g9~|Ú†+Ö­ìº$уžÃçlø‰2“çøFÄ«ìxÝ–w~¹Ã%7'@È"á;+Šù«uæNYyûá<Ï}P1· %Hùµ^šÞïšùx©gA/ñBI% ½ÝœÀ2“o>‰H¡=@ES\s󯤖ã@¤»?ð°ŒØH’o¥ÍP#^wð¤õ¸×äè|±vÈ uM¾Ïòß ï#Ö+°]#LlËÁKÿm$´á¸{GîФ€«íNÂ$Ãn´ÁMÁžÑFm´ÑFm´Ñ–ÔþžSCÀ{radio-beam-0.3.3/radio_beam/tests/data/NGC0925.bima.mmom0.image.tar.gz0000644000175100001710000001525614025155256025561 0ustar runnerdocker00000000000000‹‰#XìlçyÇ)K²~X’eËVÿ|㟒MQwG%l!Ó”-ÇY’2ê«q"OôÙÇ;îî([Y²E“-†YÐvù§éš,È’ k [»%ö£ÝÖÝ/tÉÖŠÙ€¶hÝë†yÏïøœýt'žŸOr¼‡ïû¼÷>ï÷}Þ÷Ž´-MŸNs A Í*5)T«é5.VUÜ:8 í39ÿÙ#À‡9ž‹q¼‹8>ùoa +Ò0-É`, Jy5¿µêïP¦WšU¯ZÒ¬z+aãó/ æÿv°öü;¯!E›Óo¶{‚£ÑÈŠóÏ ‚7ÿÁüÇ >À¸[9Е¸Ëç¿´P—YŠÓ«¬&›&Ì|o±1ë–ööäºn*–n,°9Ý`¦>g]• y¬*k²!Yr…AžT­ÕÚì}¿GDl„õ®ÿ¹w±W]ÿöºÇý_€XÿQ1ÂÓú¿¼L¹Ž¢%iɨ­¬¤µ;Õ¶±Éõ¾´¹v·[þ8mâÎ䆋Ÿ 4gµXÌNiùš=«öq¦¹Îöz´ë•Z]•sFE6äJVª{³ßæÖÿðÀœï 4ÛTõòïb™¶*(‡‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ˆ»›ÛöoÿÓôoÿ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ be~ð*éLg:Ó™Ît¦3élŸ?þÕÆƒ?ùà ï ‚ ‚ ‚ ‚ ‚x™šžÌ-6‚ ‚ b£lrÏ#®=â³ópŒ»ö'|åÏÃqصÿ*ÐüåF¶ý]8˜kw´Û\{'ØCÞõÁ>ãÚö¯QÚáÚŸ{»kìa×~É×öU°{\û`oqí¿û>×¾öA×Þ„å1xÙíÚ—^×þ¼œpíÇáå˜kÿ¼ì÷Ƹ Çþ#Ÿ½§ít;jeøÊŸnG­^iG­¾ÝŽZýg;jÕÝãÝÓZ•;P««¨Õ¨Õ}m_î@­¾ÖZýyjòVjÕÓ‰åÇ;Q«S¨Õt'jµÐ‰Z=Ó‰Z½Ò‰cÿ7Ÿ=°íèfÔJö•ÿÊfÔê+›Q«¿ÜŒZýûfÔê¿7ãxºP«ó]¨Õå.Ôêá.ÔêÉ.lû\jõÛ]¨Õt¡&ov¡Vÿã+ß×ZźQ«‰nÔJíF­~µµúJ7Žý ŸÝÖƒöhjõQ_ù£=¨Õ³=¨Õõ VßéA­¾×ƒãmëE­¦zQ«ŸíE­ô^Ôê±^lû…^ÔêK½¨ÕK½¨É뽨Õ÷}åƒ[P«ã[P«ÈÔêâÔêñ-¨Õ³[pìßôÙ×}öÞ>ÔêÁ>,7ûP«Ï÷¡V¿Ó‡Z}«µú—>ïõ>Ôê~Ô*ßZÍö£V×ú±í/÷£VOõ£V_ìGM^ëG­þÕWÞ>€Zí@­Ž  V…ÔjaµúüŽýUŸý–ÏÞºµúÐV,ŸÛŠZ}z+jõå­¨ÕŸlE­þn+Ž÷­­¨Õø j•D­fQ«+ƒØöQ«_D­žDM~wµú{_ùOQ«Ám¨Õîm¨Ufj¥nC­>½ Çþ²Ï~ÓgoÚŽZñÛ±üÂvÔê‘í¨Õç¶£V¿·µúúvï›ÛQ«ƒC¨Utµ:3„Z}|ÛþÜjõÐjõØjòC¨Õ7|åo¡Ví;P«þ¨U|juqjõÈûs>ûuŸýã¨ÕáX~n'jU߉Z=±µza'jõÕ8Þ×w¢V;‡Q«ÑaÔêþaÔê#ÃØ¶2ŒZÕ†Q«kèɯ £V_ó•ÿÓ0jõ“aÔ*pjuâÔªpjU¿ÇþŸýšÏÿ-°¹vï.›kØ…ý&À>îÚe°zºí˜_Ü…zþé.'<ÇþÇ]Ø×¦{±¯#`ïríÔ½¨Uì=®}ì¤7×`Ç]û;`§]›íF;æÚŸÜý¾à³¿ö¤kïÁò”Ï®ìAŸßô•ÿõŒÿúŒ¿o/Æ`oó·q:ûÏ^Œ_Ù‹ñi/ÆÿݽÿÖ}ÿä>ì·vƵ_ö•¿á³íI÷b;´c»?Æ6í+ŸÛ±=³cû‡ýÛžÛäŒí‘Øï³0¶·}åƒ íCŸ‡}å_;äiË0?ÿ‹á:ÝwæUô>ÌÉ<Ø×þ”Ïþ}°Ã®ý`ñâ9è‹ç jõ3ñú&Ø{]û3±¯âšúúAÜ+Þò]s.8ê]ÿê6{}ž8„ý¾t÷“ë‡p½l9Œñ>ŒûLü0æÕ™Ã¨êóyÔçóÙø/ýúaŒá/Àºö}å{`l>‚ñœ?‚ñè¾òÇ`¿OÁ=óÅ#¸þ1Ø ×þéÔm÷QŒaò¨/Ïb Ÿ=Š}=cxÍg¿qûúþQ\³â1ܯ.Ãýê±c¸_= ÷º?<†×ùŸÏõc¸FB#˜gF0Wë#8O`Ì/úüÿlóüG#˜“»FqŸOŒb¿EÝžEmŸÅ<cûÚƒ}<@AAAAAAqç`ÿYçFñϵéxÓ&‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ˆÛÅñ§™ó7ærR6®Èª¼À¦TUÑtÅdYÉXP%­Â& CZðüÎHK²|…¤Š¢³Ü¬)ó’¥ ¬"YR¾;]˜È&Yvjº”)Lf ¹l 62/¦¢kL ñƉ5i!‘Mç²Ù‰éSI–¹&—–\aº–d‰ÄÙ™i.–ä#INL†ù×òK뵚)3E«7,“Í骪_M¶|3õ†Q–SZµÌ%ÑWQ†U½šªJªÙ0æå…”øê%Í2SÇ}Š¥ÔäT”óU ¥’ªÒãB±pØ>…£±à‰ÖÉ.tß5ëFƒ•Š\†¾ëh†\7S|0â/+_’´ï+™WÌeÆaZz=u",æN3…ó™BòÜÔtÆ®`e=+äò¥cIºi–uÃUɲÕ@ôB6w*6aW­¢_5âxä?Ú¼„¦³Zý[Ä虉é›f#g$MS´*3kºn]ËnzrªTL1¬b>“N1~ lÄëòª¡X–¬¦óÅbzâ\êÉK¦9kO±vÙ‰Ú¼ªXåK£é‰ü9×+-©Ê¬!-ª…þÎLMÚ#g"âÜ×ó’2g«l>=óЇF?è)‰&ÃqJyJù»%åŤMŠ”ò”òwMÊG“ðÊ…)å)åĒb‚RžRþnIùXRH$¹¥<¥üÝ’òñ$Ï%Ã"¥<¥üÝ“ò"Ÿ)åWHy!B)¿ñ”?Oçò CC‘*ìt=­×’ÌËOž;%—Ç1ÏoQjr\.]âû™›'9èÀs[%3=—ælÚeIÅB½ayS*C,3ça­øÜw­ð`H7v¹¡%ânÝ*òàïz­ ë±(Ûü¡,ŽQUgÎOž›8½rÇB$Û7ÝVí¹é²¸kì±9UªÎKjªE&ìTe+5f^’ FÄQ¯**Í+òU¦Ï9íªvn‚hpmeVQkÍ)ªÌÞÙOÖ]b·2¡‹Y:+f2lÄÞÖšWƒzÉdU]¯´z,é°¡0­Q›• »WÈXݨ˜nˆr6³h™½‰h²ÚêÁ}Êå 4‘T6ôt.¡Yê‚çv–ô–x$NÄbѦ)Æ.Рàã‘°€o9ûÅS4¬âEOï*W•ŠÜ,^L ÄŒÄ9ÏŒG¸bb,åÖ“㹞tŽÝâtžÃ"¹R•SÂ{”ÓçŠË$ñ¬´¡†´½#sØI\Ç„Ä KaÖK›0—ˆÇWÊp!›nᘎ®']ï]º¸»ŸT«„¹÷j뻫ÓsL°„•Ò1x~…´iæJÓŒQ1ònw>Øî–˜Ë‡dox+î‹b„ó™3çOeà± Ÿ5æOÙäÚ3>26ѨŽqè½bâ‹IÎyÞpýVÍ|×ç©ßªð?s4*eøDrM©5jþC©^W•¦Ö%™Áš€”L»µ²5'`$RU^g>gY¹¢HÚú›Db§'òùsð,çÏ»ò qxPSÇ –?[ò*WT>àÃC“ç¶Úó¤ë²X;+ªv '„c'„x‘ÄÇ&åYøÐäù.?%aûo`pðyÞs[eJ<—%Rbñ)±ÂÉo§Ü¬(X ñèss)>$b™!Wa),Ð †•9À+= ·žŠ};mz3Åd'ÕrjDƒB4ÈK¼ ó ˜’8£^Ãær ®èÞ­à³pXLˆ…L±äÛu 2|œ_´é L›‚Æ=ßM8ŸB=·Uõ\–ŠÅKõùëYEI±•.·ÜOBS(‹†x.‘9ÁqìÜkBb”wÞHFÙ”ËAV—Ài,‚„wÊ+rÕe3;Q:SÄT“¬K撵˕-¸¹ž‹u&òûO€ãɰÒ„<¯Õò¬éá×ê­Á´ q„5óJ«´&™W<=jz¨j…¸P8 ‘®ÇJÃÿ¼Œ‰„ƒÐÊ^Èüh1›Ëf¦K¾Á c’sÛmÕ¬2T¶¹–ß*ƒmùÀ†£áÀ|ž×ô79UÂé˜S,˜Iç焇ä1MŸ‡P›ÞKâÌN\söŸØpB’ƒ)i:­dÓÁâø¬R“Æß•í¸uI7Æ­Ëãù™“ç¦Òãvåøôét3‘¡e³MÈŽÖS× u³`:WÊ$ÙŒ)·6Âyɸj1eŽ©ºä̤¢Áª©ܘ"…àIÙpV.TÛë%™\Z²ŒÓHMº¬©Ÿ…BYMÑðM]ò,g!º¶!×ôy95'©¦döw—–ÑË}°MñÐRW%CyÈÙdœtUÖ¤šœ:xpôq½ßG– ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ bc,ó#gŸÃ:®êUKšUåqç5T‘¬›îÃþ±ÔÑhÄ>ó1‘óŸm1Êø0Çs1N€—Ôr¼`Ü-çŠ4LK2 ȆR^Ío­ú;”W€@ S³³dÏ4›¶I8·ÁÑ›W%Esk:€’çÍ)Ù,7][l;§WYM6MûWXîµD8¶8- ÎoÑp/»Û¾t³dÉ•Ún¶Q§{¾·X–TÉHëj£¦Ù~4.<ÌqÓ¡£4•ÍÀy {ös~ GY×*&”ô-I«HF¥he%m¹‚î%ò}fùH/.i»ÝùÌôTÉnä^ /›™(Î2Kò_ù6¯¼™Ì2Ói¥ÝE›pg&ŸKŸ±/=SJ»ŸÓ;µ(ZöÏSniÑ/Lå S¥ n£µØâë¢çfçªÍg¯cW6S,NœÎÜTˆÛb÷¹\z¢4•›þÇØ“;y6“.]œ:õ>yãÆÿõí*mËõpzë³ÍWæÏS¹/7¹ûæÃ_¾H¯BmîKp´‹Y××5Gáè<©êå+Üg€¡<Ôõ“à;ûÓU?ج÷þïŽë¦Xãþωœ¸ôþÏǺÿ·”»$àè04{ªÛÜ «Ó=:à¸7°d‘ÛGûûô­`Åõß\ösÜÅR1û.âZëŸã"ÞúçÅXÖ¿(‚;­ÿÛ7ôí;â>E¼7¬±þmN×}¬¹þÃü’û¿†m€Öÿm ´P—YŠMÙsÞ[l̺ï{{ßïÀˆÛš÷ÿ[ÐÇZë?—Üÿ#1zþ¿=4Ÿ{Ûîs ¤¨r%-«ª÷‰Ü~¸µ?ÖöLåuS±Q—[ØÀ§_ïøg8zKxÍ[_z.]p´×¤º[ì\Åÿ=†ÿ²öäoÞÀ× Nãu»nÇÿבּþßÍ×þ-V_ÿ|,–Þÿ…0=ÿßšëÿ€½xýßÿ·òÑ÷ÿ«|ÿìý^`Ùo¿XnéÚ<º½ï™Àîó]isY·çv ù‘{å¥oÓÙÐËvõ¾¯ì®)fÙ}t]»y“ðëv·ÇÓcä÷¼½ì°ÛŸYöBCvŸ–¬B‰^—ýa볦l̈¯¬ Ê`Êë m ®+š¥hÕ²¬YÎeÖnÓ[QÜ_Í­O­«º¡V`·Ý·Ã±/°ÌVÚvp[ô;- ¹®JeÙnµs=­zêÊ5YÝP?N‹öÓeZúÙä76xþ¦ÏoxðëïÇ?øõ÷cÖaî I66|ᦆ/lxøëïçÿÚ»šØ6Š(iåd2;oæ½yoÞ¼™óÃGÓ1nÆÇ枟9pà0Ì…£S‡§¦Á©ÁŸLhÈd!̃=}¥J™Ãe%W¥ÉÁNÁÌ£¥¤Éæy„u®'‹{ó¬°!Ó´.KêtÇ9° äzHÿ^_xäÂ*ü í}(&qÜŸNÍòý¼¤z 'Jýcå²´bŸÒîµÙp(¥~>þ·S׿Ùò^<Úž„} \öV•ˆõáÜø£¥²vʲ{\én鉒T– |±6vŒa¹¼lŽ[Û˜´øÚYEÕ±µƒ%[5$U`„ÛQ½pVZTí-²V´¿qyÖËcÝ/Η4•Ÿ£½ª¤{ŠLe8´ÇpAî³S¨9Ãjžh¸‰æÿRZ9i6ŸÎ^ÆSúêû£«—^»Ùkeõ^ rO(õ‡`d¸ò܆¬ó_sfòó zllÑðm دÈj¾(Ù¢L`šé)KY÷ÃS:÷ªÅô•G{¦/¯?î0ž¨ÕÖDQ¯',¬5x÷âÚOË#©ë_œ=Î~=ÝæækY<Æ„¶f'ÙškR¡"–÷­¾!- ü>ÄÌ'ç‰Ì:ÌØ©¥l$Sóá”ÿ2'ù¦ZŽ×«¬ó¬ެåO›êC»štYýL~ðLj'¿ñôÚöž¹×j×Þ>ø^÷\Yy…¹†~“S†^m Rêœås9¤¢`¿–O)T߯ðËL¸CúŒ‰ÔbÉ›*¡†Ñö¾’;A/g‹è_µÖƒš¿.Îu‡ƒØáâ¢(Íì„«xXÙ (â¾·²NÝzøß¹ýƒ ÷¶CÛ4o›5Dnã+ÄDÖ„DÂ8)à‚9?…Cs3p}ª± žj¡K’ê‘¿ÄDºv MŽÍÎu^vYfû6 ã½BûMÙðv5Éuù¾C+CE*천©À¼˜A7„ ‚HTŸ¬¨ê¢lzQ71SNñ<Ëò|²AïÒ¢"/Tå„–µÌ"÷{DË@ÜŠk4bðÝŠ]UqõL>«/po…Uc](ñͨp¦WUñL°žLIbž9:˜:þÝç1–S/MY¿ý㛩›·N]d[¯¦þ>yþ¹¯µ­0 û¬Ñ؈?˜~Ìz½ò¹R9ìë3ÜuMÎë+—·FÖR·o¼o0ï1›Ï Ù ²ë‡í„ÎH‹á<£Z|õøñ°|Þw郺] BÖpûˆ°!dÕ~ÇâJÅywJÝ\÷¾¥€òvíÛxl¶î©,SöNè Œ¦Ý~zÛÞK÷¯_ƒ9Þ!ãœUæÝYhøèEfúŠ]åc1Õi!UøËÀ¸1½Ê²™£qº’Wà€˜Ò'ÂMg­˜Íü8âÊJ:è-2̤Y4PµƒytՀݻ§ÂÅ·oŠ<Þ¨®T%§áífE.S&§j’‹Åª o¶·ê±á]`œ¬»- RIq.¸Ø{Ûâ¬aƒ;ÌŸ±’gªxS{W‡Ù¯Ù}âÉ6‚ÏýVÒ>*h>ÿ#9¼;N÷?]Cü*GaþV×îFÔÕÿ‚´xz÷P[h€ŽŽ&ä¸ù߉á8ÜÿN&X,Ùê>¸ÇõßGþmÉòÏÿ‰{ä?’Iý': ”þ·˜äŸÿ3êÕÿáø(é7`çû1Dž™û"ʺ—€\ÿ[¢á›ÿ“öèrt”îÿwvþÏ“¬åüŸ_˜oþ jšÓÒØ%5i@|«”ÔY ô¿Å, ý'ã^ýÙ“ ÿÿÕXúßÿCÊ?ÿ'x5Îÿ¹£gàKñK/ _‚¿lãÃèUöMŸܨÑô†—u]Aò|êBP}Ÿ¦©17¨^Mß?¶^»N0v—„Ø«»¶±—”ýoñ ™øeÿGþ¡{ð‹ÿ;Ïÿ.þO @ @ @ @ „‰ÿßÜ&radio-beam-0.3.3/radio_beam/tests/data/generate_commonbeam_table.py0000644000175100001710000000501114025155256026163 0ustar runnerdocker00000000000000 ''' UNUSED IN CURRENT TESTING SUITE! Run in a CASA environment to generate the smallest common beam for a range of parameters. ''' RUN = False if RUN: try: import casac except ImportError: raise ImportError("This script must be run in CASA") # Tested for CASA 4.7.2 import casadef if casadef.casa_version != "4.7.2": raise Exception("This script is only tested in CASA 4.7.2. " "Found version {}".format(casadef.casa_version)) import numpy as np # Generate fake cube w/ 2 spectral channels im1 = ia.newimagefromarray(pixels=ia.makearray(0, [4, 4, 1, 2])) # Give the first channel a circular beam im1.setrestoringbeam(major='3arcsec', minor='3arcsec', pa='0deg', channel=0) # Give the other channel an elongated beam, and rotate it majors = [] minors = [] pas = [] # Include the PA of the one beam so tests can be run later. orig_pas = np.arange(179) for pa in orig_pas: im1.setrestoringbeam(major='4arcsec', minor='2.5arcsec', pa='{}deg'.format(pa), channel=1) com_beam = im1.commonbeam() majors.append(com_beam['major']['value']) minors.append(com_beam['minor']['value']) pas.append(com_beam['pa']['value']) common_beams = np.array([orig_pas, majors, minors, pas]).T np.savetxt("commonbeam_CASA_comparison.csv", common_beams, delimiter=",") # im1 = ia.newimagefromarray(pixels=ia.makearray(0, [4, 4, 1, 4])) # for i, pa in enumerate([0, 20, 40, 60]): # im1.setrestoringbeam(channel=i, major='4arcsec', minor='3arcsec', # pa="{}deg".format(pa)) # im2 = ia.newimagefromarray(pixels=ia.makearray(0, [4, 4, 1, 4])) # for i, pa in enumerate([0, 60, 20, 40]): # im2.setrestoringbeam(channel=i, major='4arcsec', minor='3arcsec', # pa="{}deg".format(pa)) # im3 = ia.newimagefromarray(pixels=ia.makearray(0, [4, 4, 1, 4])) # for i, pa in enumerate([60, 40, 20, 0]): # im3.setrestoringbeam(channel=i, major='4arcsec', minor='3arcsec', # pa="{}deg".format(pa)) # im4 = ia.newimagefromarray(pixels=ia.makearray(0, [4, 4, 1, 4])) # for i, pa in enumerate([60, 0, 20, 40]): # im4.setrestoringbeam(channel=i, major='4arcsec', minor='3arcsec', # pa="{}deg".format(pa)) # print(im1.commonbeam()) # print(im2.commonbeam()) # print(im3.commonbeam()) # print(im4.commonbeam()) radio-beam-0.3.3/radio_beam/tests/data/header_aips.hdr0000644000175100001710000004200214025155256023417 0ustar runnerdocker00000000000000SIMPLE = T / BITPIX = -32 / NAXIS = 2 / NAXIS1 = 2707 / NAXIS2 = 2707 / EXTEND = T /Tables following main image BLOCKED = T /Tape may be blocked OBJECT = 'omega_ce' /Source name TELESCOP= 'ATCA ' / INSTRUME= ' ' / OBSERVER= '' / DATE-OBS= '2010-01-22' /Obs start date YYYY-MM-DD DATE-MAP= '2016-07-20' /Last processing date YYYY-MM-DD BSCALE = 1.00000000000E+00 /REAL = TAPE * BSCALE + BZERO BZERO = 0.00000000000E+00 / BUNIT = 'Jy/beam ' /Units of flux EQUINOX = 2.000000000E+03 /Epoch of RA DEC VELREF = 259 />256 RADIO, 1 LSR 2 HEL 3 OBS ALTRVAL = -6.85933178146E+07 /Altenate FREQ/VEL ref value ALTRPIX = 1.000000000E+00 /Altenate FREQ/VEL ref pixel OBSRA = 2.01691208348E+02 /Antenna pointing RA OBSDEC = -4.74768611168E+01 /Antenna pointing DEC DATAMAX = 3.216996155E+02 /Maximum pixel value DATAMIN = -6.924145508E+01 /Minimum pixel value CTYPE1 = 'RA---SIN' / CRVAL1 = 2.01691208348E+02 / CDELT1 = -9.166666860E-05 / CRPIX1 = 1.356000000E+03 / CROTA1 = 0.000000000E+00 / CTYPE2 = 'DEC--SIN' / CRVAL2 = -4.74768611168E+01 / CDELT2 = 9.166666860E-05 / CRPIX2 = 1.356000000E+03 / CROTA2 = 0.000000000E+00 / HISTORY / History of input map 1 HISTORY -------------------------------------------------------------------- HISTORY /Begin "HISTORY" information found in fits tape header by IMLOD HISTORY EXTEND = T / File may contain extensions HISTORY IRAF-TLM= '2015-07-27T09:00:19' / Time of last modification HISTORY BTYPE = 'Intensity' HISTORY RADESYS = 'FK5 ' HISTORY LONPOLE = 1.800000000000E+02 HISTORY LATPOLE = -4.747686111680E+01 HISTORY PC01_01 = 1.000000000000E+00 HISTORY PC02_01 = 0.000000000000E+00 HISTORY PC03_01 = 0.000000000000E+00 HISTORY PC04_01 = 0.000000000000E+00 HISTORY PC01_02 = 0.000000000000E+00 HISTORY PC02_02 = 1.000000000000E+00 HISTORY PC03_02 = 0.000000000000E+00 HISTORY PC04_02 = 0.000000000000E+00 HISTORY PC01_03 = 0.000000000000E+00 HISTORY PC02_03 = 0.000000000000E+00 HISTORY PC03_03 = 1.000000000000E+00 HISTORY PC04_03 = 0.000000000000E+00 HISTORY PC01_04 = 0.000000000000E+00 HISTORY PC02_04 = 0.000000000000E+00 HISTORY PC03_04 = 0.000000000000E+00 HISTORY PC04_04 = 1.000000000000E+00 HISTORY CUNIT1 = 'deg ' HISTORY CUNIT2 = 'deg ' HISTORY CUNIT3 = 'Hz ' HISTORY CUNIT4 = ' ' HISTORY PV2_1 = 0.000000000000E+00 HISTORY PV2_2 = 0.000000000000E+00 HISTORY RESTFRQ = 4.475999873410E+09 /Rest Frequency (Hz) HISTORY SPECSYS = 'TOPOCENT' /Spectral reference frame HISTORY casacore non-standard usage: 4 LSD, 5 GEO, 6 SOU, 7 GAL HISTORY TIMESYS = 'UTC ' HISTORY OBSGEO-X= -4.750915837000E+06 HISTORY OBSGEO-Y= 2.792906182000E+06 HISTORY OBSGEO-Z= -3.200483747000E+06 HISTORY WCSDIM = 4 HISTORY CD1_1 = -9.1666666666670E-5 HISTORY CD2_2 = 9.16666666666702E-5 HISTORY CD3_3 = 2049241561.325 HISTORY CD4_4 = 1. HISTORY LTV1 = -1205. HISTORY LTV2 = -1205. HISTORY LTM1_1 = 1. HISTORY LTM2_2 = 1. HISTORY LTM3_3 = 1. HISTORY LTM4_4 = 1. HISTORY WAXMAP01= '1 0 2 0 0 0 0 0 ' HISTORY WAT0_001= 'system=image' HISTORY WAT1_001= 'wtype=sin axtype=ra' HISTORY WAT2_001= 'wtype=sin axtype=dec' HISTORY WAT3_001= 'label=freq' HISTORY WAT4_001= 'label=stokes' HISTORY /END FITS tape header "HISTORY" information HISTORY -------------------------------------------------------------------- HISTORY IMLOD OUTNAME ='WCEN_LOW ' OUTCLASS ='ICLN ' HISTORY IMLOD OUTSEQ = 0 INTAPE = 1 OUTDISK= 1 HISTORY IMLOD INFILE = 'PWD:wcen/wcen_atca_5500_pbcor_c.fits ' HISTORY IMLOD RELEASE = '31DEC14' HISTORY / History of input map 2 HISTORY -------------------------------------------------------------------- HISTORY /Begin "HISTORY" information found in fits tape header by IMLOD HISTORY EXTEND = T /Tables following main image HISTORY BLOCKED = T /Tape may be blocked HISTORY / IEEE not-a-number used for blanked f.p. pixels HISTORY -------------------------------------------------------------------- HISTORY /Begin "HISTORY" information found in fits tape header by IMLOD HISTORY EXTEND = T / File may contain extensions HISTORY IRAF-TLM= '2015-07-27T09:11:10' / Time of last modification HISTORY BTYPE = 'Intensity' HISTORY RADESYS = 'FK5 ' HISTORY LONPOLE = 1.800000000000E+02 HISTORY LATPOLE = -4.747808335040E+01 HISTORY PC01_01 = 1.000000000000E+00 HISTORY PC02_01 = 0.000000000000E+00 HISTORY PC03_01 = 0.000000000000E+00 HISTORY PC04_01 = 0.000000000000E+00 HISTORY PC01_02 = 0.000000000000E+00 HISTORY PC02_02 = 1.000000000000E+00 HISTORY PC03_02 = 0.000000000000E+00 HISTORY PC04_02 = 0.000000000000E+00 HISTORY PC01_03 = 0.000000000000E+00 HISTORY PC02_03 = 0.000000000000E+00 HISTORY PC03_03 = 1.000000000000E+00 HISTORY PC04_03 = 0.000000000000E+00 HISTORY PC01_04 = 0.000000000000E+00 HISTORY PC02_04 = 0.000000000000E+00 HISTORY PC03_04 = 0.000000000000E+00 HISTORY PC04_04 = 1.000000000000E+00 HISTORY CUNIT1 = 'deg ' HISTORY CUNIT2 = 'deg ' HISTORY CUNIT3 = 'Hz ' HISTORY CUNIT4 = ' ' HISTORY PV2_1 = 0.000000000000E+00 HISTORY PV2_2 = 0.000000000000E+00 HISTORY RESTFRQ = 7.975999774420E+09 /Rest Frequency (Hz) HISTORY SPECSYS = 'TOPOCENT' /Spectral reference frame HISTORY casacore non-standard usage: 4 LSD, 5 GEO, 6 SOU, 7 GAL HISTORY TIMESYS = 'UTC ' HISTORY OBSGEO-X= -4.750915837000E+06 HISTORY OBSGEO-Y= 2.792906182000E+06 HISTORY OBSGEO-Z= -3.200483747000E+06 HISTORY WCSDIM = 4 HISTORY CD1_1 = -6.6666666666670E-5 HISTORY CD2_2 = 6.66666666666701E-5 HISTORY CD3_3 = 2049361403.91 HISTORY CD4_4 = 1. HISTORY LTV1 = -784. HISTORY LTV2 = -784. HISTORY LTM1_1 = 1. HISTORY LTM2_2 = 1. HISTORY LTM3_3 = 1. HISTORY LTM4_4 = 1. HISTORY WAXMAP01= '1 0 2 0 0 0 0 0 ' HISTORY WAT0_001= 'system=image' HISTORY WAT1_001= 'wtype=sin axtype=ra' HISTORY WAT2_001= 'wtype=sin axtype=dec' HISTORY WAT3_001= 'label=freq' HISTORY WAT4_001= 'label=stokes' HISTORY /END FITS tape header "HISTORY" information HISTORY -------------------------------------------------------------------- HISTORY IMLOD OUTNAME ='WCENUP ' OUTCLASS ='ICLN ' HISTORY IMLOD OUTSEQ = 0 INTAPE = 1 OUTDISK= 1 HISTORY IMLOD INFILE = 'PWD:wcen_atca_9000_pbcor_c.fits ' HISTORY IMLOD RELEASE = '31DEC14' HISTORY CONVL RELEASE ='31DEC14 ' /********* Start 16-JUL-2016 18:59:57 HISTORY CONVL INNAME='WCENUP ' INCLASS='ICLN ' HISTORY CONVL INSEQ= 1 INDISK= 1 HISTORY CONVL OUTNAME='WCEN_CONV ' OUTCLASS='CONVL ' HISTORY CONVL OUTSEQ= 1 OUTDISK= 1 HISTORY CONVL BLC= 1. 1. 1. 1. 1. 1. 1./BLC HISTORY CONVL TRC= 2275. 2275. 1. 1. 1. 1. 1./TRC HISTORY CONVL FACTOR= 2.23193E+00 / Units scaling factor HISTORY CONVL BMAJ= 2.9700 BMIN= 1.6500 BPA= -15.1/Output beam HISTORY CONVL / plane 1 conv with 2.17968 x 1.23466 at 168.0 HISTORY CONVL OPCODE=' ' /Operation requested HISTORY CONVL DOBLANK = 1 / Blanks restored after FFT HISTORY OHGEO RELEASE ='31DEC14 ' /********* Start 16-JUL-2016 19:00:16 HISTORY OHGEO INNAME ='WCEN_CONV' HISTORY OHGEO INCLASS ='CONVL' HISTORY OHGEO INSEQ = 1 HISTORY OHGEO INDISK = 1 HISTORY OHGEO IN2NAME ='WCENLOW' HISTORY OHGEO IN2CLASS ='ICLN' HISTORY OHGEO IN2SEQ = 1 HISTORY OHGEO IN2DISK = 1 HISTORY OHGEO REWEIGHT( 1) = 1.00000E+00 HISTORY OHGEO REWEIGHT( 2) = 3.33400E-01 HISTORY OHGEO APARM( 9) = 1.00000E+00 HISTORY FITTP DATAOUT = 'PWD:wcen_up_ohgeo.fits' / data written to disk file HISTORY /END FITS tape header "HISTORY" information HISTORY -------------------------------------------------------------------- HISTORY IMLOD OUTNAME ='WCEN_OHGEO ' OUTCLASS ='ICLN ' HISTORY IMLOD OUTSEQ = 0 INTAPE = 1 OUTDISK= 1 HISTORY IMLOD INFILE = 'PWD:wcen/wcen_up_ohgeo.fits ' HISTORY IMLOD RELEASE = '31DEC14' HISTORY / End of old histories HISTORY COMB RELEASE='31DEC14 ' HISTORY COMB INNAME='WCEN_LOW ' INCLASS='ICLN ' HISTORY COMB INSEQ= 1 INDISK= 1 HISTORY COMB IN2NAME='WCEN_OHGEO ' IN2CLASS='ICLN ' HISTORY COMB IN2SEQ= 1 IN2DISK= 1 HISTORY COMB OUTNAME='WCEN_ST ' OUTCLASS='MEAN ' HISTORY COMB OUTSEQ= 1 OUTDISK= 1 HISTORY COMB USERID= 5 HISTORY COMB CTYPE='MEAN' /Mean HISTORY COMB BLC= 1 1 1 1 1 1 1 / Bottom left corner HISTORY COMB TRC= 2707 2707 1 1 1 1 1 / Top right corner HISTORY COMB A(1)= 6.9856E-01 A(2)= 3.0144E-01 HISTORY COMB / Undefined pixels magic-value blanked HISTORY FITTP DATAOUT = 'PWD:wcen/wcen_stacked.fits' / data written to disk file ORIGIN = 'AIPSmacbook-pro 31DEC14' / DATE = '2016-07-20' / File written on Greenwich yyyy-mm-dd HISTORY AIPS IMNAME='WCEN_ST ' IMCLASS='MEAN ' IMSEQ= 1 / HISTORY AIPS USERNO= 5 / COMMENT FITS (Flexible Image Transport System) format is defined in 'Astronomy COMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H HISTORY AIPS CLEAN BMAJ= 8.2477E-04 BMIN= 4.5736E-04 BPA= -15.06 HISTORY AIPS CLEAN NITER= 0 PRODUCT=0 / DIRTY MAP END radio-beam-0.3.3/radio_beam/tests/data/header_jybeam.hdr0000644000175100001710000000670214025155256023741 0ustar runnerdocker00000000000000SIMPLE = T / Written by IDL: Fri Feb 20 13:46:36 2009 BITPIX = -32 / NAXIS = 4 / NAXIS1 = 1884 / NAXIS2 = 2606 / NAXIS3 = 200 // NAXIS4 = 1 / EXTEND = T / BSCALE = 1.00000000000E+00 / BZERO = 0.00000000000E+00 / BLANK = -1 / TELESCOP= 'VLA ' / CDELT1 = -5.55555561268E-04 / CRPIX1 = 1.37300000000E+03 / CRVAL1 = 2.31837500515E+01 / CTYPE1 = 'RA---SIN' / CDELT2 = 5.55555561268E-04 / CRPIX2 = 1.15200000000E+03 / CRVAL2 = 3.05765277962E+01 / CTYPE2 = 'DEC--SIN' / CDELT3 = 1.28821496879E+03 / CRPIX3 = 1.00000000000E+00 / CRVAL3 = -3.21214698632E+05 / CTYPE3 = 'VELO-HEL' / CDELT4 = 1.00000000000E+00 / CRPIX4 = 1.00000000000E+00 / CRVAL4 = 1.00000000000E+00 / CTYPE4 = 'STOKES ' / DATE-OBS= '1998-06-18T16:30:25.4' / RESTFREQ= 1.42040571841E+09 / CELLSCAL= 'CONSTANT' / BUNIT = 'JY/BEAM ' / EPOCH = 2.00000000000E+03 / OBJECT = 'M33 ' / OBSERVER= 'AT206 ' / VOBS = -2.57256763070E+01 / LTYPE = 'channel ' / LSTART = 2.15000000000E+02 / LWIDTH = 1.00000000000E+00 / LSTEP = 1.00000000000E+00 / BTYPE = 'intensity' / DATAMIN = -6.57081836835E-03 / DATAMAX = 1.52362231165E-02 / BMAJ = 0.0002777777777777778 BMIN = 0.0002777777777777778 BPA = 0.0 radio-beam-0.3.3/radio_beam/tests/data/m33_beams_bintable.fits.gz0000644000175100001710000002460314025155256025402 0ustar runnerdocker00000000000000‹—õ%Yÿm33_beams_bintable.fitsÿÿìÏ1 1Fá«ü7ÐÒÆB1B@—…¤H;š,l‘Œ$±ØÛ»v bí|xðŒ¾ŽìÑ`±ÁÓÄ9TÆY[ƒR)yÊMGmGí:½ÝÚ£œi§J¨Ë#à‹áà´A§·]{éo!ƒ§OyŽ!•™Siö”³j8uöŽ !„Bˆ¿ñÿÿì˜=oƒ0†ÿÊmLUiÔ. uZ"0(q*ut¨•VŒ€(åß×`1ð¡ŠëЪ·Ò£{}wæµk;¹wú†ãRf;1:ŸÎGÊó ?ÆÈg©½é0oÊ?¯ßyΣR9^·ñå¼Åûg ì"o®–™ÇP6‰NðÌ~ž¥ü8½ÚñcUŠ2õËË8Ïêã™æÝ}—§(Åt=Âup lø¼@%äêü"ÈÔB&B­ä(õiŒgÂ"Hã d*à”Ës6Û¸Ä{Ü÷óV]½‘ŒÏIZüòùPç-jûêù ¶_·¢1“÷Bvƒõø©ùÓ“ÛöóúÙ¦0ØÏßž^ì`ì5$Í~`8¾½…¹õe›`çkžIô«y¼u™æñ<*D4—WëµZ½.ÅÑk!ëµõ.µÞнKd½š÷&N¼ZïmÃk'A¯æ™[@ÊoÕðÚ!¿^~èÿÿûµ?_ÿÿìœå›dÕõp) Ü AÂ08Á‚ÿ ÚÝAB€ ƒ5n,0Ó¸»»tª­º¤»º»ª«ñÁ!Ø».wå¡?¾óí®çœ{νu÷Ý{¯ªž9W}ôɜޣ®|ñ¦}‹ùö>ì2YD6»v‡QYv6ìŸÆdƒ°Óa×s—Ý»ö1‡¿’]ë…}Äáⲫ`gÊ–= ëð|KÊ…5Ãæs¸”ì~X­ã––Ý+‡-àð×3örì—‘];v#‡ËÎØËÞ°›9\Nv)lØ-./;¶ì6W ûƒlEÙßa¿—­$û+lcØí®,;¶>ìW‘uÃÖ†ÝÉ᪲ØêŽ[MV [Ñó­.+‡-#[Cvl ç®)ûl1Ùod{Íé=ò[ç®%Û ö¥×ñ[Ù°a·r¸¶lkØ»Î]G¶,ïëÊfÃFeëÉÖ%e¿“­KxÖ—­{C6K¶ ìUçn Ûöì1gÏXãiÙ†!ûðkØ#ÎÝHöìØ]n,ûv—ën"›‚Ýâ¸Mec°žo3Yv÷esY?lžl Ù°‹œû{Ù5°ë`ßr¸¥ìrX¿ßq¸•ìqØ)îokÙƒ°Üß6²»`ÇÀîåp[Ù-°ØÝþA¶Ö»Ãífì¥A¶½ì X5ì~w]+•í(;v¨sw’Ûß½ì,ûl/Ø=î";¶›sÿOÖÛQ¶«¬ ¶l7YðœnöâMóƒ5v—-#çÊá²-a0÷‹àPv l-؃þQ¶7lUØî)Û¶¼ãö’í[ö‡{˶…Å·l‹9½=?z¯ö•mûÆqûÉÖƒ}*Û?d|Èùˆß¾àû'Y‘qÄo_ð¾>À¹ËÂÆÝó²ÅaÙA²Å`C®q°çû/ì¯ãñÑóºã•=¯È“½ {ι‡ËÆaOÀæðÙ°s/â°D¶ö¼¬T¶vìËdïÁn‚½Èa¹ìEØõ°G8¬ñìöüö5‡•2žÝžȪd䔞 å°Zv'ìÏW#»v†¬VFNé9Ùë­“‘SzŽ—ÕË.ƒ-k]ë’5ÊÈ)=­ÞÓ&1ÓSç¸f1ÓSé=m‘ýv„¬UFÌôäÜ6Y+l_Y»¬6ǹ²í`A ~Åa§Œ˜éÙɘé’ÛÊ=wˈ™žMe=2b¦g¶Ïß‘²`ë¹—£dÄLÏšîåϲMœ<GËÖ‡­+;FÄÌ’~F‘ýõ|ÇÊV™ÓÛýë'ãÑý•ÏÁñ²%aŸ8÷Yö¾kü5dï +ÈN”} ó|“ñ¥+)»Uö6¬_v›ìß°7e·ËÈ)]¯ºÆ2ú”®¼÷wÊ=åÜ»d÷ÂqÜÝ2â£ë~Ù=2ú”®»d÷Êž‚5æKÞ'{6ßœw¿Œ>¥+¨1çðÙy°+ŒÕegÂþ—ß’Ñt]gñ°ŒÞ ëJÙ#2â£ë÷÷¨Œøè:Áó=&k†-{\FNéêò¾ Ùt¶¿9ô#Ù(lOßC˨s:©Eûÿ´HFžéœíù>q5`ëŸÊV„ñÄégÎý¶²ï«ÏgìeYÏ÷…ŒÞ¾s Ÿ«/e#°¨ì+Ùí°•ÌÝ_Ën Ï÷ûFF]×ñ‰ïöÿÊè?:~`\àš¾•Ñt|cñŒÞ ã3ïË÷²»a)÷üƒìVØ€qùãÏ{éx;ÑÿM—ÿ†ñ>Œèÿ¦©ë:^’éÿ¦©¹:ž ?߈þoz®sÉçýßô)Î¥ÞŠèÿ¦wnÀôÓ<ÏA-J½ÑÿM ësú¿évصáuDôÓ€]B‘S#ú¿éßÃ΃‘S#ú¿é’ŸoDÿ7}lnXÛFôÓûÁþæÐˆþoºä7Þ×ýßtìh™þošþ¨£Û¹ú¿ib¦£E¦ÿ›ÞV'ÓÿMÿV!ÓÿMS‡u!ÓÿMS‡u$ÓÿMS‡uõ$95¢ÿ›þ•syGDôEâ£ã`™þ¯ø,¨'ɳý_ñ+XPO’#ú¿â'°Í\CÿW¤Oéâ|Ñÿƒçyϧÿ+~[Ó{¥ÿ+Rëu¬,Óÿ°e]WÿW¤OéXÂóéÿŠ“°ŒüÑÿé·Ú¿‡‘#ú¿âìK×ÐÿïanÔ{ ÿ+Þæ\êóˆþ¯ØçÜ€éÿŠ `cÞý_ñj؈LÿW$§´½=@DÿWäyiÜ ïëˆþ¯HÿÑž&~ùÌ"ú¿"y«ý¯CÿW<öT˜"ú¿"½}û#2ý_‘Ú§ýeÎÇóÑÿW Ï×wLÿWä}ß~[ø.‰èÿмïÛoß§ý_ñpØÕa½ÑÿÉ3í—‡ïÓˆþ¯¸ìB™þ¯HÝÔ~ŽLÿW¤·o?]¦ÿ+¿í'ù~Ñÿ‰ßöã§ÿ+3í–éÿŠÄL{gXGôŵ`ÍžOÿW\V'ÓÿéíÛ˽6ý_qiØŽÓÿMQ¿´ïãýÓÿMQ·“Ëú‡éÿ¦xžÛç„ïñˆþoŠÞ¥}g÷§ÿ›z¶Lÿ7Åóܾ¥Lÿ7EÎkßÔgWÿ7ul}Þkä­ˆþoêØÚ2ýß1ÓĵdDÿ7õ ,ˆ7jɈþoê9ز2ýßÔã°%|þôSÂ"Ž;äç5Ú_B]ÑÿM½ ú7ê·ˆþoŠ8j[äuèÿ¦ˆ™¶weú¿©+`“0êäˆþoªö!×FÑÿMUæeú¿)â²­ßuËg¬Ô“Ô—ýßÔq°W½ýßy¦í™þoªö´÷@ÿ7Eh{D¦ÿ›"¦Ûî“éÿ¦Jaw¨±#ú¿©ƒaA=IÑÿM­ {˜gˆ˜Œèÿ¦¨UÚî…»ýßý~[пQÛGôSÄLÛE®«ÿ›"Ï´ëuèÿ¦6Íõ9ÐÿMQ›µâ8ýß1Óv‚ãôS¼7ÚŽqœþoŠ<ÓÖ%ÓÿM- kq/ú¿©(¬ÎÏRÿW Fj«áÚÈÑý_¡8ryDÿWøvkèÿ Ôfmû¹?ý_øh;"ü®2¢ÿ+$»¦ÿ+Ú¶w/ú¿Â;°­dú¿ý~Û¦2ý_!xv7fÔ$ý_áZØú2ý_á!Øfá÷’ý_:¬m¶LÿW ßo[Î5ôúý¶%eú¿Âu°¨ïý_á ç^Óÿ.tnp½ú¿BïœÞÖEžOÿW(ƒýhžÑÿ…õ$5DDÿW j%çõ‘Ÿ"ú¿ïöVúÁ>òXDÿWXô~äèˆþ¯@ÝúqÔú¿B]È~Ú‹þ¯P {ÉØ×ÿŽ€=ë8ý_<Óú¸ãzgìå!Ÿý_<ÓzLÿW(‡‘ßÐoFô…Ã`7ÈôúýÖ뜫ÿ+l»Òuõ…õ`—ÊôzñÖ d—θ/g{ú¿y¦õ4×Ðÿˆ™Ö¿É®˜±Æ±¾¯æÍXãH™þ/Ožií0>ôùE°f÷¢ÿË“gZkÜ‹þ/O¿ßZæ8ý_žš¿µ„Ï—:9¢ÿËó<·,Óÿå߆ííºú¿<1Óº»Lÿ— ¶³Lÿ—'V[ƒïÅé×#ú¿<}Eë k¹÷çýµ$Í)ú¿<õ_ËBX ¦ÿË÷À‚ïäÒ0ý_ž~¦%p(oÃôù9°çÝŸþ/¿3ìI™þ/O?ÓòˆLÿ—'Ï´Üçùôyj³–;dú¿<µYËMÎÕÿåégZæËôyj³–«dú¿ü °àw(ïÀôùÅËgÑÿåƒ]þ&!¢ÿ›üv†{ÑÿM~»Èû¬ÿ›$§´œë\ýßdÁ¹ÓÿMÒï·œì\ýßdÖêžõ“ô-Çó¼POGô“ôÝ-G›Ïõ“ÿ•øYêÿ&‰™–ƒeú¿Ég`û¹†þoò1Øž2ýß$µYË®2ýßämÎ î•þo²Ï¹ÓÿMÒk´l[ÓÿM΃íþþ ¢ÿ›¤ŸiÙ&ü]ADÿ7yl-çêÿ&φ­êç¦ÿ›<¶‚Lÿ7y liçêÿ&O€Å}?ëÿ&yž›”éÿ&‰ßæo|'êÿ&ëaŸ9Nÿ7I¿ßüCø»‡ˆþo’:¬ùëð7ýßä~Î Æéÿ&÷†¥­'õ“ôVÍCÖ»ú¿Iú™æwÜ‹þor[Øœ«ÿ›$Ï4¿ì8ýßäF°geú¿Ib¦ù ™þo’ÐüçÓÿMP#5ßÏsE_ÑÿMЋ7ßiŽ×ÿMšo ¿SŠèÿ&ã°ë|ëÿ&蛯t]ýßİKeú¿‰×`W >7ýßýQó™°`œþo";Í5ô䀿 Ïãó‰èÿ&†aÇÊô aGÁøÌ"ú¿‰§a¬ÁgÑÿM< «†ñYDôτ짹ú¿ b¦¹Ì5ôäžæCeú¿ òLó2ýßÄ-°½½ýßy¦yÇéÿ&è5šwö3ÒÿM\ÛÎçEÿ7A½Ö¼¥ãôÄLó&ŽÓÿMgšgÉôƒ­-ÓÿMš×ð|ú¿‰nØJ~nú¿‰]ÂóõчGôÏ×·½?ë X,¾;£ú¿‰Ãçô6}ÆjTÿ7qìË0Þ¢ú¿ b¦iQ¸nTÿ7Anlz/ü]_Tÿ7AÛÔŽ\kTÿ7A-Ú4º‘¨þobsØHxŸ£ú¿‰Ù°þðóˆêÿ&裚ÞrœþobMØ«ŽÓÿM¬{1¼WQýßįaO;Nÿ7ñ+Øc2ýß8}JÓ+Ü« aú¿ñ7`ÏÁfÃôãÄeÓ-Þýß8y¦iAø¹EõãS°kÜ‹þo<›'Óÿ ¾çýÕÿ ¾ç]]mÆ^ÎtÏú¿ñÁNu/ú¿ñça'ºýßø°c«ÿ'›z\Cÿ7N¿ßÔ.ÓÿóL61HžˆêÿÆ©}šJÚ+ªÿç=Þt,x®ôã—‡,¨_¢ú¿qz즽Â÷KTÿ7NÝD9?xÖô㧇l±5c/;z¾ fìe[™þo|uXŸ5{Tÿ—£l:ðÅ›n`ͨþ/GÍÐDØGMÕÿo jGÞ/Qýßøf°Ueú¿qzˆ&rÞüà™Ôÿï [&|7Eõãô.M‹ûìêÿƩÚ"îYÿ7N¬6~+ÓÿÿÄ ïĨþoœœÒø±Lÿ7¾>ì=Ù6?߃ŸæRûEõãÔ\‹B§ÕÿÓ»4Ž„õdTÿ7NïÒ8à½×ÿG`oÉô9rJcÐû‘£ú¿9¥ñE¯Cÿ—{–à>ÿ¦ÿËQ_5¾;¦ÿÁ‚ïÊÉ'Qý_ŽœÒx—kèÿrô.w„u{Tÿ—£o¤íã¹êÿrô.×z_ô9ê°ÆøéÿräžÆKdú¿õPcð}^3ú¿Ü¹°¹2ý_îØ©ÎÕÿåè÷Oô3×ÿ宇ýÅqú¿ÜU°™þ/w¬M¦ÿËQ‡56x>ý_î,XµLÿ—#§4š£ú¿1Óx¨çÓÿåÈ)ò} ÿË‘S÷ò^éÿr­°Ýü,õ9ê°Ædú¿}Tã¶>Cú¿½xã2ý_ŽœÒ¸‘{ÑÿåÖ€mÏçFüEõ¹a[Êô¹a«9Wÿ—Û¶‚Lÿ—#V—–éÿrÀâ^›þ/·ëœÞ†}ïêÿrËèçLÿ7öìS?sý_nØG®¡ÿË‘S¦½÷ú¿½KÄãôcßÀ2ŽÓÿ‘S†dú¿±a eú¿±"ìuϧÿ#§4¼â8ýߨsé#£ú¿±·KÕÿ ÿ’éÿÆè» ¿¿ŒêÿÆè!O¢ú¿±'a7™ËôcÄeÃõÖ ú¿±{aWËôcô. —™·ôc7À.tœþoìZØ9ŽÓÿýv†ùMÿ7F¬6œì^ôcÄjÃq2ýߨ°£œ«ÿ;Ö!ÓÿýÖd|èÿƈ™†Zï³þo¬V.Óÿ5“éÿÆèí0fôcôÓ »›õc³`;Ëôcÿ£Æœ¼ÿôcô) ;ðyŸ¢ú¿1zˆ†­}'êÿÆèí6ö¹×ÿm›%Óÿm [Û=ϱ—Õ}ÖôcÔ˜ Áß ÐgDÏš±Æ¯aôQýßy¦aq×Ðÿ- ‹ø>Ðÿ¦ÂóõñŽêÿFaäÐ>Þ¹Qýß(5R}·èµ¢ú¿Qj¤ú÷ÌGú¿Qú£ú¼kèÿFéSêG§ÿ¥ß¯ÿ_λxÆAÝI½dÆÁïÃè—¢ú¿Ñ7a¯:Wÿ7J}_ÿ${&6¢ú¿QÞÅõÀˆ¨þoôDØ}0â ªÿ}(d?å7ýß(9¥þ©ð;ë¨þoô¦ð|?1ýßèõÎ ˜þoôØUžOÿ7J=^Ä}iTÿ7JŸRï?ýßè9°³§ÿ¥«?ݺ]ÿ7JÞª?)ü›Ÿ¨þo”¼UÄ}ZTÿ7JMX”õ¤þo”Þ¾¾ÓóéÿFÉ)õÛ¤§êÿF‰Áú Þèñ¢ú¿Ñc`®¡ÿ=v„kèÿFéSêô|ú¿Qb¦~_×Õÿeß Ï×GŒGõÙIXð{Lb<ªÿ%§Ôoçùô£{Á¶tú¿QrJý&Þý_öK؆öú¿ì"Øz0êШþo”œR¿ºkèÿF‡­dléÿ²?–‘éÿFWvnPëÿF—…­(ÓÿeÉ)uß¿ú¿QòVÝçÞSý_öØG2ý_–X­{×û§ÿËÒÛ×Mz>ý_–üQ— {ö¨þ/Ë{¼Žúo~ðþÓÿeŸÙO׫ÿË’SêÞp ý_öaØ«2ý_–œR÷¢sõYrJÝÓ2ý_–œR÷¨÷êù{yÀ=ëÿ²ó`w9Nÿ—%§ÔÝâû@ÿ—í…õ9Nÿ—%§Ô]+ÓÿeÉ)uó\Cÿ—=v‰Lÿ—%ë·}Óÿeç® ~Tÿ—­…]þ ªÿË–;—ûÕÿeKaÇ ú¿ìÁ° Æ‡éÿ²ûÁÚ½§ú¿ìa¾ôYrO]Lÿ—]&<_õjTÿ—å9­k“éÿ²›Àþä^ôYrJÝÞ®«ÿËЃՕ:Wÿ— žÓCeú¿,9¥î^‡þ/»ì÷®¡ÿËÆaËô™ïa³dú¿ÌW°µýÜô™·`¿1¯êÿ2ÔHu«Àèû¢ú¿Lö¿ÔÿeÈ)u‹›+ô™Øb2ý_æÎ9½µß Ás ÿËÜûL¦ÿËÐÛ×~ä\ý_æ9Ø´Ïþ/“„1øLÿ—IÀ²°Ïaú¿Ì=°açêÿ2ÄLmÂg\ÿ—!fjß”éÿ2ÄLí«îEÿ—!fj_pœþ/CÌÔ>å8ý_†:¬ö1¨ÿË3µ÷;Nÿ—!fjïtœþ/CÌÔÞâžõ™£` §ÿËtÀ®õ|ú¿ y«vž÷@ÿ—9 v±Lÿ—!fjÏó:ô™C`gº†þ/CÌÔþÝ5ôj½Ú Ïcͨþ/Sû‹Ÿ‡þ/Co_Ûí\ý_†Þ¾¶M¦ÿËÐÛ×6Èô™ßÁªÝŸþ/C­W[â8ý_fØ!Þ+ý_†Þ¾vÿÅôzûÚ=Ãk‹éÿ2QØ®á1ý_šÞ¾vÇÅf¬±Mèúbñkl.Óÿ¥ß…m¾‹cú¿4yµv½0öcú¿t¶–Lÿ—¦w©]5ŒÕ˜þ/M=T»ñA¼ÄôéaK‚qú¿ô °ÕÂwbLÿ—&§Ô® Óÿ¥ŸÓ[ókèÿÒÀ> ß/1ý_ú<Ø¢ÐÿÅôiÞÏ5ï‡ù<¦ÿKÓÔ|~Óÿ¥ƒg2þ"¦ÿK5ÓîEÿ—&Ô,ôèÿÒÔ“5¯»?ý_šz²æÇéÿÒôö5Ï{ŸõécaO:Nÿ—ÞöZø½ULÿ—¦–¯!‡ömÓÿ¥k`O„y&¦ÿK—Á‡Óÿ¥éíkîuœþ/Mž©¹ÒçEÿ—Þv™Lÿ—&ÏÔ\à3©ÿK“gjΖéÿÒô5§Éôémaó9Õÿ¥·€ÆBLÿ—&ÏÔåºú¿ôÚ°çêÿÒ«ÁšBßÓÿ¥W„9wULÿ—^¹äí˜þ/½˜sÉÓ1ý_ŠÞ¾¦6üÍqLÿ—"ÏÔ¿“æ½Óÿ¥è÷köéÿRô.5;»?ý_êض<㼇cú¿Ô£°à;õ`Ïú¿uXÍ&^‡þ/µ6+¬-bú¿Ôë°udú¿Ô+°5œ«ÿK‘gjVòèÿROÀ–q®þ/EïR³„ãô©»a‹ù>Ðÿ¥nÓ[ý­Lÿ—êƒ}á\ý_ŠÞ¥úc×Ðÿ¥:ÃóõÓÿ¥š÷_™þ/ų[ýyø¾˜þ/E¨Nº®þ/EÌT'¼^ý_Š˜©~Óuõ©#a¯ùéÿRô.Õ/8Nÿ—j€=-Óÿ¥Êœ<ú¿Ô¡Î ˜þ/EïR}§÷@ÿ—Úv³Lÿ—"~«xú¿Ôΰk§ÿK‘gª¯p/ú¿ñQ}±ãô)òLuoاÄô)òLõ\×Ðÿ¥Ö‚ê\ý_jU؉2ý_ŠPý×=|Æ=áw@1ý_j=X[ø7î1ýßÈw°÷¢ÿ¡~©®õ}¥ÿyVþ–.¦ÿ™†â\ý_Š\V½¿ëêÿFÈeÕ{¹®þo„ëÿFÈoÕ«zÿô#Ás:›¹Ü“˜þo„ýß09¥2"{îç½T|þ¿1ýß0±Zñ¥Lÿ7L¬V,òÚôÃ{ÃÞ“éÿ†w‡dú¿áeaÁßçQÓÅôüÇ+>ôèÿ†é*Šáwà1ýßð†°·Ì—ú¿¡ïa¯Û—éÿ†¾„½b¡ÿ¦w©xÚëÕÿ o {Ô÷†þox6ì~ßaú¿ab¦âNëýßð°›}ýß0y«b¾Lÿ7ôìçêÿ†¨Ã*æyú¿¡ Ø%~nú¿¡ ¬×{¯ÿ„)Óÿ 3÷ÚôC<Ï•éÿ†è÷+Žq ýßP Öí3¤ÿ"oU´zú¿!j¤Šϧÿ¢«¨ò|ú¿¡a%ŽÓÿ QûTÐûõQÛÇôC=°™þoˆ>¥¢ÜgCÿ7DŸR|¿@¯Óÿ Í…í`>×ÿ ÛÚü«ÿ:¶™ï ýßÐѰ «ÿ¢gªX×w¢þo¨ öcZÿ7DüV¬â8ýß½KÅrŽÓÿ Ñ»T,å3¤ÿ¢·¯ˆÉôC{Îé-ÿÞ¹ú¿!j¤ò¯\Cÿ7DN)ÿ„{°$Lÿ7DÞ*ÿ@¦ÿÚ6åùôCôöå9™þoˆÞ¾<å^ôCÄGù Lÿ7´ìs·þo°£÷룷ŒéÿyvË_•éÿy®Ê_éÿ`OÉôƒÿý+ü›‹Ø73ÖxÖü«ÿƒ=î{Wÿ7˜…õ¹?ýß 9¥üŸ^‡þoðØ?¼?ÌX÷bçêÿ_†²¸þoP~–Lÿ7øìÔðŠëÿÉ)å'†Ÿ[\ÿ7xìXçêÿoÖqýß ñQ~røÛž¸þo÷xyðÛizиþo÷}yµsõƒÂJÃëëÿ‰òưV‰ëÿOv.1×ÿ Ò3•ï¾ÿâú¿AzûòÝÂØëÿéíËw”éÿÏ‚mÃ\ÆÄõƒ§Á6—éÿ+a³]Cÿ7xl½°ŠëÿéíË× ßCqýß ½}ùªaNŽëÿçÀ–눸þopØÒa__mÆq犯þóe?†ÿE\ÿ7Hß]öµçÓÿ ÒÛ—}Öüqýß uXÙ‡áß•ÇõƒÔaeE×Õÿ R‡•;Nÿ7¸,í8ýß }wÙ Lÿ7ÀsZ–û­¸þo€ç´lT¦ÿøörûqýßÀ{°ç¿‹ëÿ&aO¸†þo€˜){ÈkÓÿ /˨1ûø,âú¿E°Û½ú¿j²¼Wú¿b¿,ø?Rø,âú¿bµìJ™þoà Ø¥îYÿ7@o_v¾{Ñÿ Ü ;Ë=ëÿn‚ê8ýßÀõ°=Ÿþo€©ìXÇéÿ¨ÃÊŽtœþoàvX‡×¦ÿ o•5Éô§Àª½§ú¿úî²Rϧÿ §”ß¿Cqýß9¥l™þo€üV¶§Lÿ7@V¶[øwEqýßuXÙN¡ ëÿƒmë\ýßÀ°-Œ7ýßuXÙFŽÓÿ üÄà¡0ýßïû²u}çèÿ‚gwMÙ3ÖX1ü-S\ÿ7@Tök™þo`]د\Wÿ7°æœÞÒeú¿•a߸gýßÀr°Ï¼ú¿z—Òdú¿~ž«Ò÷½ý_?}w)5fß!0ý_ÿç°Œëêÿú©ÃJ‡\Wÿ×OVúŽãôý<Ï¥¯ËôýiØ+2ý_?uXéóîOÿ×ÿìI?7ý_?uXéÃÎÕÿõS‡•Þ+Óÿõ?»Ýóéÿú çN?®ÿë¿;œ|‡×ÿõŸ»6t¹qý_ÿ™°yáßcÇõý×Á.p]ý_?1SzŽ÷Eÿ×OÌ”žî¸ªkœãý¯ž±Æq2ý_?1Sz”sõýÄLi§×¦ÿë'fJ›eú¿þ.Xñ¦ÿë§+­tœþ¯Ÿš°ôÇéÿúéíK4.õýÔH¥ûú>ÐÿõS‡•ΑéÿúÿÛE¦ÿë_üF3xëÿï¿‘å¹ëÿúégJ·ÝW\ÿ×O?Sº‘Lÿ×OVºŽ¹Lÿ×O\–®inÔÿ%>€^”5ãú¿D6K¦ÿëâc ϧÿë'§”FÍ«ú¿½xÉwŽÓÿ%¾€}é{\ÿ—ø¶H¦ÿK%ïû.Öÿ%ˆ’‚÷Jÿ— O)“éÿÁ²0â/®ÿKœáùþ Óÿ%x&KÆCG×ÿ%^„½æºú¿½}É‹2ý_‚:¬ä™þ/AN)yL¦ÿKSJðèÿä”’»ÌÓú¿Ä|Ø­Þý_â*XŸçÓÿ%¨ÃJ®‘éÿÄGÉ<çêÿÔ>%7…¿i‹ëÿÔ%%óÃß¾Åõ â£d®çÓÿ%þ ;Å{ªÿKSJNð×ÿ%È)%Ç8Wÿ— >Jºeú¿D9¬Íóéÿä”’ßWú¿ÄŸ`UÆ–þ/Áû¾¤Äuõ ƒç*Èo×Âô ƒç*Èo×Àô‰­eÁú¿Äf°Ý]Cÿ·0ÛÆóéÿR«”lúظþ/±º,¸ý_bEØFîOÿ— .K~'Óÿ%ˆ™’µ\Wÿ·ðGØj~n×,öË¿_þýòï—¿üûåß/ÿ~ù÷Ë¿ÿïÿÿÿ­ bÀ{radio-beam-0.3.3/radio_beam/tests/data/m83.moment0.fits.gz0000644000175100001710000002044514025155256023756 0ustar runnerdocker00000000000000‹b¸ÌVm83.moment0.fitsí}SÛF»Æ¿Š†ÜöØF’å×g83HKŸRpÛôaFØÔØ’+Ézæô³Ÿ]É–lÙºï½o©1‡Æ“pàǵÚ]{iµº<}ÿá݉¦h[^Cm_ùÞLC-òµ·§ÃK-Œlolãm? ioN‡N?ðz‚gý¬íÈ֢癳’½Î?ž^j<]ð¼ùôÖ 4ÿnIv§Žº¾nåýztyøñär;ϼ³”7òý`ìzvähögg;N;ºÅ5¶è3 £)õ}p?;“U– Î8ÞÈÑf¾ëE9ÚÂ;vF©J%¾E´—_ß­¶?S·šf§otÄ«»­>’âm«‹¤ý Þjûk˜ýf¯Åæ½;?ûp.˜ölôô¸='¼3QàGG›øÞ½ÍÇq«9'Œ\{"x“e…¼;®ñVå­è[òħ]œ\ß^ü”ò £mv =~%ú~øóZ{çzŽ(_iwóÇ\ò9_ž%ïðøäò79¾ÔÞþ»½¥ýü1·#?:VZ`øFÎt ïä§ŸOÏÎ?n?3Õ÷\K¨ëùŸe1mülðºüpr´Ð7<ÿp~tr6̵ç´ï{¹p&[°xþæòû“óÆÇ¬ý™f[ïÍ^·eÈñþjz­/ÞjþmèRÛ³ö±±Röµ×‚÷[Z¿mËÒ%¯ß6ÍBÞoï?Y{±zF§g6õ^»Û/âý§÷þÇã†`fõÑîX³i´­žÕõ¯å7Éã¶$Ê~:µ£ÑƒëÝkLJÓ˜¼–_Šú0u£ÕÐ; £?Ô[Ë´{ÍžÕ]ÜÓËóF¯£kÔ±Ô—¢¥¶7rüÓâñåßÚ§©6ŒFHyýpz9<¿øM;:¼<Ô.‡‡CÑ¥¿¾‘¦¡OÖ'Œ†¡Mc`öfK;={{®]^ŸÔÜ©}ïƒÁhâØÞ7ß”âïâ½G÷ph†¥mYºÿŽì;î&ö}lc®®µÈ?y¢·¤o–ã=:<™ 41–7 1ò͡ӰnÌåwÿ÷ÑX¾-¿á±©™}ÝêiC1Kð¾.þšÑXýÙÕ~UWÞG7Q´½¹;¾¹¹9Ôuó&Õ÷±ÓÓïzöÍÇÎm»9 ÷X­…«oêÓ~+ôMmonOöø<{ù#?Þ0Èüçz³;wqúöÊ–7í@üÀ’g{Ï{¥xáìiµ~¿Lk¯ ïÎu&ãŒWº¼¶9žgWÆ›?¶wO«4ˆIÈ’XO¶=g’ŒùUð‘í­Ôoiž+ë#ªŽ—Låªã­ž3«àÝ9N:ÿ­¤~'îlêzSû³ä‰7JòäÇ‘?™O½XŸ0‡hÿÅôùó(tŰª:þ¼ÛóDƒ~¼—úÞÚ“Ð)}üþt?Ôªá =ú$µh4A”SïÒ¼ÅIIÔÇ­sÏ®o9k])2‰‘¯F‰>=--—gÇé­Ú? ÞÄr‚‰;u£jôÍg³^ÈãyrÀÏÆ9¼¢ý âüé­˜rJN¸Þ ™õ+䄇¹»“ú¬ÒÇO΂3^«4Oê»s£åñ›ˆ²—:~Rß OLøŸKñÄÀìÍ\g$ç½Z7kÎ\}òóeP)JÁò ì1Ûsè<¹ÞØ #;’-fÏó“CXÌk¼{r'€‹ãg(•â9ŸÅ`0–ï„Êçˆ'´…îŸK¿Ö*­Oú±ó¸l/JãÄ“ÕY%o¡OŒyn«ŒW ú*ã-"i`D{Iã³Ò<×Sïñö'†P}þñîÿi9ä‹Ã§tþÀx²NªäÙ?÷Æê~¨ƒŒWžcq™ÏG*¼¸ÌðD;ó#mÑßÔÚ È³?¯ðŒò<׳oÃTŸÒx…è[á5Œ²<1ß—–|Á+ß^$o—T»µCgäE•ñìäâÉ‚·gÏfKƒÀãÝp6YÌXUÇ{¬Ü ‹?ŸUTö£3³ƒrüº?ŽÓÈIµ¼OÌ-©y˜ úIz ò^@ ëÛ} êcä± o=UjÏ ‘Ç‚œ–?~òáN'‰¾Õá”Ù^ü§‰.OoåÛŸ<`Ë¢64?Ýœy÷¤±¥¯øtÄãŸ>˜í/|öFi{>Si„×îZÛ®WpÏGoçç#Lß®ÏGRß¶|—{>Ây´ö‡ðÈç#„G>!<òù+/õ|„ðÈç#ŒW8]àñÈç#„G>aí…z>Bx¹óQéú Ÿ?ùüµÚù£30º+Ÿì‰{Ø‘¼'1t¢dªôƹw=OÞÌŠÑ}=¦ã©6ß¼¿¼Œß–­uq’ø¶‘¼êñE79ìÈlXó䗆ܮ!ˆtñÑ™‰/SÞeü~Mÿ4ÝkÚôRþwúU˜üšƒZmµml{”·7hé@yãëç£HˬÄñ›©¾QÔ(~!¢P}¦ðy}q¯"×Nèé›åx»]?¤ o§ë‡p}¹õ*ú^I^þþÆnÝè×M£n¶Òò²Kå¥ÝO§PÞ¸;' áð|\¡ÖÓà<ÚzŸ„g<Úz$œG[ŸƒóhëUpÞ4Lj¸*}c_|"†ë„‡å‰8/£U£ïÞ=Dšè˜wUˬl<Í XÍ(|ku­¶:[É}ËÓcÐ §¾=¤ß˜ò¶þ€hg3?DO’+/~y—c ,¯.õÅS} Õë‹—ÒÌ–õ{U“øí .>¤úÄ×”ãT­>1ÞOíLß•^׌ºfÖµl¼¯kV]k×µN]ëÖµ^]ëÇ cñ·µøÛNþýoÊŸ_×Åï–K”›Ýßž§Y^9à×ãa?[ÿY¾Bxú„¹ãýR_ùñ%þ<[%Þ•n½h}Σ]ŸIxùùÂkö÷fks~ô¢ü}{`åõ•ò÷oçþÓ·kèËù{õ÷oçþ¾½9ß/åïÙß#<²¿GxdðÈþ;~TðÈþá‘ý=Â#û{„Gö÷ïÕù{…ò®ú{ãKû{…ñj§þ뿯Íßãým·þÑGö÷ØxOõ÷ìïÛ›ùÕ«ö÷A+??zQþ¾·y}¿”¿Gx;÷÷˜¾]û{D_Îß›¨¿Gx;÷÷B_~}D)ðÈþá‘ý=Â#û{„Gö÷Øñ£ú{„Gö÷ìïÙß#<²¿Gx¯Îß+”wÕß·¾´¿W¯vêï±þûÚü=Þßvëï}d÷TðÈþ¾?ÐÿQþ^¸Ð—ìïÛææõ…2þãíÚߣúvìï1}9ßúËXnÎãíÚßcú¨þ-/Ñߣýƒèï1ÕßK^•ù=Æ£ú{ŒGõ÷êï1Õßc<ª¿Çx¯Íß«”w—þÓ·kößWæïúÛNý=¦êïÑñžèï1Íßwº±¹^` _?›¸Ñ7ßjWqçuÇálŠòãÒrdíÏl%ýʸ֞ÜÑÃAma“±è9À°ÿ-ù7åþTÛŸzÑþï¶Žü}yòß'Žïí Â…6u½Û1›—ûï{­ÜîKÜ~þפ¼ô×Uß^¶¿¤<«fØcÿ : <Ú~Ò8¶Ÿ4Σí'óhûIã<Ú~Ò õAʇ^uûIãõýŸqmÿgEžòþÏÊú÷Æy´ýŸ^uû?ã<ÚþÏ8¶ÿ3ΣíÿŒóhû?ã<ÚþÏ ÜŸ´ÿ3Σíÿ¬¦O}ÿgœGÛÿYAiÿç„íŸHÛÿçÑöÆy´ýŸqmÿgœGÛÿçÑöVÖ§¸_³²¾Êx´ýŸ <¥ýŸUÛŸêþÏ1Ïʯ÷áïÿ¬ÆSßÿY§¾ÿ3Σíÿ¬ÎSÛÿçÑöVà‘öVÒGØÿYIaÿç„—_ÏÅßÿY§¾ÿ3ΣíÿŒóhû?ã<ÚþÏ8|ý«ÒþÏ8¶ÿ3Σíÿœð@¿KÏc-h?ýÇÂúvŸÇ‚úhÏ÷ÃyŒ<ä1òXÇÈcA^.ÕF«Õú«ÕkõÓúý×;âúhû3(Ô/=ßyŒ|ä1ò]ÇÈwáöBÏw‘þAÎw-ðù ô|ä1ò]ÇÈwA#ßyŒ|ÕGÌwa=ßEõó]ÇÈwq-ßUÑGÉwA#ß…û/=ßyŒ|×ó0z¾ òù.Ècä» ‘ï‚b¾ òù.¬žïÂ홞ï‚r¾‹ðÈù.Â#络ï"<ò~ ¼ŸVëyìñ¡ehÿÒŽ1ºiûû{ógD9ßÅÆj¾‹ðÈùngóù¥ò]„GÎw9ßExä|á‘ó]„GÎw9ßUÐGÊw15ßUÐGÊw9ßUáQò]5}êù.Â#ç»Xÿ¥æ»ÍýôK廜ï"j¾‹µgj¾‹ðÈù.Â#络ï*´?R«Ðþªä‘ó]5}•ñÈù®*O5ßUj„|á‘ó])ßíÀë×Èù.Â#绊<å|á‘ó]ŒGÍwq}´|×GËw9ßUà‘ò]„GÎw9ßExä|W¡ò]¬>èù.Ècä» “ïnì¿ëD¿?¯…»É;ë/ï%ìßÙ\o‘é#ï_ý~ë¾âÅ<^~1kݨ›õêï‹yk?Zí<gÿqPcÿqˆÇÙâqöãFÊ;qnŸã’êþÙ /²½±Œå}3&NÐøñûÆ~ö÷O_̵Â,•D3ôÉ\hù¹*ŽŸà¹SûÞ©Š7qÃ(ÖVÓ?î&óÏc9õžsæ…Y¿3÷óòÇ×Í8wK/Wßÿð'º-¯»|øÖÿüoÙòŠÉ¾3{p¦c7¨¤>’‡3ùÙåéÌ0B®¿AúÂQ`G£õöÒÛ¼,nÃÁ`ÌaúXË«õGX¦ý×;¸j׋ÇY^é×ÙÃr¦É{µ {ìÚ“GgâDÓhº|eüXË•ÿIy‹ïXÎD“xE>™gyF[|¾|´ÚÁög%}-ï²¼ùû·¾–÷u—7´¡+ÎÀƒØ·‰¿ºöV ˆƒ«Ó½ÙÊž¯Öêµ»­v·­›õŸÄÿèõŸã‘ÿ^kÿµßÐâŸÐ·ýïÏê3Võeë7¬¾ÙÓ…¼n{ÇúÌ}fæï»zÇêFߨõñk­êË®ïw»½^Ë2tëï8~†¾™¯½Þç''åÍϧ×Êë‹Þ/ëf¥¼ò­F6_­äéÉ…ú¶<ÏõU×ǖ羨úز~÷U×Ç–õŽ/©>ÌöÀÊ_?,qôV,ážo–ãAQZÞJVÿñôùÑݪ>9ãß+óU^<×[lã½æþ!Ë ž/wÝ?dþ'ÕVÞ×GËÜô¯¹>dyÁñ`×õÑ´ ú‘S£Êg _kÃøQèëÏ+Ü´§âÃMøàÑëÝiâüáßiÑóÌhCíGQ=!®/¿žÐwù<†¦úæ“Hs=ô±ÆM‰Z‘ª~ü6¯Ç/4ånX¾[ŠWx )-¯YÍ \}E!k/ÕÜ ÀÔ'>Fq#&êË7eT/SŸüb)¤o½ù⇓©/pîœÀY.ð¼ªË‘Û?Û R[ðÒq–ɓזW€×®Y0yölæx ߬°¾Dåø…³§©=KÊ+“9Œ·y½{…—®Ý—+éyw®BÇËú@׃XúÀ(Ìs×ÏAÞ ¸~ëÛýõsP_îúùFË2âøèÅë;A^þú¹Âõx˜G¿~·?úõsXýú¹äåýK™ëç qýä1®ŸÃ¼õëç+7çñxŒëÝz½ä1®wƒ¼õëÝWzÓ4:ýV¿®éâO:¾ˆÏ±¹ŠÊ‹S¿ôëç qý?~´ëç qýˆ×Ï1}Ôëç’—÷/E×Åt+þÂk†ieç7]®Â“ó¼N·×6{Zà?…Í Û_Þ¿”õ/y½¬ïeøB}L¿QÈËù •õ0ç7Š×‹òüF±>žß(ä1ýF!é7 òrüF1ç7 yL¿ñ8~£ÇôÅýwÃo˜f¿×ýÒ~X~£Êõz é7ª\¯ò˜~£Êõz’—¿¾úÂü†Yè¯x~£˜÷2ü ïEøb}<¿QÌËù SÍo<–ßÚËoúX~£˜ÇóÅ<žßËËðfþz^I¿QÌãù ÇðÅ<žß(æmúVÛè[_ØoõËòÅ<žß@ŽÙoóx~£˜Çó >†ß0óùÁKóÅ÷ƒñüFñý~/Ão¼äûA}L¿¡z?bKÑoTy?"Üþx~£ÊûAÓoTy?"V^Žßȯ·-ë7 yL¿ñ8~£Çô…¼-~£Û7¿ôõ ~y~£Çôðñ£ûBÓoò˜~ÒÇñ/<ß×ÿ½¶õ˜²¼/y}¬ÕÛœ¯¾êúèmúû—Tmks=×k®YÞ—Û?zòþA¸½¼ªúHÊ ÕG¼Ÿ¯8¯ÕHüfªoUR#ÛõÆæýûñ&HBäÚ”5}³o·û£'úòþ`MßN÷GÇõ妬:ìÿpm¿pœGÛÿYAßÚÛ¯çÑöãÆy´ýÂqíy• Ç´¿7ΣíŸó–› T¥oì‹OÄðšð°ýÍ^Þ߯ò2šº>ˆwo‹’ÞpUS¿?àÖöÆ3; o©ïG¦>z¨ÕµZám1)oñó³1¢ÞˆßþÔÚÝ®w—öøå]ŽYñíâ—ëñ?©>ô̪öâéK¦˜Ëú½ªÉ ‡í .>¤úÄ×UHäéKï5Hô!ûÁ+µç§Åð,yq©Çý$+oòµS²Ì<}3[ާK}åûoüy¶ßºxWî6X”ïâ<Ú~Œ=¹ÀÆól^³ß•å…üýÎýnk`åçç¥ü.ÂÛ¹ßÅôíÚï"úhÏÇyd¿‹ðÈ~ÓGõ»X{¦ú]„Gö»ìw±ãGõ»ìw[›ûG”ò»ìwÙï"¼WçwÊ›÷»FüÏò»ˆ¾û]DÙïâíy·~ÑGö»ìwÙï Þ?g¿–¤¼ù¼ó%ù]KßÜß§ŒßÅx»ö»¨¾û]L_Îïšuë/ôŠû/Æ£ú]ÉË÷ß2~ãQý.Æ£ú]ŒGõ»êwÑãGô»êw1Õïb<ªßÅxT¿‹ñvïwï*õ»*åÍû]3þçËø]•ñj—~í¿D¿«Ðžwêw1}T¿‹Ž§D¿‹ñ¨~·m ôüýÓPø\ÙËäžféŽÚ²ªåÇïûw§õa\kOîèá ¶0‹ÎX´G´KÁñgÚ]àOµý”çEû¿Û~8ò÷åiu?œ8¾·/ ?ÖÔõnÇl^î¿ïµn sº/‘ûÛU6¾,åßÑ^äñÛXoÿ²µõò[~=ƒ·óõò˜¾]¯—Gôùóh6•}²­V²?¢/÷°ã´;®ExÅ?çñŠÍ8'Æ“è!å­?>…Ã+6§<ž4ã·éÃC÷ô¦bù=Ê#Ý¿€Õõþ„G¾«Â‡ŸóxÅ“¯øaô<ù~ T_üðäÊÊûÉqfÉ£kó“ÕÞ¯Î8áur`¶6÷ƒ]ÏÿØ,oþø¥åuîDmÄ_Igt,¿”qXrIªO›þÌ "× Þ磭ëš÷œ|9“É烚Þ4í`:£ZüÎóÚ;aärƒlþßsðþíå"*lld… kçÅ!ßugœ(ÿØÙ|ë -Ÿòɇ¤ÄkY´xêuäÈ©ÆAÍG` [ÍŽ.þ«Õ£^¿Ùê ë%fE2~\TY–wæªîEB©ÕÄ·¿ œ??"?;ÞH~×–*)¬«èùBOŽ{ÿ%ïþ.kãýå@;ß‘éo&ߊ3Îd¢Ý:q{ïŒ7U¿Êé;³£¹h¬ !RR1/Ÿg/y#áë¼÷s(Óù$r““üÑ$-¯|Òâo¥”ŽR^}Gñï—“ëøÆ!£®eõ!š™&Ÿ ,Ôð!pÂ_@>Lvúcþ¦Ò¿K_Ü$å!;#ß{ž+Êôù%\±¾¾ìZ…ã銾ä¸ÄÆó#aäílü{Àg'¢ßS¡¾·n$oºu쩼+i¬­äróëÈâÓà@3š}«Ó¶´Ûgùôážnuµo’‘î[ÍŽÄ|Zkô:M½Ýêkߌûoôå×O.õ¹G™À‹Xƒ¬áÓx¼¿Íô=¹ÂuFNÒKq!øÇOžß ›ÓŽåÉóùË)¾7²×烋·ÊòVç[Ò \Õ>š­Ñb>œ–w1Á± ÓjßÈoX›×Ȭë£Ñ¹½[›Fw­žiŒoâÿX|{vþìºÔÃ%yåM>[”Z”÷}¯%Å%³3OÞŒùéÓ±× ƒ7vƒWOt$irâ'¨hÐó?”ßìyæ —/ûô|õö·8±Æ»7˨'7¯ŠÖ±É3å^Z+ÏÛ7Y™íöjæ”å‰y\dNLJÃÃÚµ¶?¥ÄF²á¤¼¸5E©¢p~{xñqxÓ^ ¦ÉY>¾ü IÓ¬®,Ž_~yçüq¡}mˆÉÞ-ÅÛÍõP‚¾õÈ);ìYžXv *¥/ÁÄ­z¯/JSI ô¿zºþ¯–ž­G×ÿj‰·’gI×­ìï·ëz·ÞÛx¿_7ÄÀx ¢Ïù<šÌ…©þ:Ô ÷ƒWçOy¼ÐŸ¸+‡ø¼x=ÿåäÝùÑéð·e/%9ˆq&(™žf†[u­—”l¼Êƒ/ÌüF.\¯L«e¥‘ Ùìhñ@±-LyE!ñUT¿­¢x޳öWMœXNßfœXȳâœ-qb–ïV'–Ò·%NÌê#‰ =Ÿ'´@±Œ¾-qb¦¯š8q‹>q¾³p{8N|ÈæÓ•Ä‰¥ôm‰W®§äòDËìm™'ê;a¶zV>PìYͶÞ.Ê×õmÉ7ÔâÄ}Õæ‰2ï”úº²ýI‰ùí‘^Ùùw£¼Eãß <ÿöÛæK?ÿ6º-ùÀzüœò ‹t¥Û³¹¹þŽÏ¿,}Àù÷màÆ¼¾&ŸÝmnn÷ðÊúïzy[›·‹í¿ÿ¯ûo¾~7òû÷_–>µþÛ´eþÇôߤ¼_Ï¿¯µÿŠúí_Þß}ÿU×§Ü»«È?ïhþËÑ÷%ç¿}_rþ{)&yRŸ!}†ð7ž—ºÛõ4,}”õ4-«'ê$žÿöZzWÌ…óó_³Ùë™Ý‚ùïª>c u7ëiŽ/µ“³cíÝù÷ÃÃ7ïNJÖǯ<¶ž<`ËsæÍh.­G½Ùjêšhž¢øí}ÃÜ׆!Ÿ5зdäRÒ×××××××××××××××××Wu¯ÿ$êdþ@sradio-beam-0.3.3/radio_beam/tests/data/ngc0925_na.fits.gz0000644000175100001710000001343714025155256023541 0ustar runnerdocker00000000000000‹eºÌVngc0925_na.fitsí sÚJ²Ç¿ÊÜݪk'1 Ñ‹Ç]Ÿ*ÂV¯ì$÷Ö­” ²­ H,$>Ÿ~{f$! (YœH‰ù—ÑôLOwÏ_òXë »*B×(☠ šÚÖ£½Z¬ÑÆFm2FënÍôÕ,êjj“¡öñ¯<}µÒ_ÐLßèhó²4¢)»£¯|ÔÆèžµ]<+d?ºdsaXkÓ¶ÖÑíëZªíƒ×;Ñ¡M à<èanO¿.Ô9Í÷jkBÚwÑ¿iÕy ¡ ß?WÆöv55¥/b/•5AíªãÖ`¼û®Bß ðA|‡ÖOFw=5/Þ 9VG÷êxÊQO嵕‰Z(ðp½.—¸ZIàvÄÊàaMFÜjC†Œ>ÁQêõJíö1^O!ýÇsœTâä_óñºúzƒ–+{j¬×¦õKmÞõ50/±ïûO•æÛ^e¸Þ;ËܬÉè{œo¿'¸^u8hÝ"o<óeÎ=Ôwœ€*êÒž>ÞHAmµË»W»#µs`~ð¨ò/ÉÀjkƒ+„Qw<‚7oÕ.ˆ%#~DéNF÷J—ñ¤²,ùÛ'¡Š2ßé³ÎHýgþw´2ÑW}¾ß„G‚Ó¾zYëpÔA-qøoi~7æ‘q½¤öœöáÝhÉ<^ˆ=´ó/§òˆ=rå=„œç‡HíAÜ4yët{ˆž=À•r5 >DžùûÔ<+8Y_sÓ2€ ¡Ú[ÝÚ`ôÁ¾\Òœæï%ü¦‚¡wð:Z·Û¨¶ÐŸ 4]SÍÈål×pY˜¯ÊHß ˆ®;j³KL)Äs ‘o”åop7é+<8¹M‰ö ™/ð­®2__tµ¾œCGÚ?6vc#Fikã?¯Ù«DG§õ'ÊP%FÁW¨ïe‘k6^Sé_t/®Ð?ïÜ0KW0ð•n‹ü“Û‰xrõmhàÅÑJܾ jÙÖfeÏI ó´ÒH;0$“òHRHRü2`åTixS{µ2æúÆ^¡…=££ZIà ó”!²—šÎ_£Û“Û7ÑzêHéß°Nã*Xlˆ¸Á7F ì=ÞpÞ‹á¡O.c\ã$™F ðŠE6ìU2Í—ˆ’Dhüµn'6ÇHu_Àì…Ÿ/éf¥ÏÑôY·,c]ßòZCeÔ»Äo…ºjâ! £¡CáåÚ˜¾IÑŒ'PDƒ@ƒü£Œ´ÉÑ7eÒ…K}ݲÃ<‘ñ8Â+Ïáë?e£õ³>³¿Aj­?¥áÉ>ÞxûÀêB (^õ3ÇÜ.ìü0 _àzçÆJ·¦‡s¤(^ñ¤2…µº°=Ì ´1,Jà»@OÁ«»×Ky“OÙy¨oÃtÓ§ææ…Íä)&°Rº­,§ê?Â{ÒáǧÛÕW# 2ì&ÆÜXOí%ñ,nA gxÜšTbÞÃ|z&Kå7só cnaZ0ÅÖ¤„¸1¿Â‡ql4ïóÚ¤°9µË•±^³+ôþ¤r@"´2fÛ鑚b7R»ª2& Ü…€!Ÿã„´«ˆËë îUR Œ^ε~²Õ|Ÿç_ε~ÚÕ<ÌË/ÞðxÑñ†˜¶}¥5¢Œ>ˆ×`=wÌARyëhL+•˜ DY°¾à®GñbíѺg˜Ä./Ò|Ò« ó¡~œ\_tn.àÛ{u4¾¦lèH÷EdTz×o îú§…•¾W»§Ž€î1ÀÜ>æ¬C<-Mw>ad|ß°ê?z4ç1S7‚—ݾ¼Å{-ömuýöåAû6‡Ðß™íË70ŽâUZúÜ|X‘pwM–ÚùvfÌñ­:Ô>H2ƃ»ñ÷œ$ÔÞ‰õš{âÅÕÞ(‰6Až—Ô0M`h.‚Þfn_sà†À¾÷ÆŠÚ Ü:QG*^§«Ü¸Dàu Ð{"K%CAêz|ómwwO‚q"Þk·4ÁÊ=Ó*£í×xd4ßã‘°7=OëóÌ!øOaw<Âî½d<×!pŒâx„˜Y{˜·³/£»E§úêØÙ²ã¿¯ßS†,#çüÞ` ³B@RªXÇtah¾ùò­Rà±èh±!)Q¼ñ‰ ¢ÆE•ñZ¥ÃÅ’JÂ#Ä»k*£‘ò %ðœIx#µ£ÐöÁôÉæÑ£±2Hº¡;ÛR)yŸBP;s6>uRr²Ihíƒñh¯Œ˜øÑ¼Ö3Iÿ”¯OàF©7¾‚/öò ÒÖ)9Wáƒ:cøÐ:{Áf˜§Â‚±²vY3ÀÉ&ûñ‰ütx~‡t!ÙŠ¾Ç .èBÚ=ȃÁò¬¾†sÒOd‹³Œ¾m¢xΑw´}?‡—æÈÿñþøó¤\$òQ¼"þ;—øï§æ¿ìñ“óßÔþ…î¦Å-`_Óò'ºÔ”Éñ"b,¯éðšÊX¥Ø¿ë¥ãUTˆö¶s¢·’ðØ.SñÊ/"t|ªª/Ïý‡—“}ó¾Þñ°«Mvõ+/ŸŠ%e8"þ”lÛàjƒç å ¶ñò›¿./¯ùËx!û²S‚öe'ÆÚ×ãåd_ÆË/~fÁ(aÅßÖÀ̶â÷SûYïQZBÓJ¯*Õu‘ýÇöÖhêvâ+Çy¾c¸2¾šöv=¡;ZOí~™Ý(q/ÆrÅ‚Ã4õù<CáÈM.â”0¯³ ³"~vÿ=W…šŸ×n¶ýÌþžßó÷ŒçþìØ˜ÚT±AÆöÝ=«J<›ë½ŠÙÙ+ô%ÿQ} '48|˜އZäÜ× /²ñ¢õ%ˆÿ=ô%ñjöxÞ+Ó—ðüNKÂô%¸Âñõ%u&–u%¸ÌÕ8hAºÐ—áú’B_’Œ—H_r4î/ô%QËù¹êK²Çç£/øY¬6¤$ú’}{œIý¹Ð—·o­Á%Ñ—œ­}ýý…ìúbßzC’¢x…¾¤Ð—ú’üB_’ö8¨/á } 9ÿ¼ô%¾½Ð—üÖú^Žâñß¹Ä?5ÿÍM_òÃòßB_Ãû9ú’SöSÎ[_2¹ïtoÒí7’ÝFH¶à¯$ïµñHrÔRÇAýBþþª ÿ#Üœì/j?@_® »ýÜú^D¯yáý æ¼ýY¬Ë|²ýçz©~ãúâVévàÝð2E‰-È£û ðÓ´@õ}OûmDÕäQI+ã_HÿnF¹× Ïɼò³›¹Ï•Pßá=:dM_̹æúâa¦y;½v~Œ¼“" µÏÑGxÂWmODÛ/H™ÏÝà;ÁêÏ£eg=;4ÌÓv¾VdŽ=‰'ÐæÙ@O+3Y¾êŒçLßÙ„º`o즪;Sc Ò¹éͪQ¼üÖóW§7ãQ¡7KÏ+ôf…Þì7Л!>ê+\Ä)9éÍDA¬ ~Ó‡ý3;5àŸc¼ß>//ÿìòøC äÓùgç÷Ï»zljÿìêëeÔÑ賩& ‚½¨ToÈA½¨Y/*1…¨S¼äùèB/z˜WèE ½h2^¡ÍW/ê.çg«Íoœ^”<õµÖ¸(^¬=Îd?ù×׋žð¼*j_IŽâzÂBOXè ó =aÚ#ä¯ =áyë w z¡'ü}ô„Áø@ õSœè~¡sÿ~}=á©zQ¡Å+ì{.öý©ùo~zÑ•ÿzÑÞOÒ‹ž°ŸòËêEë AÜkßÏÒ‹ dKlO/ŠålzÑj]%ÉÓ‹J–äsЋÊ>½¨\èEó^•^TÉ3£~¤^”ÌÔŸ¤ ?>Z/š}=uzQzÑô¼B/ZèE½hœ¾Ó{æe}>a‹Gô¢ayâo Ðï¥=ñþ…W¢ÇôxÂ^û$Q”êb5cû„_J/*4Äj¯Ð‹Òãìõ¢CÆ ÁKï•>±¬È,Kvº«‡yáúF“œò#ô¢ê„G%¢×°pi컋ÎdØP>Èœô¹Ç‰æjåïË£Mw@/нh3‡ö½½èiùQ¡ï,ô…¾3Ž—@ß)sG×x˜÷Êôqå´ƒ¼èýËèx-´ª×œ¸wOäþ¥·œŸÛþ寯O<Ù¾RïµØ÷×ߟŽÖŸ&µ¯ÔàjQ¼©?Å‚øNàëEúmenÀFÉx…þúÓBzçÿ}©ø,õ§MݚєŲ7ð±Z€Cü+E?Ì{]úSß‚~úS°Ç\·¾ÐíhðÈÄ&&Œ”ÕÒž'ÜZ ò =k¾zÖ„ñ}¤ZïµÄ“E¾kß$Ï?=[ûùBœ}¥"_(ò…"_(ò…ã¼"_(ò…"_(òçUqÿG¾÷œ¢8ïû?NÓ{ó{õìH½÷~þñëê½ETè½Óó ½w¡÷þõõÞ<®GÝÆEœæeÓ{ËÏÕë~ÞÖ{³Çïž±Þ[Dç¬÷îÃÕ~n¶ÚîõîÖ#vâüùÀgª§öxb¨}2ÅzM®Ö3¶O<3½÷Ý=„N{ñš]OKïïú^¼&“߇+ˆQlVú4yXæµ<½÷Ú}ܺU¢xzŸç6m|«u&ž¸=ðô^úB_Mׯô8o0j³z±/œª {5cuÞ¥=!R} tt(íò´žr3ÊæOk IÜ{^ã1Švþ*õMaõrÈqsn9óÀýy6Há–ð|÷T-Ÿ“ycµËRâ2Ž×‘+ΉG`»;4²\q×´”®ÖÌ­}¾|0cÃíN3p×ìîö{òàù÷ó²Ã×ëæ¿YºÞa¾×ëÖs.´lwíµow¿ ql'óTãjÌÏÆóúÀ»÷üÕéý׺š#Ï­‡e¥íõ_Î<'~v¤¾òy¤ŽS Pÿ©ÝîXû_õ’Æ¹×ÆH)½ÖAŸ¯ßÑÔnû”. òîî›;ýüx'Ÿ†žwFGo”IÄû”3¯£ÜÇù]o‡ò£¥DŸËwÏ™'äÌsæI9óäœyÕœyµœyõ|y˜Ë‡74ïÆïnDÎK8I†¸²¶ëe¬×h©¯Ix}mÂâ Ú°, ûñ$½PιÐÔ!¯Ý}ÌØq‘¼žÖ*“֭뱤ÛG?G½ÝŒK•äÑ'y¼´Ï‡ÛãUóáÑÕ÷í-[‘¼šãEtå|MÏëtÛ> åa‡‡³ðF ­\Òæy7Q§1H(¾W[>àé¼~sðQ;Í Õ%²ð>h}Úwn÷Á°¹r>CÖÙ缉3áJb™ëùÍG;3TÐÄÞ¥QÇ‘ Èùö{Üõ¶½á¥×¸:'¤½àPüGÚçðöÚXA-ËL¢Á5æ³c<ˆn{Š $õ:}¼£ÀxCCÿ‚VÆÚœmáº=à7ÓšÙßÖ^³«ôÿLZªúëC\½ù†ª1^ Þ΢ñ`½]ku9'¨·»¼¼öß/Xo÷5pWoï)ŸáÜx½™ËË©ÞÎxðÅIßpù*ñ—#¼Éˆðˆ·+_¹_à+—•7:šç ²c¹ßK8þ•ŒV:¡ÕՆǮw·ÊìAO î‡ö”1¬ù íáÛe4™÷CëmïHQâËU\¹ùÓ0–¨ƒþqÚä<˜tï?UšªÒ;Âc¤Ë²,ãj4ïÂcy®€­ƒÌ5z\Ù ú”͵óx£E¼ðvGà“–æwò µ±!ú ™SôUŸoÉã‚à ÀãMFJœÙ¿È 1|ÿåÅú—˜Ù»ÏËË¿0^ÿÂNŒÏ/'ÿÂxà^|Þ%î3}˜ Qóø’ËËë¶|C†K–â½÷±7è¡Ìã¯ÚàªQ¼ØñcÝ}^^ãñöôä&ÁñGNäâÇŸÇËiü1ÞÉË[ˆÇ–7gA ­rYxn8Éò"ˆšCA}Ý­ d[ó´A°¸-àžÃömÝù¤GyúÃúróý™S˜q4Èk‘z&‘øEGíJ·j÷"È@¼G} p òúÊGmLyˆöbøì´¼Öh¨}tybµÎÑ“óò:Âí»WºNû¤²ÌªÌ2RºöT˜hñþ f¶íóòòŒ—`=Jè<^Nú«a³5H¼?] ìOã†Ä5D>Š—Ÿ=\^ðv°ìö`¼$öà>ù±öðxþžl7@ÀWþOÎùK<î5ñ°WôÓþ !Þp¤*k,Âr¹V‡fÛÆÞ$ˆH”ÊGk;áöúü#«lðu/•ÖÜ\"{»¼×€ø&ïœæD«ÇyJô©×X©ä=×ùGú`Ày²öy‚[XBQpyoÑG‡™Ò;¯\–ªuïm2â‘öášà%"lçï?‡Äœy±Ä=ËȪþŠõ47.õÕtaZo€ùxysû×›ÿûöÈlÙù?Hi3û+ÜàÄ(^¼¿:î öyyé/¡¿úŸŸ{¼œÖÆË¯^âð"ë%'ð*Ý3 Ð^!¶B¤lë¹Ù-[¤«ŠäõY¢îìÔ8Yd¿¦‚,v;°“·“Û3Ÿ"Trƒ‘v£õi©hñnÎtÁݤ€1;Ro4RoQÛÿÅä ô öŸìme¢’¯À#ã½ÄUK˜Ö÷*¨cún_·-t³2 ë›9}F/p”‹Òl¿}îõ’ÆÁK­5þ´^hði=gX¹¿¶åïn¬Žúba! Þˆ¸²è£5èõÔþ¾£7û]væÆw“ÜáÁdf4'), ('BMIN', '>f4'), ('BPA', '>f4'), ('CHAN', '>i4'), ('POL', '>i4')]) beams['BMIN'] = [0.1,0.1001,0.09999,0.099999] # arcseconds beams['BMAJ'] = [0.2,0.2001,0.1999,0.19999] beams['BPA'] = [45.1,45.101,45.102,45.099] # degrees beams['CHAN'] = [0,0,0,0] beams['POL'] = [0,0,0,0] beams = fits.BinTableHDU(beams) beam = Beam.from_fits_bintable(beams) npt.assert_almost_equal(beam.minor.to(u.arcsec).value, 0.10002226, decimal=4) npt.assert_almost_equal(beam.major.to(u.arcsec).value, 0.19999751, decimal=4) npt.assert_almost_equal(beam.pa.to(u.deg).value, 45.10050065568665, decimal=4) @pytest.mark.skipif("not HAS_CASA") def test_from_casa_image(): # Extract from tar import tarfile fname_tar = data_path("NGC0925.bima.mmom0.image.tar.gz") tar = tarfile.open(fname_tar) tar.extractall(path=data_dir) tar.close() fname = data_path("NGC0925.bima.mmom0.image") bima_casa_beam = Beam.from_casa_image(fname) def test_attach_to_header(): fname = data_path("NGC0925.bima.mmom0.fits.gz") hdr = fits.getheader(fname) hdr_copy = hdr.copy() del hdr_copy["BMAJ"], hdr_copy["BMIN"], hdr_copy["BPA"] bima_beam = Beam.from_fits_header(fname) new_hdr = bima_beam.attach_to_header(hdr_copy) npt.assert_equal(new_hdr["BMAJ"], hdr["BMAJ"]) npt.assert_equal(new_hdr["BMIN"], hdr["BMIN"]) npt.assert_equal(new_hdr["BPA"], hdr["BPA"]) def test_beam_projected_area(): distance = 250 * u.pc major = 0.1 * u.rad beam = Beam(major, major, 30 * u.deg) beam_sr = (major**2 * 2 * np.pi / (8 * np.log(2))).to(u.sr) assert_quantity_allclose(beam_sr.value * distance ** 2, beam.beam_projected_area(distance)) def test_jtok(): major = 0.1 * u.rad beam = Beam(major, major, 30 * u.deg) freq = 1.42 * u.GHz conv_factor = u.brightness_temperature(beam.sr, freq) assert_quantity_allclose((1 * u.Jy).to(u.K, equivalencies=conv_factor), beam.jtok(freq)) def test_jtok_equiv(): major = 0.1 * u.rad beam = Beam(major, major, 30 * u.deg) freq = 1.42 * u.GHz conv_factor = u.brightness_temperature(beam.sr, freq) conv_beam_factor = beam.jtok_equiv(freq) assert_quantity_allclose((1 * u.Jy).to(u.K, equivalencies=conv_factor), (1 * u.Jy).to(u.K, equivalencies=conv_beam_factor)) assert_quantity_allclose((1 * u.K).to(u.Jy, equivalencies=conv_factor), (1 * u.K).to(u.Jy, equivalencies=conv_beam_factor)) def test_beamarea_equiv(): major = 0.1 * u.rad beam = Beam(major, major, 30 * u.deg) conv_factor = u.beam_angular_area(beam.sr) assert_quantity_allclose((1 * u.Jy / u.beam).to(u.Jy / u.sr, equivalencies=conv_factor), (1 * u.Jy / u.beam).to(u.Jy / u.sr, equivalencies=beam.beamarea_equiv)) assert_quantity_allclose((1 * u.Jy / u.sr).to(u.Jy / u.beam, equivalencies=conv_factor), (1 * u.Jy / u.sr).to(u.Jy / u.beam, equivalencies=beam.beamarea_equiv)) # Add a by-hand check value = (1 * u.Jy / u.sr).to(u.Jy / u.beam, equivalencies=conv_factor).value byhand_value = 1 * beam.sr.value npt.assert_allclose(value, byhand_value) def test_convolution(): # equations from: # https://github.com/pkgw/carma-miriad/blob/CVSHEAD/src/subs/gaupar.for # (github checkin of MIRIAD, code by Sault) major1 = 1 * u.deg minor1 = 0.5 * u.deg pa1 = 0.0 * u.deg beam1 = Beam(major1, minor1, pa1) major2 = 1 * u.deg minor2 = 0.75 * u.deg pa2 = 90.0 * u.deg beam2 = Beam(major2, minor2, pa2) alpha = (major1 * np.cos(pa1))**2 + (minor1 * np.sin(pa1))**2 + \ (major2 * np.cos(pa2))**2 + (minor2 * np.sin(pa2))**2 beta = (major1 * np.sin(pa1))**2 + (minor1 * np.cos(pa1))**2 + \ (major2 * np.sin(pa2))**2 + (minor2 * np.cos(pa2))**2 gamma = 2 * ((minor1**2 - major1**2) * np.sin(pa1) * np.cos(pa1) + (minor2**2 - major2**2) * np.sin(pa2) * np.cos(pa2)) s = alpha + beta t = np.sqrt((alpha - beta)**2 + gamma**2) conv_major = np.sqrt(0.5 * (s + t)) conv_minor = np.sqrt(0.5 * (s - t)) conv_pa = 0.5 * np.arctan2(- gamma, alpha - beta) conv_beam = beam1.convolve(beam2) assert_quantity_allclose(conv_major, conv_beam.major) assert_quantity_allclose(conv_minor, conv_beam.minor) assert_quantity_allclose(conv_pa, conv_beam.pa) def test_deconvolution(): # equations from: # https://github.com/pkgw/carma-miriad/blob/CVSHEAD/src/subs/gaupar.for # (github checkin of MIRIAD, code by Sault) major1 = 2.0 * u.deg minor1 = 1.0 * u.deg pa1 = 45.0 * u.deg beam1 = Beam(major1, minor1, pa1) major2 = 1 * u.deg minor2 = 0.5 * u.deg pa2 = 0.0 * u.deg beam2 = Beam(major2, minor2, pa2) alpha = (major1 * np.cos(pa1))**2 + (minor1 * np.sin(pa1))**2 - \ (major2 * np.cos(pa2))**2 - (minor2 * np.sin(pa2))**2 beta = (major1 * np.sin(pa1))**2 + (minor1 * np.cos(pa1))**2 - \ (major2 * np.sin(pa2))**2 - (minor2 * np.cos(pa2))**2 gamma = 2 * ((minor1**2 - major1**2) * np.sin(pa1) * np.cos(pa1) + (minor2**2 - major2**2) * np.sin(pa2) * np.cos(pa2)) s = alpha + beta t = np.sqrt((alpha - beta)**2 + gamma**2) deconv_major = np.sqrt(0.5 * (s + t)) deconv_minor = np.sqrt(0.5 * (s - t)) deconv_pa = 0.5 * np.arctan2(- gamma, alpha - beta) deconv_beam = beam1.deconvolve(beam2) assert_quantity_allclose(deconv_major, deconv_beam.major) assert_quantity_allclose(deconv_minor, deconv_beam.minor) assert_quantity_allclose(deconv_pa, deconv_beam.pa) def test_conv_deconv(): beam1 = Beam(10. * u.arcsec, 5. * u.arcsec, 30. * u.deg) beam2 = Beam(5. * u.arcsec, 3. * u.arcsec, 120. * u.deg) beam3 = beam1.convolve(beam2) assert beam2 == beam3.deconvolve(beam1) assert beam1 == beam3.deconvolve(beam2) assert beam1.convolve(beam2) == beam2.convolve(beam1) # Test multiplication and subtraction (i.e., convolution and deconvolution) # subtraction-as-deconvolution is deprecated. Check that one of the gives # the warning with warnings.catch_warnings(record=True) as w: assert beam2 == beam3 - beam1 assert len(w) == 1 assert w[0].category == RadioBeamDeprecationWarning assert str(w[0].message) == ("Subtraction-as-deconvolution is deprecated. " "Use division instead.") # Dividing should give the same thing assert beam2 == beam3 / beam1 assert beam1 == beam3 / beam2 assert beam3 == beam1 * beam2 @pytest.mark.parametrize(('major', 'minor', 'pa', 'return_pointlike'), [[maj, min, pa, ret] for maj, min, pa, ret in product([10], np.arange(1, 11), np.linspace(0, 180, 10), [True, False])]) def test_deconv_pointlike(major, minor, pa, return_pointlike): beam1 = Beam(major * u.arcsec, major * u.arcsec, pa * u.deg) if return_pointlike: point_beam = Beam(0 * u.deg, 0 * u.deg, 0 * u.deg) point_beam == beam1.deconvolve(beam1, failure_returns_pointlike=True) else: try: beam1.deconvolve(beam1, failure_returns_pointlike=False) except BeamError: pass def test_isfinite(): beam1 = Beam(10. * u.arcsec, 5. * u.arcsec, 30. * u.deg) assert beam1.isfinite # raises an exception because major < minor #beam2 = Beam(-10. * u.arcsec, 5. * u.arcsec, 30. * u.deg) #assert not beam2.isfinite beam3 = Beam(10. * u.arcsec, -5. * u.arcsec, 30. * u.deg) assert not beam3.isfinite @pytest.mark.parametrize(("major", "minor", "pa"), [(10, 10, 60), (10, 10, -120), (10, 10, -300), (10, 10, 240), (10, 10, 59), (10, 10, -121)]) def test_beam_equal(major, minor, pa): beam1 = Beam(10 * u.deg, 10 * u.deg, 60 * u.deg) beam2 = Beam(major * u.deg, minor * u.deg, pa * u.deg) assert beam1 == beam2 assert not beam1 != beam2 @pytest.mark.parametrize(("major", "minor", "pa"), [(10, 8, 60), (10, 8, -120), (10, 8, 240)]) def test_beam_equal_noncirc(major, minor, pa): ''' Beams with PA +/- 180 deg are equal ''' beam1 = Beam(10 * u.deg, 8 * u.deg, 60 * u.deg) beam2 = Beam(major * u.deg, minor * u.deg, pa * u.deg) assert beam1 == beam2 assert not beam1 != beam2 @pytest.mark.parametrize(("major", "minor", "pa"), [(10, 8, 60), (12, 10, 60), (12, 10, 59)]) def test_beam_not_equal(major, minor, pa): beam1 = Beam(10 * u.deg, 10 * u.deg, 60 * u.deg) beam2 = Beam(major * u.deg, minor * u.deg, pa * u.deg) assert beam1 != beam2 def test_from_aips_issue43(): """ regression test for issue 43 """ aips_fname = data_path("header_aips.hdr") aips_hdr = fits.Header.fromtextfile(aips_fname) aips_beam_hdr = Beam.from_fits_header(aips_hdr) npt.assert_almost_equal(aips_beam_hdr.pa.value, -15.06) def test_small_beam_convolution(): # regression test for #68 beam1 = Beam((0.1*u.arcsec).to(u.deg), (0.00001*u.arcsec).to(u.deg), 30*u.deg) beam2 = Beam((0.3*u.arcsec).to(u.deg), (0.00001*u.arcsec).to(u.deg), 120*u.deg) conv = beam1.convolve(beam2) np.testing.assert_almost_equal(conv.pa.to(u.deg).value, -60) def test_major_minor_swap(): with pytest.raises(ValueError) as exc: beam1 = Beam(minor=10. * u.arcsec, major=5. * u.arcsec, pa=30. * u.deg) assert "Minor axis greater than major axis." in exc.value.args[0] radio-beam-0.3.3/radio_beam/tests/test_beams.py0000644000175100001710000005151114025155256022250 0ustar runnerdocker00000000000000import numpy as np import numpy.testing as npt from astropy import units as u from astropy.io import fits import warnings import pytest from ..multiple_beams import Beams from ..beam import Beam from ..commonbeam import common_2beams, common_manybeams_mve from ..utils import InvalidBeamOperationError, BeamError from .test_beam import data_path def symm_beams_for_tests(): majors = [1, 1, 1, 2, 3, 4] * u.arcsec minors = majors pas = [0] * 6 * u.deg return Beams(major=majors, minor=minors, pa=pas), majors, minors, pas def asymm_beams_for_tests(): majors = [1, 1, 1, 2, 3, 4] * u.arcsec minors = majors / 2. pas = [-36, 20, 80, 41, -82, 11] * u.deg return Beams(major=majors, minor=minors, pa=pas), majors, minors, pas def load_commonbeam_comparisons(): common_beams = np.loadtxt(data_path("commonbeam_CASA_comparison.csv"), delimiter=',') return common_beams def test_beam_areas(): beams, majors = symm_beams_for_tests()[:2] areas = 2 * np.pi / (8 * np.log(2)) * (majors.to(u.rad)**2).to(u.sr) assert np.all(areas.value == beams.sr.value) assert np.all(beams.value == beams.sr.value) def test_beams_from_fits_bintable(): fname = data_path("m33_beams_bintable.fits.gz") bintable = fits.open(fname)[1] beams = Beams.from_fits_bintable(bintable) assert (beams.major.value == bintable.data['BMAJ']).all() assert (beams.minor.value == bintable.data['BMIN']).all() assert (beams.pa.value == bintable.data['BPA']).all() def test_beams_from_list_of_beam(): beams, majors = symm_beams_for_tests()[:2] new_beams = Beams(beams=[beam for beam in beams]) assert beams == new_beams abeams = asymm_beams_for_tests()[0] new_abeams = Beams(beams=[beam for beam in abeams]) assert abeams == new_abeams def test_beams_equality_beams(): beams, majors = symm_beams_for_tests()[:2] assert beams == beams assert not beams != beams abeams, amajors = asymm_beams_for_tests()[:2] assert not (beams == abeams) assert beams != abeams def test_beams_equality_beam(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beam = Beam(1 * u.arcsec) assert np.all(beams == beam) assert not np.any(beams != beam) @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_equality_fail(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beams == 2 @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_notequality_fail(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beams != 2 @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_equality_fail_shape(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) assert np.all(beams == beams[1:]) @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_add_fail(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beams + 2 @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_sub_fail(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beams - 2 @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_mult_fail(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beams * 2 @pytest.mark.xfail(raises=InvalidBeamOperationError, strict=True) def test_beams_div_fail(): # Test whether all are equal to a single beam beams = Beams([1.] * 5 * u.arcsec) beams / 2 def test_beams_mult_convolution(): beams, majors = asymm_beams_for_tests()[:2] beam = Beam(1 * u.arcsec) conv_beams = beams * beam individ_conv_beams = [beam_i.convolve(beam) for beam_i in beams] new_beams = Beams(beams=individ_conv_beams) assert conv_beams == new_beams def test_beams_div_deconvolution(): beams, majors = asymm_beams_for_tests()[:2] beam = Beam(0.25 * u.arcsec) deconv_beams = beams / beam individ_deconv_beams = [beam_i.deconvolve(beam) for beam_i in beams] new_beams = Beams(beams=individ_deconv_beams) assert deconv_beams == new_beams def test_indexing(): beams, majors = symm_beams_for_tests()[:2] assert hasattr(beams[slice(0, 3)], 'major') assert np.all(beams[slice(0, 3)].major.value == majors[:3].value) assert np.all(beams[slice(0, 3)].minor.value == majors[:3].value) assert hasattr(beams[:3], 'major') assert np.all(beams[:3].major.value == majors[:3].value) assert np.all(beams[:3].minor.value == majors[:3].value) assert hasattr(beams[3], 'major') assert beams[3].major.value == 2 assert beams[3].minor.value == 2 assert isinstance(beams[4], Beam) # Also test int64 chan = np.int64(3) assert hasattr(beams[chan], 'major') assert beams[chan].major.value == 2 assert beams[chan].minor.value == 2 assert isinstance(beams[chan], Beam) mask = np.array([True, False, True, False, True, True], dtype='bool') assert hasattr(beams[mask], 'major') assert np.all(beams[mask].major.value == majors[mask].value) def test_average_beams(): beams, majors = symm_beams_for_tests()[:2] assert np.all(beams.average_beam().major.value == majors.mean().value) mask = np.array([True, False, True, False, True, True], dtype='bool') assert np.all(beams[mask].average_beam().major.value == majors[mask].mean().value) @pytest.mark.parametrize(("beams", "majors", "minors", "pas"), [symm_beams_for_tests(), asymm_beams_for_tests()]) def test_largest_beams(beams, majors, minors, pas): assert beams.largest_beam().major.value == majors.max().value assert beams.largest_beam().minor.value == minors.max().value # Slice the object mask = np.array([True, False, True, False, True, True], dtype='bool') assert beams[mask].largest_beam().major.value == majors[mask].max().value assert beams[mask].largest_beam().minor.value == minors[mask].max().value # Apply a mask only for the operation assert beams.largest_beam(mask).major.value == majors[mask].max().value assert beams.largest_beam(mask).minor.value == minors[mask].max().value @pytest.mark.parametrize(("beams", "majors", "minors", "pas"), [symm_beams_for_tests(), asymm_beams_for_tests()]) def test_smallest_beams(beams, majors, minors, pas): assert beams.smallest_beam().major.value == majors.min().value assert beams.smallest_beam().minor.value == minors.min().value # Slice the object mask = np.array([True, False, True, False, True, True], dtype='bool') assert beams[mask].smallest_beam().major.value == majors[mask].min().value assert beams[mask].smallest_beam().minor.value == minors[mask].min().value # Apply a mask only for the operation assert beams.smallest_beam(mask).major.value == majors[mask].min().value assert beams.smallest_beam(mask).minor.value == minors[mask].min().value @pytest.mark.parametrize(("beams", "majors", "minors", "pas"), [symm_beams_for_tests(), asymm_beams_for_tests()]) def test_extrema_beams(beams, majors, minors, pas): extrema = beams.extrema_beams() assert extrema[0].major.value == majors.min().value assert extrema[0].minor.value == minors.min().value assert extrema[1].major.value == majors.max().value assert extrema[1].minor.value == minors.max().value # Slice the object mask = np.array([True, False, True, False, True, True], dtype='bool') extrema = beams[mask].extrema_beams() assert extrema[0].major.value == majors[mask].min().value assert extrema[0].minor.value == minors[mask].min().value assert extrema[1].major.value == majors[mask].max().value assert extrema[1].minor.value == minors[mask].max().value # Apply a mask only for the operation extrema = beams.extrema_beams(mask) assert extrema[0].major.value == majors[mask].min().value assert extrema[0].minor.value == minors[mask].min().value assert extrema[1].major.value == majors[mask].max().value assert extrema[1].minor.value == minors[mask].max().value @pytest.mark.parametrize("majors", [[1, 1, 1, 2, np.NaN, 4], [0, 1, 1, 2, 3, 4]]) def test_beams_with_invalid(majors): majors = np.asarray(majors) * u.arcsec beams = Beams(major=majors) # Average assert beams.average_beam().major.value == np.nanmean( majors[np.nonzero(majors)]).value # Largest assert beams.largest_beam().major.value == np.nanmax(majors).value # Smallest assert beams.smallest_beam().major.value == np.nanmin( majors[np.nonzero(majors)]).value # Extrema extrema = beams.extrema_beams() assert extrema[0].major.value == np.nanmin( majors[np.nonzero(majors)]).value assert extrema[1].major.value == np.nanmax(majors).value # Additional masking mask = np.array([True, False, True, False, True, True], dtype='bool') if np.isnan(majors).any(): bad_mask = np.isfinite(majors) else: bad_mask = majors.value != 0 combined_mask = np.logical_and(mask, bad_mask) # Average assert beams[mask].average_beam().major.value == np.nanmean( majors[combined_mask]).value # Largest assert beams[mask].largest_beam().major.value == np.nanmax( majors[combined_mask]).value # Smallest assert beams[mask].smallest_beam().major.value == np.nanmin( majors[combined_mask]).value # Extrema extrema = beams[mask].extrema_beams() assert extrema[0].major.value == np.nanmin(majors[combined_mask]).value assert extrema[1].major.value == np.nanmax(majors[combined_mask]).value def test_beams_iter(): beams, majors = symm_beams_for_tests()[:2] # Ensure iterating through yields the same as slicing for i, beam in enumerate(beams): assert beam == beams[i] # @pytest.mark.parametrize('comp_vals', # [vals for vals in load_commonbeam_comparisons()]) # def test_commonbeam_casa_compare(comp_vals): # # These are the common beam parameters assuming 2 beams: # # 1) 3"x3" # # 2) 4"x2.5", varying the PA in 1 deg increments from 0 to 179 deg # # See data/generate_commonbeam_table.py # pa, com_major, com_minor, com_pa = comp_vals # beams = Beams(major=[3, 4] * u.arcsec, minor=[3, 2.5] * u.arcsec, # pa=[0, pa] * u.deg) # common_beam = beams.common_beam() # # npt.assert_almost_equal(common_beam.major.value, com_major) # # npt.assert_almost_equal(common_beam.minor.value, com_minor) # npt.assert_almost_equal(common_beam.pa.to(u.deg).value, com_pa) # npt.assert_almost_equal(common_beam.pa.to(u.deg).value, pa) def test_common_beam_smallcircular(): ''' Simple solution if the smallest beam is circular with a radius larger: Major axis is from the largest beam, minor axis is the radius of the smaller, and the PA is from the largest beam. ''' for pa in [0., 18., 68., 122.]: beams = Beams(major=[3, 4] * u.arcsec, minor=[3, 2.5] * u.arcsec, pa=[0, pa] * u.deg) targ_beam = Beam(4 * u.arcsec, 3 * u.arcsec, pa * u.deg) assert targ_beam == beams.common_beam() def test_commonbeam_notlargest(): beams = Beams(major=[3, 4] * u.arcsec, minor=[3, 2.5] * u.arcsec) target_beam = Beam(major=4 * u.arcsec, minor=3 * u.arcsec) assert beams.common_beam() == target_beam def test_commonbeam_largest(): ''' commonbeam is the largest in this set. ''' beams, majors = symm_beams_for_tests()[:2] assert beams.common_beam() == beams.largest_beam() # With masking mask = np.array([True, False, True, True, True, False], dtype='bool') assert beams[mask].common_beam() == beams[mask].largest_beam() assert beams.common_beam(mask) == beams.largest_beam(mask) # Implements the same test suite used in CASA def casa_commonbeam_suite(): cases = [] # https://open-bitbucket.nrao.edu/projects/CASA/repos/casa/browse/code/imageanalysis/ImageAnalysis/test/tCasaImageBeamSet.cc # In some cases, I find smaller common beams than are listed in the CASA # tests. The values for the CASA tests are commented out. # 1 cases.append((Beams(major=[4] * 2 * u.arcsec, minor=[2] * 2 * u.arcsec, pa=[0, 60] * u.deg), Beam(major=4.4812 * u.arcsec, minor=3.2883 * u.arcsec, pa=30.0 * u.deg))) # Beam(major=4.4856 * u.arcsec, minor=3.2916 * u.arcsec, # pa=30.0 * u.deg))) # 2 cases.append((Beams(major=[4] * 2 * u.arcsec, minor=[2] * 2 * u.arcsec, pa=[20, 80] * u.deg), Beam(major=4.4812 * u.arcsec, minor=3.2883 * u.arcsec, pa=50.0 * u.deg))) # Beam(major=4.4856 * u.arcsec, minor=3.2916 * u.arcsec, # pa=50.0 * u.deg))) # 3 cases.append((Beams(major=[4] * 2 * u.arcsec, minor=[2] * 2 * u.arcsec, pa=[1, 89] * u.deg), Beam(major=4.042 * u.arcsec, minor=3.958 * u.arcsec, pa=45.0 * u.deg))) # 4 cases.append((Beams(major=[4] * 2 * u.arcsec, minor=[2] * 2 * u.arcsec, pa=[0, 90] * u.deg), Beam(major=4 * u.arcsec, minor=4 * u.arcsec, pa=0.0 * u.deg))) # 5 cases.append((Beams(major=[4, 1.5] * u.arcsec, minor=[2, 1] * u.arcsec, pa=[0, 90] * u.deg), Beam(major=4 * u.arcsec, minor=2 * u.arcsec, pa=0.0 * u.deg))) # 6 cases.append((Beams(major=[8, 4] * u.arcsec, minor=[1, 1] * u.arcsec, pa=[0, 20] * u.deg), Beam(major=8.3684 * u.arcsec, minor=1.6253 * u.arcsec, pa=2.7679 * u.deg))) # Beam(major=8.377 * u.arcsec, minor=1.628 * u.arcsec, # pa=2.7679 * u.deg))) # 7 cases.append((Beams(major=[4, 8] * u.arcsec, minor=[1, 1] * u.arcsec, pa=[0, 20] * u.deg), Beam(major=8.369 * u.arcsec, minor=1.626 * u.arcsec, pa=17.232 * u.deg))) # 10 cases.append((Beams(major=[4, 1] * u.arcsec, minor=[2, 1] * u.arcsec, pa=[0, 0] * u.deg), Beam(major=4 * u.arcsec, minor=2 * u.arcsec, pa=0.0 * u.deg))) return cases @pytest.mark.parametrize(("beams", "target_beam"), casa_commonbeam_suite()) def test_commonbeam_angleoffset(beams, target_beam): # https://open-bitbucket.nrao.edu/projects/CASA/repos/casa/browse/code/imageanalysis/ImageAnalysis/test/tCasaImageBeamSet.cc#447 common_beam = beams.common_beam() # Order shouldn't matter common_beam_rev = beams[::-1].common_beam() assert common_beam == common_beam_rev npt.assert_allclose(common_beam.major.value, target_beam.major.value, rtol=1e-3) npt.assert_allclose(common_beam.minor.value, target_beam.minor.value, rtol=1e-3) npt.assert_allclose(common_beam.pa.to(u.deg).value, target_beam.pa.value, rtol=1e-3) def casa_commonbeam_suite_multiple(): cases = [] # 8 cases.append((Beams(major=[4] * 4 * u.arcsec, minor=[2] * 4 * u.arcsec, pa=[0, 60, 20, 40] * u.deg), Beam(major=4.48904471492 * u.arcsec, minor=3.28268221138 * u.arcsec, pa=30.0001561178 * u.deg))) # This is the beam size in the CASA tests. The MVE method finds a slightly # smaller beam area, so that's what is tested against above and below. # Beam(major=4.485 * u.arcsec, minor=3.291 * u.arcsec, # pa=30 * u.deg))) # 9 cases.append((Beams(major=[4] * 4 * u.arcsec, minor=[2] * 4 * u.arcsec, pa=[0, 20, 40, 60] * u.deg), Beam(major=4.48904471492 * u.arcsec, minor=3.28268221138 * u.arcsec, pa=30.0001561178 * u.deg))) return cases @pytest.mark.parametrize(("beams", "target_beam"), casa_commonbeam_suite_multiple()) def test_commonbeam_multiple(beams, target_beam): # https://open-bitbucket.nrao.edu/projects/CASA/repos/casa/browse/code/imageanalysis/ImageAnalysis/test/tCasaImageBeamSet.cc#447 common_beam = beams.common_beam(epsilon=1e-4) # The above should be using the MVE method common_beam_check = common_manybeams_mve(beams, epsilon=1e-4) assert common_beam == common_beam_check npt.assert_almost_equal(common_beam.major.to(u.arcsec).value, target_beam.major.value, decimal=6) npt.assert_almost_equal(common_beam.minor.to(u.arcsec).value, target_beam.minor.value, decimal=6) npt.assert_allclose(common_beam.pa.to(u.deg).value, target_beam.pa.value, rtol=1e-3) @pytest.mark.parametrize(("beams", "target_beam"), casa_commonbeam_suite()) def test_commonbeam_methods(beams, target_beam): epsilon = 5e-4 tolerance = 1e-4 two_beam_method = common_2beams(beams) many_beam_method = common_manybeams_mve(beams, epsilon=epsilon, tolerance=tolerance) # Good to ~5x the given epsilon npt.assert_allclose(two_beam_method.major.to(u.arcsec).value, many_beam_method.major.to(u.arcsec).value, rtol=3e-3) npt.assert_allclose(two_beam_method.minor.to(u.arcsec).value, many_beam_method.minor.to(u.arcsec).value, rtol=3e-3) # Only test if the beam is circ_check = not two_beam_method.iscircular(rtol=3e-3) or \ not many_beam_method.iscircular(rtol=3e-3) if circ_check: # The pa can be sensitive to small changes so give it a larger # acceptable tolerance range. npt.assert_allclose(two_beam_method.pa.to(u.deg).value, many_beam_method.pa.to(u.deg).value, rtol=5e-3) def test_catch_common_beam_opt(): ''' The optimization method is close to working, but requires more testing. Ensure it cannot be used. ''' beams = Beams(major=[4] * 4 * u.arcsec, minor=[2] * 4 * u.arcsec, pa=[0, 20, 40, 60] * u.deg) with pytest.raises(NotImplementedError): beams.common_beam(method='opt') def test_major_minor_swap(): with pytest.raises(ValueError) as exc: beams = Beams(minor=[10.,5.] * u.arcsec, major=[5., 5.] * u.arcsec, pa=[30., 60.] * u.deg) assert "Minor axis greater than major axis." in exc.value.args[0] def test_common_beam_mve_auto_increase_epsilon(): ''' Here's a case where the default MVE parameters fail. By slowly increasing the epsilon* value, we get a common beam the can be deconvolved correctly over the set. * epsilon is the small factor added to the ellipse perimeter radius: radius * (1 + epsilon). The solution is then marginally larger than the true optimal solution, but close enough for effectively all use cases. ''' major = [8.517199, 8.513563, 8.518497, 8.518434, 8.528561, 8.528236, 8.530046, 8.530528, 8.530696, 8.533117] * u.arcsec minor = [5.7432523, 5.7446027, 5.7407207, 5.740814, 5.7331843, 5.7356524, 5.7338963, 5.733251, 5.732933, 5.73209] * u.arcsec pa = [-32.942623, -32.931957, -33.07815, -33.07532, -33.187653, -33.175243, -33.167213, -33.167244, -33.170418, -33.180233] * u.deg beams = Beams(major=major, minor=minor, pa=pa) err_str = 'Could not find common beam to deconvolve all beams.' with pytest.raises(BeamError, match=err_str): com_beam = beams.common_beam(method='pts', epsilon=5e-4, auto_increase_epsilon=False) # Force running into the max iteration of epsilon increases. err_str = 'Could not increase epsilon to find common beam.' with pytest.raises(BeamError, match=err_str): com_beam = beams.common_beam(method='pts', epsilon=5e-4, max_iter=2, max_epsilon=6e-4, auto_increase_epsilon=True) # Should run when epsilon is allowed to increase a bit. com_beam = beams.common_beam(method='pts', epsilon=5e-4, auto_increase_epsilon=True, max_epsilon=1e-3) radio-beam-0.3.3/radio_beam/tests/test_kernels.py0000644000175100001710000000266414025155256022631 0ustar runnerdocker00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .. import beam as radio_beam from astropy import units as u import numpy.testing as npt import numpy as np import pytest from pkg_resources import parse_version from astropy.version import version SIGMA_TO_FWHM = radio_beam.SIGMA_TO_FWHM min_astropy_version = parse_version("1.1") @pytest.mark.skipif(parse_version(version) < min_astropy_version, reason="Must have astropy version >1.1") def test_gauss_kernel(): fake_beam = radio_beam.Beam(10) # Let pixscale be 0.1 deg/pix kernel = fake_beam.as_kernel(0.1*u.deg) direct_kernel = \ radio_beam.EllipticalGaussian2DKernel(100. / SIGMA_TO_FWHM, 100. / SIGMA_TO_FWHM, 0.0) npt.assert_allclose(kernel.array, direct_kernel.array) @pytest.mark.skipif(parse_version(version) < min_astropy_version, reason="Must have astropy version >1.1") def test_tophat_kernel(): fake_beam = radio_beam.Beam(10) # Let pixscale be 0.1 deg/pix kernel = fake_beam.as_tophat_kernel(0.1*u.deg) direct_kernel = \ radio_beam.EllipticalTophat2DKernel(100. / (SIGMA_TO_FWHM/np.sqrt(2)), 100. / (SIGMA_TO_FWHM/np.sqrt(2)), 0.0) npt.assert_allclose(kernel.array, direct_kernel.array) radio-beam-0.3.3/radio_beam/utils.py0000644000175100001710000001776214025155256020132 0ustar runnerdocker00000000000000 import math import numpy as np import astropy.units as u DEG2RAD = math.pi / 180. class BeamError(Exception): """docstring for BeamError""" pass class InvalidBeamOperationError(Exception): pass class RadioBeamDeprecationWarning(Warning): pass def deconvolve_optimized(beamprops1, beamprops2, failure_returns_pointlike=False): """ An optimized, non-Quantity version of beam deconvolution. Because no unit conversions are handled, the inputs MUST be in degrees for the major, minor, and position angle. Parameters ---------- beamprops1: dict Dictionary with keys 'BMAJ', 'BMIN', and 'BPA' for the beam to deconvolve from. Can be produced with `~radio_beam.Beam.to_fits_keywords`. beamprops2: dict Same as `beamprops1` for the second beam. failure_returns_pointlike : bool, optional Return a point beam (zero area) when deconvolution fails. If `False`, this will instead raise a `~radio_beam.utils.BeamError` when deconvolution fails. Returns ------- new_major : float Deconvolved major FWHM. new_minor : float Deconvolved minor FWHM. new_pa : float Deconvolved position angle. """ # blame: https://github.com/pkgw/carma-miriad/blob/CVSHEAD/src/subs/gaupar.for # (githup checkin of MIRIAD, code by Sault) maj1 = beamprops1['BMAJ'] min1 = beamprops1['BMIN'] pa1 = beamprops1['BPA'] * DEG2RAD maj2 = beamprops2['BMAJ'] min2 = beamprops2['BMIN'] pa2 = beamprops2['BPA'] * DEG2RAD alpha = ((maj1 * math.cos(pa1))**2 + (min1 * math.sin(pa1))**2 - (maj2 * math.cos(pa2))**2 - (min2 * math.sin(pa2))**2) beta = ((maj1 * math.sin(pa1))**2 + (min1 * math.cos(pa1))**2 - (maj2 * math.sin(pa2))**2 - (min2 * math.cos(pa2))**2) gamma = 2 * ((min1**2 - maj1**2) * math.sin(pa1) * math.cos(pa1) - (min2**2 - maj2**2) * math.sin(pa2) * math.cos(pa2)) s = alpha + beta t = math.sqrt((alpha - beta)**2 + gamma**2) # Deal with floating point issues # This matches the arcsec**2 check for deconvolve below # Difference is we keep things in deg^2 here atol_t = np.finfo(np.float64).eps / 3600.**2 # To deconvolve, the beam must satisfy: # alpha < 0 alpha_cond = alpha + np.finfo(np.float64).eps < 0 # beta < 0 beta_cond = beta + np.finfo(np.float64).eps < 0 # s < t st_cond = s < t + atol_t if alpha_cond or beta_cond or st_cond: if failure_returns_pointlike: return 0., 0., 0. else: raise BeamError("Beam could not be deconvolved") else: new_major = math.sqrt(0.5 * (s + t)) new_minor = math.sqrt(0.5 * (s - t)) # absolute tolerance needs to be <<1 microarcsec atol = 1e-7 / 3600. if (math.sqrt(abs(gamma) + abs(alpha - beta))) < atol: new_pa = 0.0 else: new_pa = 0.5 * math.atan2(-1. * gamma, alpha - beta) # In the limiting case, the axes can be zero to within precision # Add the precision level onto each axis so a deconvolvable beam # is always has beam.isfinite == True new_major += np.finfo(np.float64).eps new_minor += np.finfo(np.float64).eps return new_major, new_minor, new_pa def deconvolve(beam, other, failure_returns_pointlike=False): """ Deconvolve a beam from another Parameters ---------- beam : `Beam` The defined beam. other : `Beam` The beam to deconvolve from this beam failure_returns_pointlike : bool Option to return a pointlike beam (i.e., one with major=minor=0) if the second beam is larger than the first. Otherwise, a ValueError will be raised Returns ------- new_beam : `Beam` The convolved Beam Raises ------ failure : ValueError If the second beam is larger than the first, the default behavior is to raise an exception. This can be overridden with failure_returns_pointlike """ # The header keywords handle the conversions to degree for BMAJ, BMIN, BPA. beamprops1 = beam.to_header_keywords() beamprops2 = other.to_header_keywords() return deconvolve_optimized(beamprops1, beamprops2, failure_returns_pointlike=failure_returns_pointlike) def convolve(beam, other): """ Convolve one beam with another. Parameters ---------- other : `Beam` The beam to convolve with Returns ------- new_beam : `Beam` The convolved Beam """ # blame: https://github.com/pkgw/carma-miriad/blob/CVSHEAD/src/subs/gaupar.for # (github checkin of MIRIAD, code by Sault) alpha = ((beam.major * np.cos(beam.pa))**2 + (beam.minor * np.sin(beam.pa))**2 + (other.major * np.cos(other.pa))**2 + (other.minor * np.sin(other.pa))**2) beta = ((beam.major * np.sin(beam.pa))**2 + (beam.minor * np.cos(beam.pa))**2 + (other.major * np.sin(other.pa))**2 + (other.minor * np.cos(other.pa))**2) gamma = (2 * ((beam.minor**2 - beam.major**2) * np.sin(beam.pa) * np.cos(beam.pa) + (other.minor**2 - other.major**2) * np.sin(other.pa) * np.cos(other.pa))) s = alpha + beta t = np.sqrt((alpha - beta)**2 + gamma**2) new_major = np.sqrt(0.5 * (s + t)) new_minor = np.sqrt(0.5 * (s - t)) # absolute tolerance needs to be <<1 microarcsec if np.isclose(((abs(gamma) + abs(alpha - beta))**0.5).to(u.arcsec).value, 1e-7): new_pa = 0.0 * u.deg else: new_pa = 0.5 * np.arctan2(-1. * gamma, alpha - beta) return new_major, new_minor, new_pa def transform_ellipse(major, minor, pa, x_scale, y_scale): ''' Transform an ellipse by scaling in the x and y axes. Parameters ---------- major : `~astropy.units.Quantity` Major axis. minor : `~astropy.units.Quantity` Minor axis. pa : `~astropy.units.Quantity` PA of the major axis. x_scale : float x axis scaling factor. y_scale : float y axis scaling factor. Returns ------- trans_major : `~astropy.units.Quantity` Major axis in the transformed frame. trans_minor : `~astropy.units.Quantity` Minor axis in the transformed frame. trans_pa : `~astropy.units.Quantity` PA of the major axis in the transformed frame. ''' # This code is based on the implementation in CASA: # https://open-bitbucket.nrao.edu/projects/CASA/repos/casa/browse/code/imageanalysis/ImageAnalysis/CasaImageBeamSet.cc major = major.to(u.arcsec) minor = minor.to(u.arcsec) pa = pa.to(u.rad) cospa = np.cos(pa) sinpa = np.sin(pa) cos2pa = cospa**2 sin2pa = sinpa**2 major2 = major**2 minor2 = minor**2 a = (cos2pa / major2) + (sin2pa / minor2) b = -2 * cospa * sinpa * (major2**-1 - minor2**-1) c = (sin2pa / major2) + (cos2pa / minor2) x2_scale = x_scale**2 y2_scale = y_scale**2 r = a / x2_scale s = b**2 / (4 * x2_scale * y2_scale) t = c / y2_scale udiff = r - t u2 = udiff**2 f1 = u2 + 4 * s f2 = np.sqrt(f1) * np.abs(udiff) j1 = (f2 + f1) / f1 / 2 j2 = (f1 - f2) / f1 / 2 k1 = (j1 * r + j1 * t - t) / (2 * j1 - 1) k2 = (j2 * r + j2 * t - t) / (2 * j2 - 1) c1 = np.sqrt(k1)**-1 c2 = np.sqrt(k2)**-1 pa_sign = 1 if pa.value >= 0 else -1 if c1 == c2: # Transformed to a circle trans_major = 1 / c1 trans_minor = trans_major trans_pa = 0. * u.rad elif c1 > c2: # c1 and c2 are the major and minor axes; use j1 to get PA trans_major = c1 trans_minor = c2 trans_pa = pa_sign * np.arccos(np.sqrt(j1)) else: # Opposite case where the axes are switched; get PA from j2 trans_major = c2 trans_minor = c1 trans_pa = pa_sign * np.arccos(np.sqrt(j2)) return trans_major, trans_minor, trans_pa radio-beam-0.3.3/radio_beam/version.py0000644000175100001710000000021614025155267020443 0ustar runnerdocker00000000000000# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control version = '0.3.3' version_tuple = (0, 3, 3) radio-beam-0.3.3/radio_beam.egg-info/0000755000175100001710000000000014025155267020077 5ustar runnerdocker00000000000000radio-beam-0.3.3/radio_beam.egg-info/PKG-INFO0000644000175100001710000000064114025155267021175 0ustar runnerdocker00000000000000Metadata-Version: 2.1 Name: radio-beam Version: 0.3.3 Summary: Operations for radio astronomy beams with astropy Home-page: http://radio_beam.readthedocs.org Author: Adam Leroy, Adam Ginsburg, Erik Rosolowsky, Tom Robitaille, and Eric Koch Author-email: adam.g.ginsburg@gmail.com, koch.eric.w@gmail.com License: BSD Description: UNKNOWN Platform: UNKNOWN Provides-Extra: test Provides-Extra: docs Provides-Extra: all radio-beam-0.3.3/radio_beam.egg-info/SOURCES.txt0000644000175100001710000000260114025155267021762 0ustar runnerdocker00000000000000.gitignore .readthedocs.yml CHANGES.rst LICENSE.rst MANIFEST.in README.md pyproject.toml readthedocs.yml setup.cfg setup.py specs.txt tox.ini .github/workflows/main.yml .github/workflows/publish.yml docs/Makefile docs/api.rst docs/commonbeam.rst docs/conf.py docs/convolution_kernels.rst docs/index.rst docs/install.rst docs/make.bat docs/nitpick-exceptions docs/rtd-pip-requirements docs/_templates/autosummary/base.rst docs/_templates/autosummary/class.rst docs/_templates/autosummary/module.rst radio_beam/__init__.py radio_beam/_astropy_init.py radio_beam/beam.py radio_beam/commonbeam.py radio_beam/conftest.py radio_beam/multiple_beams.py radio_beam/utils.py radio_beam/version.py radio_beam.egg-info/PKG-INFO radio_beam.egg-info/SOURCES.txt radio_beam.egg-info/dependency_links.txt radio_beam.egg-info/not-zip-safe radio_beam.egg-info/requires.txt radio_beam.egg-info/top_level.txt radio_beam/tests/__init__.py radio_beam/tests/setup_package.py radio_beam/tests/test_beam.py radio_beam/tests/test_beams.py radio_beam/tests/test_kernels.py radio_beam/tests/data/NGC0925.bima.mmom0.fits.gz radio_beam/tests/data/NGC0925.bima.mmom0.image.tar.gz radio_beam/tests/data/generate_commonbeam_table.py radio_beam/tests/data/header_aips.hdr radio_beam/tests/data/header_jybeam.hdr radio_beam/tests/data/m33_beams_bintable.fits.gz radio_beam/tests/data/m83.moment0.fits.gz radio_beam/tests/data/ngc0925_na.fits.gzradio-beam-0.3.3/radio_beam.egg-info/dependency_links.txt0000644000175100001710000000000114025155267024145 0ustar runnerdocker00000000000000 radio-beam-0.3.3/radio_beam.egg-info/not-zip-safe0000644000175100001710000000000114025155266022324 0ustar runnerdocker00000000000000 radio-beam-0.3.3/radio_beam.egg-info/requires.txt0000644000175100001710000000017314025155267022500 0ustar runnerdocker00000000000000astropy numpy>=1.8.0 six scipy [all] scipy matplotlib [docs] sphinx-astropy matplotlib [test] pytest-astropy pytest-cov radio-beam-0.3.3/radio_beam.egg-info/top_level.txt0000644000175100001710000000001314025155267022623 0ustar runnerdocker00000000000000radio_beam radio-beam-0.3.3/readthedocs.yml0000644000175100001710000000010714025155256017327 0ustar runnerdocker00000000000000conda: file: .rtd-environment.yml python: setup_py_install: true radio-beam-0.3.3/setup.cfg0000644000175100001710000000252214025155267016145 0ustar runnerdocker00000000000000[metadata] name = radio-beam description = Operations for radio astronomy beams with astropy long_description = file: README.rst author = Adam Leroy, Adam Ginsburg, Erik Rosolowsky, Tom Robitaille, and Eric Koch author_email = adam.g.ginsburg@gmail.com, koch.eric.w@gmail.com license = BSD url = http://radio_beam.readthedocs.org edit_on_github = False github_project = radio-astro-tools/radio-beam [options] zip_safe = False packages = find: install_requires = astropy numpy>=1.8.0 six scipy [options.extras_require] test = pytest-astropy pytest-cov docs = sphinx-astropy matplotlib all = scipy matplotlib [options.package_data] radio_beam.tests = data/* [tool:pytest] testpaths = "radio_beam" "docs" astropy_header = true doctest_plus = enabled text_file_format = rst addopts = --doctest-rst [coverage:run] source = radio_beam omit = radio_beam/_astropy_init* radio_beam/conftest* radio_beam/cython_version* radio_beam/setup_package* radio_beam/*/setup_package* radio_beam/*/*/setup_package* radio_beam/tests/* radio_beam/*/tests/* radio_beam/*/*/tests/* radio_beam/version* [coverage:report] exclude_lines = pragma: no cover except ImportError raise AssertionError raise NotImplementedError def main\(.*\): pragma: py{ignore_python_version} def _ipython_key_completions_ [egg_info] tag_build = tag_date = 0 radio-beam-0.3.3/setup.py0000644000175100001710000000211314025155256016030 0ustar runnerdocker00000000000000#!/usr/bin/env python import os import sys from setuptools import setup TEST_HELP = """ Note: running tests is no longer done using 'python setup.py test'. Instead you will need to run: tox -e test If you don't already have tox installed, you can install it with: pip install tox If you only want to run part of the test suite, you can also use pytest directly with:: pip install -e . pytest For more information, see: http://docs.astropy.org/en/latest/development/testguide.html#running-tests """ if 'test' in sys.argv: print(TEST_HELP) sys.exit(1) DOCS_HELP = """ Note: building the documentation is no longer done using 'python setup.py build_docs'. Instead you will need to run: tox -e build_docs If you don't already have tox installed, you can install it with: pip install tox For more information, see: http://docs.astropy.org/en/latest/install.html#builddocs """ if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv: print(DOCS_HELP) sys.exit(1) setup(use_scm_version={'write_to': os.path.join('radio_beam', 'version.py')}) radio-beam-0.3.3/specs.txt0000644000175100001710000000076614025155256016210 0ustar runnerdocker00000000000000Lingering wish list: - parameterized spectral behavior (1/nu, tabular?) - build array for a specific WCS (deal with rotated/weird images) - instantiate from CASA header - compare two beams (or an array of beams) and recommend a "common beam" to target for a matched resolution image. Analytic solution and then tolerance ~ Nyquist on top of the analytic solution. - expose the factor (area ratio) to normalize by in convolution in order to keep Jy/beam (i.e., to use the new beam as a unit) radio-beam-0.3.3/tox.ini0000644000175100001710000000201414025155256015631 0ustar runnerdocker00000000000000[tox] envlist = py{36,37,38}-test{,-all,-dev} build_docs codestyle requires = setuptools >= 30.3.0 pip >= 19.3.1 isolated_build = true indexserver = NRAO = https://casa-pip.nrao.edu/repository/pypi-group/simple [testenv] passenv = HOME WINDIR DISPLAY LC_ALL LC_CTYPE ON_TRAVIS changedir = .tmp/{envname} description = run tests with pytest deps = dev: git+https://github.com/astropy/astropy#egg=astropy casa: :NRAO:casatools casa: :NRAO:casatasks extras = test all: all commands = pip freeze pytest --open-files --pyargs radio_beam {toxinidir}/docs --cov radio_beam --cov-config={toxinidir}/setup.cfg {posargs} coverage xml -o {toxinidir}/coverage.xml [testenv:build_docs] changedir = docs description = invoke sphinx-build to build the HTML docs extras = docs commands = sphinx-build -W -b html . _build/html {posargs} [testenv:codestyle] deps = flake8 skip_install = true commands = flake8 --max-line-length=100 radio_beam