├── docs ├── requirements.txt ├── utility_functions.rst ├── Images │ ├── gsm_icrs_radec.png │ ├── gleam_50srcs_radec.png │ ├── gleam_50srcs_freqflux.png │ ├── gsm_icrs_fluxcounts.png │ ├── gleam_50srcs_fluxcounts.png │ ├── gsm_icrs_flux_molliwede.png │ ├── pointsource_catalog_radec.png │ ├── fhd_catalog_specind_atfreqs.png │ ├── gleam_50srcs_radec_raselect.png │ ├── gsm_gal_coneselect_molliwede.png │ ├── gsm_galactic_flux_molliwede.png │ ├── gsm_icrs_indselect_molliwede.png │ ├── gleam_50srcs_radec_fluxselect.png │ ├── gleam_subband_spectra_atfreqs.png │ ├── fhd_catalog_extended_models_radec.png │ ├── gsm_gal_neighborselect_molliwede.png │ ├── gsm_point_galactic_flux_molliwede.png │ ├── pointsource_catalog_radec_concat.png │ ├── fhd_catalog_extended_models_refspec.png │ ├── gleam_50srcs_fluxcounts_fluxselect.png │ ├── fhd_catalog_extended_models_fluxcounts.png │ └── fhd_catalog_extended_models_radec_picA.png ├── references │ ├── skyh5_memo.pdf │ └── skyh5_memo.tex ├── developer_docs.rst ├── Makefile ├── conftest.py ├── make.bat ├── make_index.py ├── make_skymodel.py └── conf.py ├── src └── pyradiosky │ ├── data │ ├── __init__.py │ ├── gsm_icrs.skyh5 │ ├── fhd_catalog.sav │ ├── fhd_catalog_bad.sav │ ├── gsm_galactic.skyh5 │ ├── healpix_disk.skyh5 │ ├── fhd_source_array.sav │ ├── extended_source_test.sav │ ├── fhd_catalog_no_extend.sav │ ├── old_skyh5_point_sources.skyh5 │ ├── fhd_catalog_with_beam_values.sav │ ├── pointsource_catalog.txt │ ├── simple_test.vot │ ├── mock_hera_text_2458098.27471.txt │ └── single_source_old.vot │ ├── __init__.py │ ├── cli.py │ ├── spherical_coords_transforms.py │ └── utils.py ├── ci ├── publish.yaml ├── min_deps.yaml ├── min_versions.yaml └── full_deps.yaml ├── MANIFEST.in ├── .coveragerc ├── environment.yaml ├── .readthedocs.yml ├── .pre-commit-config.yaml ├── tests ├── conftest.py ├── test_spherical_coords_transforms.py └── test_utils.py ├── setup.py ├── .github ├── original_pr_templates │ ├── documentation.md │ ├── version_change.md │ ├── build_ci_change.md │ ├── bug_fix.md │ ├── new_feature.md │ └── other.md ├── workflows │ ├── externaltests.yaml │ ├── publish-to-test-pypi.yaml │ └── testsuite.yaml ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── LICENSE ├── .gitignore ├── pyproject.toml ├── CODE_OF_CONDUCT.md ├── paper.md ├── README.md ├── paper.bib └── CHANGELOG.md /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=6.2 2 | sphinx_rtd_theme==1.2.2 3 | -------------------------------------------------------------------------------- /src/pyradiosky/data/__init__.py: -------------------------------------------------------------------------------- 1 | """Init file for data directory.""" 2 | 3 | DATA_PATH = __path__[0] 4 | -------------------------------------------------------------------------------- /docs/utility_functions.rst: -------------------------------------------------------------------------------- 1 | Utility Functions 2 | ================= 3 | 4 | .. automodule:: pyradiosky.utils 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/Images/gsm_icrs_radec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_icrs_radec.png -------------------------------------------------------------------------------- /docs/references/skyh5_memo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/references/skyh5_memo.pdf -------------------------------------------------------------------------------- /docs/Images/gleam_50srcs_radec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_50srcs_radec.png -------------------------------------------------------------------------------- /src/pyradiosky/data/gsm_icrs.skyh5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/gsm_icrs.skyh5 -------------------------------------------------------------------------------- /docs/Images/gleam_50srcs_freqflux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_50srcs_freqflux.png -------------------------------------------------------------------------------- /docs/Images/gsm_icrs_fluxcounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_icrs_fluxcounts.png -------------------------------------------------------------------------------- /src/pyradiosky/data/fhd_catalog.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/fhd_catalog.sav -------------------------------------------------------------------------------- /docs/Images/gleam_50srcs_fluxcounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_50srcs_fluxcounts.png -------------------------------------------------------------------------------- /docs/Images/gsm_icrs_flux_molliwede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_icrs_flux_molliwede.png -------------------------------------------------------------------------------- /src/pyradiosky/data/fhd_catalog_bad.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/fhd_catalog_bad.sav -------------------------------------------------------------------------------- /src/pyradiosky/data/gsm_galactic.skyh5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/gsm_galactic.skyh5 -------------------------------------------------------------------------------- /src/pyradiosky/data/healpix_disk.skyh5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/healpix_disk.skyh5 -------------------------------------------------------------------------------- /docs/Images/pointsource_catalog_radec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/pointsource_catalog_radec.png -------------------------------------------------------------------------------- /src/pyradiosky/data/fhd_source_array.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/fhd_source_array.sav -------------------------------------------------------------------------------- /docs/Images/fhd_catalog_specind_atfreqs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/fhd_catalog_specind_atfreqs.png -------------------------------------------------------------------------------- /docs/Images/gleam_50srcs_radec_raselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_50srcs_radec_raselect.png -------------------------------------------------------------------------------- /docs/Images/gsm_gal_coneselect_molliwede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_gal_coneselect_molliwede.png -------------------------------------------------------------------------------- /docs/Images/gsm_galactic_flux_molliwede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_galactic_flux_molliwede.png -------------------------------------------------------------------------------- /docs/Images/gsm_icrs_indselect_molliwede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_icrs_indselect_molliwede.png -------------------------------------------------------------------------------- /src/pyradiosky/data/extended_source_test.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/extended_source_test.sav -------------------------------------------------------------------------------- /docs/Images/gleam_50srcs_radec_fluxselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_50srcs_radec_fluxselect.png -------------------------------------------------------------------------------- /docs/Images/gleam_subband_spectra_atfreqs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_subband_spectra_atfreqs.png -------------------------------------------------------------------------------- /src/pyradiosky/data/fhd_catalog_no_extend.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/fhd_catalog_no_extend.sav -------------------------------------------------------------------------------- /docs/Images/fhd_catalog_extended_models_radec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/fhd_catalog_extended_models_radec.png -------------------------------------------------------------------------------- /docs/Images/gsm_gal_neighborselect_molliwede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_gal_neighborselect_molliwede.png -------------------------------------------------------------------------------- /docs/Images/gsm_point_galactic_flux_molliwede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gsm_point_galactic_flux_molliwede.png -------------------------------------------------------------------------------- /docs/Images/pointsource_catalog_radec_concat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/pointsource_catalog_radec_concat.png -------------------------------------------------------------------------------- /src/pyradiosky/data/old_skyh5_point_sources.skyh5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/old_skyh5_point_sources.skyh5 -------------------------------------------------------------------------------- /docs/Images/fhd_catalog_extended_models_refspec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/fhd_catalog_extended_models_refspec.png -------------------------------------------------------------------------------- /docs/Images/gleam_50srcs_fluxcounts_fluxselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/gleam_50srcs_fluxcounts_fluxselect.png -------------------------------------------------------------------------------- /src/pyradiosky/data/fhd_catalog_with_beam_values.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/src/pyradiosky/data/fhd_catalog_with_beam_values.sav -------------------------------------------------------------------------------- /docs/Images/fhd_catalog_extended_models_fluxcounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/fhd_catalog_extended_models_fluxcounts.png -------------------------------------------------------------------------------- /docs/Images/fhd_catalog_extended_models_radec_picA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RadioAstronomySoftwareGroup/pyradiosky/HEAD/docs/Images/fhd_catalog_extended_models_radec_picA.png -------------------------------------------------------------------------------- /ci/publish.yaml: -------------------------------------------------------------------------------- 1 | name: publish 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - astropy>=6.0 6 | - h5py>=3.7 7 | - numpy>=1.23 8 | - pip 9 | - pyuvdata>=3.2.3 10 | - scipy>=1.9 11 | - setuptools_scm>=8.1 12 | - pip: 13 | - build 14 | -------------------------------------------------------------------------------- /ci/min_deps.yaml: -------------------------------------------------------------------------------- 1 | name: min_deps 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - astropy>=6.0 6 | - coverage 7 | - h5py>=3.7 8 | - numpy>=1.23 9 | - pytest 10 | - pytest-cov 11 | - pyuvdata>=3.2.3 12 | - scipy>=1.9 13 | - setuptools_scm>=8.1 14 | - pip 15 | -------------------------------------------------------------------------------- /src/pyradiosky/data/pointsource_catalog.txt: -------------------------------------------------------------------------------- 1 | Source_ID RA_J2000 Dec_J2000 Flux_Density_I_Jy Frequency 2 | src1 01.00 -30.00 0.5 1e6 3 | src2 01.30 -31.00 2.0 1e6 4 | src3 01.35 -32.00 5.0 1e6 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | include LICENSE 3 | include *.bib 4 | 5 | recursive-include src/pyradiosky/data * 6 | 7 | recursive-include tests *.py 8 | 9 | recursive-include docs * 10 | exclude docs/.DS_Store 11 | prune docs/_static 12 | prune docs/_templates 13 | prune docs/build 14 | prune docs/__pycache__ 15 | -------------------------------------------------------------------------------- /docs/developer_docs.rst: -------------------------------------------------------------------------------- 1 | Developer Docs 2 | ============== 3 | Documentation for all the under-the-hood classes and functions that most users 4 | won't need to interact with. 5 | 6 | spherical_coords_transforms 7 | ___________________________ 8 | 9 | .. automodule:: pyradiosky.spherical_coords_transforms 10 | :members: 11 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | */tests/* 4 | */docs/* 5 | setup.py 6 | branch = true 7 | source = 8 | pyradiosky 9 | 10 | [report] 11 | omit = 12 | */tests/* 13 | */docs/* 14 | setup.py 15 | 16 | show_missing = true 17 | 18 | [paths] 19 | source = 20 | src 21 | */site-packages" 22 | tests = 23 | tests 24 | */tests 25 | -------------------------------------------------------------------------------- /ci/min_versions.yaml: -------------------------------------------------------------------------------- 1 | name: min_versions 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - astropy==6.0 6 | - astropy-healpix==1.0.2 7 | - astroquery==0.4.4 8 | - h5py==3.7.0 9 | - numpy==1.23.* 10 | - scipy==1.9.* 11 | - coverage 12 | - pytest-cov 13 | - setuptools_scm==8.1 14 | - pyuvdata==3.2.3 15 | - pip 16 | - pip: 17 | - lunarsky==0.2.5 18 | -------------------------------------------------------------------------------- /ci/full_deps.yaml: -------------------------------------------------------------------------------- 1 | name: full_deps 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - astropy>=6.0 6 | - astropy-healpix>=1.0.2 7 | - astroquery>=0.4.4 8 | - coverage 9 | - h5py>=3.7 10 | - matplotlib # just for the doctests 11 | - numpy>=1.23 12 | - pip 13 | - pytest-cov 14 | - pyuvdata>=3.2.3 15 | - scipy>=1.9 16 | - setuptools_scm>=8.1 17 | - pip: 18 | - lunarsky>=0.2.5 19 | -------------------------------------------------------------------------------- /environment.yaml: -------------------------------------------------------------------------------- 1 | name: pyradiosky 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - astropy>=6.0 6 | - astropy-healpix>=1.0.2 7 | - astroquery>=0.4.4 8 | - h5py>=3.7 9 | - matplotlib 10 | - numpy>=1.23 11 | - coverage 12 | - pip 13 | - pre-commit 14 | - pypandoc 15 | - pytest-cov 16 | - pyuvdata>=3.2.3 17 | - scipy>=1.9 18 | - setuptools_scm>=8.1 19 | - sphinx 20 | - pip: 21 | - lunarsky>=0.2.5 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | 4 | """Testing environment setup and teardown for pytest.""" 5 | 6 | import os 7 | import shutil 8 | from pathlib import Path 9 | 10 | import pytest 11 | 12 | 13 | @pytest.fixture(autouse=True, scope="session") 14 | def setup_and_teardown_package(tmp_path_factory): 15 | """Make data/test directory to put test output files in.""" 16 | cwd = Path.cwd() 17 | tmp_path = tmp_path_factory.mktemp("skymodel_tests") 18 | try: 19 | os.chdir(tmp_path) 20 | if not os.path.exists("Images"): 21 | os.makedirs("Images") 22 | yield 23 | finally: 24 | os.chdir(cwd) 25 | shutil.rmtree(tmp_path) 26 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | builder: html 11 | configuration: docs/conf.py 12 | # might want to do this in future: 13 | # fail_on_warning: true 14 | 15 | # Do not build the docs in additional formats (e.g. PDF and ePub), use default 16 | formats: [] 17 | 18 | # Set the version of Python and other tools you might need 19 | build: 20 | os: ubuntu-22.04 21 | tools: 22 | python: "3.12" 23 | 24 | # Optionally install options and requirements required to build your docs 25 | python: 26 | install: 27 | - method: pip 28 | path: . 29 | extra_requirements: 30 | - doc 31 | - requirements: docs/requirements.txt 32 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: '(^docs/conf.py|^user_data/External_tables/)' 2 | 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v6.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: check-added-large-files 9 | - id: check-ast 10 | - id: check-json 11 | - id: check-merge-conflict 12 | - id: check-xml 13 | - id: check-yaml 14 | - id: debug-statements 15 | - id: end-of-file-fixer 16 | - id: mixed-line-ending 17 | args: ['--fix=no'] 18 | 19 | - repo: https://github.com/astral-sh/ruff-pre-commit 20 | rev: v0.14.6 21 | hooks: 22 | - id: ruff 23 | - id: ruff-format 24 | 25 | - repo: https://github.com/PyCQA/bandit 26 | rev: 1.9.2 27 | hooks: 28 | - id: bandit 29 | args: [--skip, "B101", --recursive, pyradiosky] 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | 4 | import pytest 5 | from astropy.time import Time 6 | from astropy.utils import iers 7 | 8 | 9 | @pytest.fixture(autouse=True, scope="session") 10 | def setup_and_teardown_package(): 11 | """Handle IERS errors.""" 12 | # Do a calculation that requires a current IERS table. This will trigger 13 | # automatic downloading of the IERS table if needed, including trying the 14 | # mirror site in python 3 (but won't redownload if a current one exists). 15 | # If there's not a current IERS table and it can't be downloaded, turn off 16 | # auto downloading for the tests and turn it back on once all tests are 17 | # completed (done by extending auto_max_age). 18 | try: 19 | t1 = Time.now() 20 | t1.ut1 # noqa 21 | except Exception: 22 | iers.conf.auto_max_age = None 23 | 24 | yield 25 | 26 | iers.conf.auto_max_age = 30 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | 4 | from setuptools import setup 5 | 6 | # The only reason we currently need a setup.py is because we use a special 7 | # branch scheme. See https://setuptools-scm.readthedocs.io/en/stable/customizing/ 8 | 9 | 10 | # define the branch scheme. Have to do it here so we don't have to modify the path 11 | def branch_scheme(version): 12 | """ 13 | Local version scheme that adds the branch name for absolute reproducibility. 14 | 15 | If and when this is added to setuptools_scm this function can be removed. 16 | """ 17 | if version.exact or version.node is None: 18 | return version.format_choice("", "+d{time:{time_format}}", time_format="%Y%m%d") 19 | else: 20 | if version.branch == "main": 21 | return version.format_choice("+{node}", "+{node}.dirty") 22 | else: 23 | version_str = version.format_choice( 24 | "+{node}.{branch}", "+{node}.{branch}.dirty" 25 | ) 26 | version_str = version_str.replace("/", ".") 27 | return version_str 28 | 29 | 30 | setup(use_scm_version={"local_scheme": branch_scheme}) 31 | -------------------------------------------------------------------------------- /.github/original_pr_templates/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Update or improve the documentation. 4 | labels: documentation 5 | --- 6 | 7 | 8 | ## Description 9 | 10 | 11 | ## Motivation and Context 12 | 13 | 14 | 15 | ## Checklist: 16 | 17 | 18 | 19 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/main/.github/CONTRIBUTING.md). 20 | - [ ] My code follows the code style of this project. 21 | Documentation change checklist: 22 | - [ ] Any updated docstrings use the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html). 23 | -------------------------------------------------------------------------------- /.github/original_pr_templates/version_change.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Version Change 3 | about: Update the version. 4 | --- 5 | 6 | 7 | ## Description 8 | 9 | 10 | ## Motivation and Context 11 | 12 | 13 | 14 | ## Checklist: 15 | 16 | 17 | 18 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/main/.github/CONTRIBUTING.md). 19 | - [ ] My code follows the code style of this project. 20 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/main/CHANGELOG.md) to put all the unreleased changes under the new version (leaving the unreleased section empty). 21 | -------------------------------------------------------------------------------- /src/pyradiosky/data/simple_test.vot: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
TEST 0.00000000-30.00000000 1.000000
TEST2 0.00000000 0.00000000 0.000000
28 |
29 |
30 | -------------------------------------------------------------------------------- /.github/original_pr_templates/build_ci_change.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build or Continuous Integration Change 3 | about: Modify a continuous integration setup or the package building. 4 | --- 5 | 6 | 7 | ## Description 8 | 9 | 10 | ## Motivation and Context 11 | 12 | 13 | 14 | ## Checklist: 15 | 16 | 17 | 18 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/main/.github/CONTRIBUTING.md). 19 | - [ ] My code follows the code style of this project. 20 | - [ ] If required or optional dependencies have changed (including version numbers), I have updated the readme to reflect this. 21 | - [ ] If this is a new CI setup, I have added the associated badge to the readme 22 | -------------------------------------------------------------------------------- /.github/original_pr_templates/bug_fix.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Fix 3 | about: Fix a bug 4 | labels: bug 5 | --- 6 | 7 | 8 | ## Description 9 | 10 | 11 | ## Motivation and Context 12 | 13 | 14 | 15 | ## Checklist: 16 | 17 | 18 | 19 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 20 | - [ ] My code follows the code style of this project. 21 | - [ ] My fix includes a new test that breaks as a result of the bug (if possible). 22 | - [ ] My change includes a breaking change 23 | - [ ] My change includes backwards compatibility and deprecation warnings (if possible). 24 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md). 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Radio Astronomy Software Group 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /.github/original_pr_templates/new_feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Feature 3 | about: Add a new feature 4 | --- 5 | 6 | 7 | ## Description 8 | 9 | 10 | ## Motivation and Context 11 | 12 | 13 | 14 | ## Checklist: 15 | 16 | 17 | 18 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 19 | - [ ] My code follows the code style of this project. 20 | - [ ] I have added or updated the docstrings associated with my feature using the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html). 21 | - [ ] I have added tests to cover my new feature. 22 | - [ ] My change includes a breaking change 23 | - [ ] My change includes backwards compatibility and deprecation warnings (if possible). 24 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md). 25 | -------------------------------------------------------------------------------- /.github/original_pr_templates/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Anything that doesn't fit in any of the other types of pull requests. 4 | --- 5 | 6 | 7 | ## Description 8 | 9 | 10 | ## Motivation and Context 11 | 12 | 13 | 14 | ## Checklist: 15 | 16 | 17 | 18 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 19 | - [ ] My code follows the code style of this project. 20 | - [ ] Any new or updated docstrings use the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html). 21 | - [ ] I have added tests to cover any changes. 22 | - [ ] My change includes a breaking change 23 | - [ ] My change includes backwards compatibility and deprecation warnings (if possible). 24 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md) if appropriate. 25 | -------------------------------------------------------------------------------- /src/pyradiosky/__init__.py: -------------------------------------------------------------------------------- 1 | """Define namespace.""" 2 | 3 | import contextlib 4 | from importlib.metadata import PackageNotFoundError, version 5 | from pathlib import Path 6 | 7 | from setuptools_scm import get_version 8 | 9 | 10 | # copy this function here from setup.py. 11 | # Copying code is terrible, but it's better than altering the python path in setup.py. 12 | def branch_scheme(version): # pragma: nocover 13 | """ 14 | Local version scheme that adds the branch name for absolute reproducibility. 15 | 16 | If and when this is added to setuptools_scm this function can be removed. 17 | """ 18 | if version.exact or version.node is None: 19 | return version.format_choice("", "+d{time:{time_format}}", time_format="%Y%m%d") 20 | else: 21 | if version.branch == "main": 22 | return version.format_choice("+{node}", "+{node}.dirty") 23 | else: 24 | version_str = version.format_choice( 25 | "+{node}.{branch}", "+{node}.{branch}.dirty" 26 | ) 27 | version_str = version_str.replace("/", ".") 28 | return version_str 29 | 30 | 31 | try: 32 | # get accurate version for developer installs 33 | version_str = get_version( 34 | Path(__file__).parent.parent.parent, local_scheme=branch_scheme 35 | ) 36 | 37 | __version__ = version_str 38 | 39 | except (LookupError, ImportError): 40 | with contextlib.suppress(PackageNotFoundError): 41 | # Set the version automatically from the package details. 42 | __version__ = version("pyradiosky") 43 | 44 | from .skymodel import SkyModel # noqa 45 | -------------------------------------------------------------------------------- /docs/make_index.py: -------------------------------------------------------------------------------- 1 | """ 2 | Format the readme.md file into the sphinx index.rst file. 3 | 4 | """ 5 | 6 | import inspect 7 | import os 8 | 9 | import pypandoc 10 | from astropy.time import Time 11 | 12 | 13 | def write_index_rst(readme_file=None, write_file=None): 14 | t = Time.now() 15 | t.format = "iso" 16 | t.out_subfmt = "date" 17 | out = ( 18 | ".. pyradiosky documentation index file, created by\n" 19 | f" make_index.py on {t.iso}\n\n" 20 | ) 21 | 22 | if readme_file is None: 23 | main_path = os.path.dirname( 24 | os.path.dirname(os.path.abspath(inspect.stack()[0][1])) 25 | ) 26 | readme_file = os.path.join(main_path, "README.md") 27 | 28 | readme_text = pypandoc.convert_file(readme_file, "rst") 29 | 30 | # convert relative links in readme to explicit links 31 | readme_text = readme_text.replace( 32 | "> $GITHUB_ENV 49 | 50 | - name: Check environment variable 51 | run: echo $SETUPTOOLS_SCM_PRETEND_VERSION 52 | 53 | - name: Build a binary wheel and a source tarball 54 | run: | 55 | python3 -m build 56 | 57 | - name: Publish to PyPI 58 | if: startsWith(github.ref, 'refs/tags') 59 | uses: pypa/gh-action-pypi-publish@release/v1 60 | with: 61 | password: ${{ secrets.pypi_password }} 62 | -------------------------------------------------------------------------------- /src/pyradiosky/data/mock_hera_text_2458098.27471.txt: -------------------------------------------------------------------------------- 1 | SOURCE_ID RA_J2000 [deg] Dec_J2000 [deg] Flux [Jy] Frequency [Hz] 2 | src0 28.103507 -32.541308 1.00 150000000.00 3 | src1 25.734946 -32.672235 1.00 150000000.00 4 | src2 23.361369 -32.760516 1.00 150000000.00 5 | src3 22.172001 -32.787771 1.00 150000000.00 6 | src4 20.983198 -32.804964 1.00 150000000.00 7 | src5 18.604394 -32.806366 1.00 150000000.00 8 | src6 16.226085 -32.764718 1.00 150000000.00 9 | src7 13.852232 -32.679226 1.00 150000000.00 10 | src8 11.483261 -32.551071 1.00 150000000.00 11 | src9 28.013547 -31.546839 1.00 150000000.00 12 | src10 25.670235 -31.675179 1.00 150000000.00 13 | src11 23.321217 -31.761294 1.00 150000000.00 14 | src12 18.616585 -31.806457 1.00 150000000.00 15 | src13 17.440155 -31.791342 1.00 150000000.00 16 | src14 13.915314 -31.682104 1.00 150000000.00 17 | src15 11.571614 -31.556511 1.00 150000000.00 18 | src16 27.927165 -30.552168 1.00 150000000.00 19 | src17 26.768313 -30.620199 1.00 150000000.00 20 | src18 25.607956 -30.677942 1.00 150000000.00 21 | src19 23.283697 -30.762426 1.00 150000000.00 22 | src20 22.120290 -30.789115 1.00 150000000.00 23 | src21 18.627961 -30.806786 1.00 150000000.00 24 | src22 17.463988 -30.791865 1.00 150000000.00 25 | src23 16.300503 -30.766550 1.00 150000000.00 26 | src24 13.975997 -30.684803 1.00 150000000.00 27 | src25 12.815469 -30.628424 1.00 150000000.00 28 | src26 11.656418 -30.561752 1.00 150000000.00 29 | src27 27.844011 -29.557348 1.00 150000000.00 30 | src28 25.548066 -29.680626 1.00 150000000.00 31 | src29 23.247039 -29.763546 1.00 150000000.00 32 | src30 18.639216 -29.807115 1.00 150000000.00 33 | src31 16.335582 -29.767633 1.00 150000000.00 34 | src32 14.034319 -29.687427 1.00 150000000.00 35 | src33 11.738024 -29.566848 1.00 150000000.00 36 | src34 27.764133 -28.562410 1.00 150000000.00 37 | src35 25.490401 -28.683338 1.00 150000000.00 38 | src36 23.212815 -28.764195 1.00 150000000.00 39 | src37 22.071845 -28.790420 1.00 150000000.00 40 | src38 20.931473 -28.805845 1.00 150000000.00 41 | src39 18.649710 -28.807197 1.00 150000000.00 42 | src40 17.509296 -28.793122 1.00 150000000.00 43 | src41 16.368255 -28.768247 1.00 150000000.00 44 | src42 12.952166 -28.635639 1.00 150000000.00 45 | -------------------------------------------------------------------------------- /docs/make_skymodel.py: -------------------------------------------------------------------------------- 1 | """Format the SkyModel object parameters into a sphinx rst file.""" 2 | 3 | import inspect 4 | import os 5 | 6 | from astropy.time import Time 7 | 8 | from pyradiosky import SkyModel 9 | 10 | 11 | def write_skymodel_rst(write_file=None): 12 | UV = SkyModel() 13 | out = "SkyModel\n========\n" 14 | out += ( 15 | "SkyModel is the main user class for point source and diffuse models.\n" 16 | "It provides import and export functionality to and from supported file\n" 17 | "formats as well as methods for transforming the data (combining, selecting,\n" 18 | "changing coordinate frames) and can be interacted with directly.\n\n" 19 | "Attributes\n----------\n" 20 | "The attributes on SkyModel hold all of the metadata and data required to\n" 21 | "work with radio sky models. Under the hood, the attributes are\n" 22 | "implemented as properties based on :class:`pyuvdata.parameter.UVParameter`\n" 23 | "objects but this is fairly transparent to users.\n\n" 24 | "SkyModel objects can be initialized from a file using the\n" 25 | ":meth:`pyradiosky.SkyModel.from_file` class method\n" 26 | "(as ``sky = SkyModel.from_file()``) or be initialized by passing\n" 27 | "in all the information to the constructor. SkyModel objects can also be\n" 28 | "initialized as an empty object (as ``sky = SkyModel()``). When an empty\n" 29 | "SkyModel object is initialized, it has all of these attributes defined but\n" 30 | "set to ``None``. The attributes can be set by reading in a data file using\n" 31 | "the :meth:`pyradiosky.SkyModel.read` method or by setting them directly on\n" 32 | "the object. Some of these attributes are `required`_ to be set to have a\n" 33 | "fully defined data set while others are `optional`_. The\n" 34 | ":meth:`pyradiosky.SkyModel.check` method can be called on the object to\n" 35 | "verify that all of the required attributes have been set in a consistent\n" 36 | "way.\n\n" 37 | ) 38 | out += "Required\n********\n" 39 | out += ( 40 | "These parameters are required to have a sensible SkyModel object and \n" 41 | "are required for most kinds of catalog files." 42 | ) 43 | out += "\n\n" 44 | for thing in UV.required(): 45 | obj = getattr(UV, thing) 46 | out += f"**{obj.name}**\n" 47 | out += f" {obj.description}\n" 48 | out += "\n" 49 | 50 | out += "Optional\n********\n" 51 | out += ( 52 | "These parameters are defined by one or more file standard but are not " 53 | "always required.\nSome of them are required depending on the " 54 | "spectral_type or component_type (as noted below)." 55 | ) 56 | out += "\n\n" 57 | for thing in UV.extra(): 58 | obj = getattr(UV, thing) 59 | out += f"**{obj.name}**\n" 60 | out += f" {obj.description}\n" 61 | out += "\n" 62 | 63 | out += "Methods\n-------\n.. autoclass:: pyradiosky.SkyModel\n :members:\n\n" 64 | 65 | t = Time.now() 66 | t.format = "iso" 67 | t.out_subfmt = "date" 68 | out += f"last updated: {t.iso}" 69 | if write_file is None: 70 | write_path = os.path.dirname(os.path.abspath(inspect.stack()[0][1])) 71 | write_file = os.path.join(write_path, "skymodel.rst") 72 | with open(write_file, "w") as F: 73 | F.write(out) 74 | print("wrote " + write_file) 75 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64", "wheel", "setuptools_scm>=8.1"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pyradiosky" 7 | authors = [ 8 | {name = "Bryna Hazelton", email = "brynah@phys.washington.edu"}, 9 | {name = "Matthew Kolopanis", email = "mjkolopa@asu.edu"}, 10 | {name = "Jonathan Pober", email = "jonathan_pober@brown.edu"}, 11 | {name = "Adam Lanman", email = "alanman@mit.edu"}, 12 | ] 13 | maintainers = [ 14 | {name = "Bryna Hazelton", email = "brynah@phys.washington.edu"}, 15 | {name = "Matthew Kolopanis", email = "mjkolopa@asu.edu"} 16 | ] 17 | description = "Python objects and interfaces for representing diffuse, extended and compact astrophysical radio sources" 18 | readme = "README.md" 19 | dynamic = ["version"] 20 | dependencies = [ 21 | "astropy>=6.0", 22 | "h5py>=3.7", 23 | "numpy>=1.23", 24 | "pyuvdata>=3.2.3", 25 | "scipy>=1.9", 26 | "setuptools>=64", 27 | "setuptools_scm>=8.1", 28 | ] 29 | requires-python = ">=3.11" 30 | keywords = ["radio astronomy"] 31 | classifiers = [ 32 | "License :: OSI Approved :: BSD License", 33 | "Development Status :: 5 - Production/Stable", 34 | "Intended Audience :: Science/Research", 35 | "License :: OSI Approved :: BSD License", 36 | "Programming Language :: Python :: 3.11", 37 | "Programming Language :: Python :: 3.12", 38 | "Programming Language :: Python :: 3.13", 39 | "Topic :: Scientific/Engineering :: Astronomy", 40 | ] 41 | 42 | [project.optional-dependencies] 43 | healpix = ["astropy-healpix>=1.0.2"] 44 | astroquery = ["astroquery>=0.4.4"] 45 | lunarsky = ["lunarsky>=0.2.5"] 46 | all = ["pyradiosky[healpix,astroquery,lunarsky]"] 47 | test = ["coverage", "pre-commit", "pytest", "pytest-cov"] 48 | doc = ["matplotlib", "pypandoc", "sphinx"] 49 | dev = ["pyradiosky[all,test,doc]"] 50 | 51 | [project.urls] 52 | Repository = "https://github.com/RadioAstronomySoftwareGroup/pyradiosky" 53 | Documentation = "https://pyradiosky.readthedocs.io/" 54 | 55 | [project.scripts] 56 | download_gleam = "pyradiosky.cli:download_gleam" 57 | make_flat_spectrum_eor = "pyradiosky.cli:make_flat_spectrum_eor" 58 | 59 | [tool.setuptools_scm] 60 | 61 | [tool.ruff.lint] 62 | select = [ 63 | "E", # pycodestyle 64 | "W", # pycodestyle warnings 65 | "F", # Pyflakes 66 | "D", # pydocstyle 67 | "UP", # pyupgrade 68 | "B", # flake8-bugbear 69 | "A", # flake8-builtins 70 | "C4", # flake8-comprehensions 71 | "N", # pep8-naming 72 | "SIM", # flake8-simplify 73 | "I", # isort 74 | # "C90", # McCabe complexity. Consider for the future 75 | ] 76 | ignore = [ 77 | "N806", # non-lowercase variable (we use N* for axes lengths) 78 | "B028", # no-explicit-stacklevel for warnings 79 | "SIM108", # prefer ternary opperators. I find them difficult to read. 80 | "D203", # one-blank-line-before-class. we use two. 81 | "D212", # multi-line-summary-first-line. We put it on the second line. 82 | ] 83 | 84 | [tool.ruff.lint.per-file-ignores] 85 | "tests/*" = ["D"] # Don't require docstrings for tests 86 | "docs/*.py" = ["D", "A"] # Don't require docstrings or worry about builtins for docs 87 | "setup.py" = ["D"] # Don't require docstrings for setup.py 88 | 89 | [tool.ruff.format] 90 | skip-magic-trailing-comma = true 91 | 92 | [tool.ruff.lint.pycodestyle] 93 | max-line-length = 88 94 | 95 | # consider setting this in the future 96 | # [tool.ruff.lint.mccabe] 97 | # max-complexity = 30 98 | 99 | [tool.ruff.lint.isort] 100 | combine-as-imports = true 101 | known-first-party = ["pyradiosky"] 102 | split-on-trailing-comma = false 103 | 104 | [tool.ruff.lint.pydocstyle] 105 | convention = "numpy" 106 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the RASG managers at [rasgmanagers@gmail.com](mailto:rasgmanagers@gmail.com). 59 | The list of managers who will receive the email is in the [readme](README.md). 60 | To report an issue involving any of the managers, you can email any subset of the managers directly. 61 | 62 | All complaints will be reviewed and investigated and will result in a response that 63 | is deemed necessary and appropriate to the circumstances. The RASG managers are 64 | obligated to maintain confidentiality with regard to the reporter of an incident. 65 | 66 | Project maintainers or contributors who do not follow or enforce the Code of 67 | Conduct in good faith may be temporary or permanently removed from the project 68 | team or blocked from RASG repositories, Slack channels or telecons. 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | 77 | For answers to common questions about this code of conduct, see 78 | https://www.contributor-covenant.org/faq 79 | -------------------------------------------------------------------------------- /paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'pyradiosky: A Python package for Radio Sky Models' 3 | tags: 4 | - Python 5 | - astronomy 6 | - radio astronomy 7 | - 21cm cosmology 8 | authors: 9 | - name: Bryna Hazelton 10 | orcid: 0000-0001-7532-645X 11 | equal-contrib: true 12 | affiliation: "1, 2" 13 | - name: Matthew Kolopanis 14 | orcid: 0000-0002-2950-2974 15 | equal-contrib: true 16 | affiliation: 3 17 | - name: Adam Lanman 18 | orcid: 0000-0003-2116-3573 19 | equal-contrib: true 20 | affiliation: 4 21 | - name: Jonathan Pober 22 | orcid: 0000-0002-3492-0433 23 | equal-contrib: true 24 | affiliation: 5 25 | affiliations: 26 | - name: Physics Department, University of Washington, USA 27 | index: 1 28 | - name: eScience Institute University of Washington, USA 29 | index: 2 30 | - name: School of Earth and Space Exploration, Arizona State University, USA 31 | index: 3 32 | - name: Kavli Institute of Astrophysics and Space Research, Massachusetts Institute of Technology, USA 33 | index: 4 34 | - name: Department of Physics, Brown University, USA 35 | index: 5 36 | date: 8 February 2024 37 | bibliography: paper.bib 38 | --- 39 | 40 | # Summary 41 | 42 | Pyradiosky is a package to fully and generally describe models of compact, 43 | extended and diffuse radio sources with full polarization support. It is designed 44 | to support simulations and calibrations of radio interferometry data. 45 | It emphasizes the provenance and metadata required to understand the errors and 46 | covariances associated with sky models built from interferometric data. 47 | 48 | # Statement of need 49 | 50 | The original motivation for the development of pyradiosky was to support 51 | high-precision simulation of interferometric data for 21 cm cosmology, but the 52 | package was deliberately developed to support a broad range of radio astronomy 53 | applications. Sky models are key to the future of 21 cm analyses because 54 | high-precision foreground subtraction has the potential to dramatically increase 55 | the sensitivity of 21 cm instruments. High-quality sky models are also extremely 56 | important for calibration of all radio astronomy data. 57 | 58 | Pyradiosky supports reading in catalogs in the widely used VOTable format as 59 | well as an HDF5 based format we developed. It also supports some formats used by 60 | 21 cm cosmology codes and is easily extensible to other formats. It provides an 61 | object interface and useful methods for downselecting and combining multiple 62 | catalogs, coordinate transformations (with polarization support) and calculating 63 | fluxes at specific frequencies. Pyradiosky uses astropy [@astropy:2013; 64 | @astropy:2018; @astropy:2022] for most coordinate transforms and for VOTable 65 | support, it also interfaces with the lunarsky [@lunarsky] package to support 66 | moon-based coordinate systems. Pyradiosky uses astropy-healpix [@astropy-healpix] 67 | for interfacing with HEALPix [@healpix] maps. 68 | 69 | Pyradiosky is different than other commonly used code to handle catalogs, like 70 | TOPCAT [@topcat] and astropy's VOTable sub-package, which are primarily table 71 | interfaces that support table operations but not the more complicated 72 | astronomy-aware operations (e.g. polarization coordinate transformations) 73 | supported by pyradiosky. Pyradiosky also provides a unified interface for 74 | catalogs and HEALPix sky maps. 75 | 76 | As part of the Radio Astronomy Software Group suite, along with pyuvdata 77 | [@pyuvdata2017] and pyuvsim [@pyuvsim2019], pyradiosky provides software 78 | infrastructure for a broad range of radio astronomy applications including 79 | enabling rigorous, seamless testing of 21 cm cosmology analysis developments. 80 | 81 | # Acknowledgements 82 | 83 | Support for pyradiosky was provided by NSF awards #1835421 and #1835120. 84 | 85 | # References 86 | -------------------------------------------------------------------------------- /src/pyradiosky/cli.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | """Command line scripts.""" 4 | 5 | import argparse 6 | 7 | import numpy as np 8 | 9 | import pyradiosky.utils as utils 10 | 11 | 12 | def download_gleam(argv=None): 13 | """Download the GLEAM vot table from Vizier.""" 14 | parser = argparse.ArgumentParser( 15 | description="A command-line script to download the GLEAM vot table from Vizier." 16 | ) 17 | parser.add_argument( 18 | "--path", type=str, help="Folder location to save catalog to.", default="." 19 | ) 20 | parser.add_argument( 21 | "--filename", type=str, help="Filename to save catalog to.", default="gleam.vot" 22 | ) 23 | parser.add_argument( 24 | "--overwrite", 25 | help="Download file even if it already exists", 26 | default=False, 27 | action="store_true", 28 | ) 29 | parser.add_argument( 30 | "--row_limit", 31 | type=int, 32 | help="Max number of rows (sources) to download, default is to download " 33 | "all rows.", 34 | default=None, 35 | ) 36 | parser.add_argument( 37 | "--for_testing", 38 | help="Download a file to use for unit tests. If True, all columns that " 39 | "are ever used are included (e.g. for different spectral types and errors), " 40 | "the rows are limited to 50, the path and filename are set to put the " 41 | "file in the correct location for the unit tests and the overwrite keyword " 42 | "is set to True.", 43 | default=False, 44 | action="store_true", 45 | ) 46 | 47 | args = parser.parse_args(argv) 48 | 49 | utils.download_gleam( 50 | path=args.path, 51 | filename=args.filename, 52 | overwrite=args.overwrite, 53 | row_limit=args.row_limit, 54 | for_testing=args.for_testing, 55 | ) 56 | 57 | 58 | def make_flat_spectrum_eor(argv=None): 59 | """Generate a noise-like SkyModel with a flat P(k) cosmological power spectrum.""" 60 | parser = argparse.ArgumentParser( 61 | description="A command-line script to generate a noise-like EoR SkyModel " 62 | "with a flat P(k) cosmological power spectrum" 63 | ) 64 | parser.add_argument( 65 | "-v", 66 | "--variance", 67 | type=float, 68 | required=True, 69 | help="Variance of the signal, in Kelvin^2, at the reference channel.", 70 | ) 71 | parser.add_argument( 72 | "--nside", 73 | type=int, 74 | required=True, 75 | help="HEALPix NSIDE parameter. Must be a power of 2.", 76 | ) 77 | parser.add_argument( 78 | "--ref_chan", 79 | type=int, 80 | default=0, 81 | help="Frequency channel to set as reference. Defaults to 0, meaning the " 82 | "start frequency.", 83 | ) 84 | parser.add_argument( 85 | "-s", 86 | "--start_freq", 87 | type=float, 88 | required=True, 89 | help="Start frequency (in Hz), used to set up the frequency array as " 90 | "freq_array = np.linspace(start_freq, end_freq, Nfreqs).", 91 | ) 92 | parser.add_argument( 93 | "-e", 94 | "--end_freq", 95 | type=float, 96 | required=True, 97 | help="End frequency (in Hz), used to set up the frequency array as " 98 | "freq_array = np.linspace(start_freq, end_freq, Nfreqs) (will be the " 99 | "last included frequency).", 100 | ) 101 | parser.add_argument( 102 | "-N", 103 | "--nfreqs", 104 | type=int, 105 | required=True, 106 | help="Number of frequencies, used to set up the frequency array as " 107 | "freq_array = np.linspace(start_freq, end_freq, Nfreqs).", 108 | ) 109 | parser.add_argument( 110 | "--filename", type=str, help="Output file name", default="noise_sky.hdf5" 111 | ) 112 | parser.add_argument( 113 | "--frame", 114 | type=str, 115 | help="Astropy Frame for output SkyModel, default ICRS", 116 | default="icrs", 117 | ) 118 | 119 | args = parser.parse_args(argv) 120 | 121 | variance = args.variance 122 | nside = args.nside 123 | frame = args.frame 124 | 125 | filename = args.filename 126 | start_freq = args.start_freq 127 | end_freq = args.end_freq 128 | Nfreqs = args.nfreqs 129 | 130 | freq_array = np.linspace(start_freq, end_freq, Nfreqs) 131 | 132 | print( 133 | f"Generating sky model, nside {nside}," 134 | f" and variance {variance} K^2 at channel {args.ref_chan}." 135 | ) 136 | 137 | sky = utils.flat_spectrum_skymodel( 138 | variance=variance, 139 | nside=nside, 140 | freqs=freq_array, 141 | ref_chan=args.ref_chan, 142 | frame=frame, 143 | ) 144 | sky.check() 145 | print(sky.history) 146 | print(f"Saving to {filename}.") 147 | sky.write_skyh5(filename) 148 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## Checklists: 11 | 12 | 13 | ### Bug Fix Checklist: 14 | 15 | 16 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 17 | - [ ] My code follows the code style of this project. 18 | - [ ] My fix includes a new test that breaks as a result of the bug (if possible). 19 | - [ ] My change includes a breaking change 20 | - [ ] My change includes backwards compatibility and deprecation warnings (if possible). 21 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md). 22 | 23 | ### New Feature Checklist: 24 | 25 | 26 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 27 | - [ ] My code follows the code style of this project. 28 | - [ ] I have added or updated the docstrings associated with my feature using the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html). 29 | - [ ] I have updated the tutorial to highlight my new feature (if appropriate). 30 | - [ ] I have added tests to cover my new feature. 31 | - [ ] My change includes a breaking change 32 | - [ ] My change includes backwards compatibility and deprecation warnings (if possible). 33 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md). 34 | 35 | ### Documentation Change Checklist: 36 | 37 | 38 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 39 | - [ ] My code follows the code style of this project. 40 | Documentation change checklist: 41 | - [ ] Any updated docstrings use the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html). 42 | 43 | 44 | ### Build/CI Change Checklist: 45 | 46 | 47 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 48 | - [ ] My code follows the code style of this project. 49 | - [ ] If required or optional dependencies have changed (including version numbers), I have updated the readme to reflect this. 50 | - [ ] If this is a new CI setup, I have added the associated badge to the readme 51 | 52 | ### Version Change Checklist 53 | 54 | 55 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 56 | - [ ] My code follows the code style of this project. 57 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md) to put all the unreleased changes under the new version (leaving the unreleased section empty). 58 | 59 | ### Other Change Checklist 60 | 61 | 62 | - [ ] I have read the [contribution guide](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/.github/CONTRIBUTING.md). 63 | - [ ] My code follows the code style of this project. 64 | - [ ] Any new or updated docstrings use the [numpy docstring format](https://numpydoc.readthedocs.io/en/latest/format.html). 65 | - [ ] I have updated the tutorial to highlight my new feature (if appropriate). 66 | - [ ] I have added tests to cover any changes. 67 | - [ ] My change includes a breaking change 68 | - [ ] My change includes backwards compatibility and deprecation warnings (if possible). 69 | - [ ] I have updated the [CHANGELOG](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md) if appropriate. 70 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration file for the Sphinx documentation builder. 3 | # 4 | # This file only contains a selection of the most common options. For a full 5 | # list see the documentation: 6 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 7 | 8 | import os 9 | import sys 10 | from io import StringIO 11 | 12 | from docutils import nodes, statemachine 13 | from sphinx.util.docutils import SphinxDirective 14 | 15 | import pyradiosky 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | sys.path.insert(0, os.path.abspath("../pyradiosky/")) 21 | readme_file = os.path.join(os.path.abspath("../"), "README.md") 22 | index_file = os.path.join(os.path.abspath("../docs"), "index.rst") 23 | skymodel_file = os.path.join(os.path.abspath("../docs"), "skymodel.rst") 24 | 25 | 26 | # -- Path setup -------------------------------------------------------------- 27 | 28 | # If extensions (or modules to document with autodoc) are in another directory, 29 | # add these directories to sys.path here. If the directory is relative to the 30 | # documentation root, use os.path.abspath to make it absolute, like shown here. 31 | # 32 | # import os 33 | # import sys 34 | # sys.path.insert(0, os.path.abspath('.')) 35 | 36 | 37 | # -- Project information ----------------------------------------------------- 38 | 39 | project = 'pyradiosky' 40 | copyright = '2019, Radio Astronomy Software Group' 41 | author = 'Radio Astronomy Software Group' 42 | 43 | # The full version, including alpha/beta/rc tags 44 | version = pyradiosky.__version__ 45 | release = version 46 | 47 | 48 | # -- General configuration --------------------------------------------------- 49 | 50 | # Add any Sphinx extension module names here, as strings. They can be 51 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 52 | # ones. 53 | extensions = [ 54 | "sphinx.ext.autodoc", 55 | "sphinx.ext.doctest", 56 | "sphinx.ext.todo", 57 | "sphinx.ext.coverage", 58 | "sphinx.ext.viewcode", 59 | "sphinx.ext.napoleon", 60 | "sphinx.ext.intersphinx", 61 | "sphinx_rtd_theme", 62 | ] 63 | # set this to properly handle multiple input params with the same type/shape 64 | napoleon_use_param = False 65 | 66 | # set this to handle returns section more uniformly 67 | napoleon_use_rtype = False 68 | 69 | # use this to create custom sections 70 | # currently used for the SkyModel.read method 71 | napoleon_custom_sections = [ 72 | ("GLEAM", "params_style"), 73 | ("FHD", "params_style"), 74 | ("VOTable", "params_style"), 75 | ("SkyH5", "params_style") 76 | ] 77 | 78 | # turn off alphabetical ordering in autodoc 79 | autodoc_member_order = "bysource" 80 | 81 | # Add any paths that contain templates here, relative to this directory. 82 | templates_path = ['_templates'] 83 | 84 | # List of patterns, relative to source directory, that match files and 85 | # directories to ignore when looking for source files. 86 | # This pattern also affects html_static_path and html_extra_path. 87 | exclude_patterns = ['build', 'Thumbs.db', '.DS_Store'] 88 | 89 | 90 | # -- Options for HTML output ------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = "sphinx_rtd_theme" 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ['_static'] 100 | 101 | 102 | def build_custom_docs(app): 103 | sys.path.append(os.getcwd()) 104 | import make_index 105 | import make_skymodel 106 | 107 | make_index.write_index_rst(readme_file=readme_file, write_file=index_file) 108 | make_skymodel.write_skymodel_rst(write_file=skymodel_file) 109 | 110 | 111 | # this is to enable running python in the rst files. 112 | # first use case is to better format the KNOWN_TELESCOPES dict 113 | class ExecDirective(SphinxDirective): 114 | """Execute the specified python code and insert the output into the document""" 115 | 116 | has_content = True 117 | 118 | def run(self): 119 | oldStdout, sys.stdout = sys.stdout, StringIO() 120 | 121 | tab_width = self.options.get( 122 | "tab-width", self.state.document.settings.tab_width 123 | ) 124 | source = self.state_machine.input_lines.source( 125 | self.lineno - self.state_machine.input_offset - 1 126 | ) 127 | 128 | try: 129 | exec("\n".join(self.content)) 130 | text = sys.stdout.getvalue() 131 | lines = statemachine.string2lines(text, tab_width, convert_whitespace=True) 132 | self.state_machine.insert_input(lines, source) 133 | return [] 134 | except Exception: 135 | return [ 136 | nodes.error( 137 | None, 138 | nodes.paragraph( 139 | text="Unable to execute python " 140 | f"code at {os.path.basename(source)}:{self.lineno}" 141 | ), 142 | nodes.paragraph(text=str(sys.exc_info()[1])), 143 | ) 144 | ] 145 | finally: 146 | sys.stdout = oldStdout 147 | 148 | 149 | def setup(app): 150 | app.connect("builder-inited", build_custom_docs) 151 | app.add_directive("exec", ExecDirective) 152 | 153 | # -- Options for intersphinx extension --------------------------------------- 154 | 155 | 156 | # Example configuration for intersphinx: refer to the Python standard library. 157 | intersphinx_mapping = { 158 | "python": ("https://docs.python.org/3/", None), 159 | "pyuvdata": ("https://pyuvdata.readthedocs.io/en/latest/", None), 160 | "astropy": ("https://docs.astropy.org/en/stable/", None), 161 | "astropy_healpix": ("https://astropy-healpix.readthedocs.io/en/latest/", None), 162 | "numpy": ("https://numpy.org/doc/stable/", None), 163 | # "lunarsky": (None), lunarsky is not on RTD yet. Add it here when it is. 164 | } 165 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pyradiosky 2 | 3 | Thank you for considering contributing to pyradiosky! It's our community of 4 | users and contributors that makes pyradiosky powerful and relevant. 5 | 6 | pyradiosky is an open source project, driven by our community of contributors. 7 | There are many ways to contribute, including writing tutorials, improving the 8 | documentation, submitting bug reports and feature requests or writing code 9 | which can be incorporated into pyradiosky itself. Following the guidelines and 10 | patterns suggested below helps us maintain a high quality code base and review 11 | your contributions faster. 12 | 13 | Please note we have a [code of conduct](../CODE_OF_CONDUCT.md), please follow 14 | it in all your interactions with the project. 15 | 16 | ## How to report a bug 17 | First check the issues to see if your bug already exists. Feel free to comment 18 | on the existing issue to provide more context or just to note that it is 19 | affecting you as well. If your bug is not in the issue list, make a new issue. 20 | 21 | When making an issue, try to provide as much context as possible including: 22 | 23 | 1. What version of python and pyradiosky are you using? 24 | 2. What operating system are you using? 25 | 3. What did you do? 26 | 4. What did you expect to see? 27 | 5. What did you see instead? 28 | 6. Any code to reproduce the bug (as minimal an example as possible) 29 | 30 | If you're really inspired, you can make a pull request adding a test that fails 31 | because of the bug. This is likely to lead to the bug being fixed more quickly. 32 | 33 | ## How to suggest a feature or enhancement 34 | First check the issues to see if your feature request already exists. Feel 35 | free to comment on the existing issue to provide more context or just to note 36 | that you would like to see the feature implemented as well. If your feature 37 | request is not in the issue list, make a new issue. 38 | 39 | When making a feature request, try to provide as much context as possible. 40 | Feel free to include suggestions for implementations. 41 | 42 | ## Guidelines for contributing to the code 43 | 44 | * Create issues for any major changes and enhancements that you wish to make. 45 | Discuss things transparently and get community feedback. 46 | * Keep pull requests as small as possible. Ideally each pull request should 47 | implement ONE feature or bugfix. If you want to add or fix more than one thing, 48 | submit more than one pull request. 49 | * Do not commit changes to files that are irrelevant to your feature or bugfix. 50 | * Be aware that the pull request review process is not immediate, and is 51 | generally proportional to the size of the pull request. 52 | * Be welcoming to newcomers and encourage diverse new contributors from all 53 | backgrounds. See our [Code of Conduct](../CODE_OF_CONDUCT.md). 54 | 55 | ### Your First Contribution 56 | 57 | Contributing for the first time can seem daunting, but we value contributions 58 | from our user community and we will do our best to help you through the process. 59 | Here’s some advice to help make your work on pyradiosky more useful and rewarding. 60 | 61 | * Use issue labels to guide you 62 | - Unsure where to begin contributing to pyradiosky? 63 | You can start by looking through issues labeled `good first issue` and 64 | `help wanted` issues. 65 | 66 | * Pick a subject area that you care about, that you are familiar with, or that 67 | you want to learn about 68 | - Some of the feature request issues involve file formats that the core 69 | developers may not be very familiar with. If you know about those formats, 70 | consider helping on those issues. 71 | 72 | * Start small 73 | - It’s easier to get feedback on a little issue or pull request than on a big one. 74 | 75 | * If you’re going to take on a big change, make sure that your idea has support first 76 | - This means getting someone else to confirm that a bug is real before you 77 | fix the issue, and ensuring that there’s consensus on a proposed feature 78 | before you work to implement it. Use the issue log to start conversations 79 | about major changes and enhancements. 80 | 81 | * Be bold! Leave feedback! 82 | - Sometimes it can be scary to make new issues or comment on existing issues 83 | or pull requests, but contributions from the wider community are what ensure 84 | that pyradiosky serves the whole community as well as possible. 85 | 86 | * Be rigorous 87 | - Our requirements on code style, testing and documentation are important. 88 | If you have questions about them or difficulty meeting them, please ask for 89 | help, we will do our best to support you. Your contributions will be 90 | reviewed and integrated much more quickly if your pull request meets the 91 | requirements. 92 | 93 | If you are new to the GitHub or the pull request process you can start by 94 | taking a look at these tutorials: 95 | http://makeapullrequest.com/ and http://www.firsttimersonly.com/. If you have 96 | more questions, feel free to ask for help, everyone is a beginner at first and 97 | all of us are still learning! 98 | 99 | ### Getting started 100 | 101 | 1. Create your own fork or branch of the code 102 | 2. Do the changes in your fork or branch 103 | 3. If you like the change and think the project could use it: 104 | - If you're fixing a bug, include a new test that breaks as a result of the bug (if possible) 105 | - Ensure that all your new code is covered by tests and that the existing 106 | tests pass. Tests can be run and coverage automatically created by running 107 | `pytest` in the top level directory. Coverage reports require the `pytest-cov` 108 | plug-in. 109 | - Ensure that your code meets the PEP8 style guidelines. You can check that 110 | your code will pass our linting tests by installing the `pre-commit` package 111 | or by running `pre-commit run -a` in the top-level pyradiosky directory. 112 | - Ensure that you fully document any new features via docstrings. 113 | - You can see the full pull request checklist [here](PULL_REQUEST_TEMPLATE.md) 114 | 115 | ### Code review process 116 | 117 | The core team looks at pull requests on a regular basis and tries to provide 118 | feedback as quickly as possible. Larger pull requests generally require more 119 | time for review and may require discussion among the core team. 120 | 121 | # Community 122 | In addition to conversations on github, we also communicate via Slack and on 123 | biweekly telecons. We welcome contributors who want to join those discussions, 124 | [contact the RASG managers](mailto:rasgmanagers@gmail.com) if you'd like to be 125 | invited to join them. 126 | -------------------------------------------------------------------------------- /tests/test_spherical_coords_transforms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | 4 | import numpy as np 5 | import pytest 6 | from astropy import units 7 | from astropy.coordinates import EarthLocation, SkyCoord 8 | from astropy.time import Time 9 | from scipy.linalg import orthogonal_procrustes as ortho_procr 10 | 11 | from pyradiosky import spherical_coords_transforms as sct 12 | 13 | 14 | @pytest.mark.parametrize("func_name", ["r_hat", "theta_hat", "phi_hat"]) 15 | def test_hat_errors(func_name): 16 | with pytest.raises(ValueError) as cm: 17 | getattr(sct, func_name)([0, 0], [0]) 18 | assert str(cm.value).startswith("theta and phi must have the same shape") 19 | 20 | 21 | def test_rotate_points_3d(): 22 | array_location = EarthLocation(lat="-30d43m17.5s", lon="21d25m41.9s", height=1073.0) 23 | time0 = Time("2018-03-01 18:00:00", scale="utc", location=array_location) 24 | 25 | ha_off = 0.5 26 | ha_delta = 0.01 27 | time_offsets = np.arange(-ha_off, ha_off + ha_delta, ha_delta) 28 | zero_indx = np.argmin(np.abs(time_offsets)) 29 | # make sure we get a true zenith time 30 | time_offsets[zero_indx] = 0.0 31 | times = time0 + time_offsets * units.hr 32 | ntimes = times.size 33 | 34 | zenith = SkyCoord( 35 | alt=90.0 * units.deg, 36 | az=0 * units.deg, 37 | frame="altaz", 38 | obstime=time0, 39 | location=array_location, 40 | ) 41 | zenith_icrs = zenith.transform_to("icrs") 42 | 43 | src_astropy = SkyCoord( 44 | ra=zenith_icrs.ra, dec=zenith_icrs.dec, obstime=times, location=array_location 45 | ) 46 | src_astropy_altaz = src_astropy.transform_to("altaz") 47 | assert np.isclose(src_astropy_altaz.alt.rad[zero_indx], np.pi / 2) 48 | 49 | alts = np.zeros(ntimes) 50 | azs = np.zeros(ntimes) 51 | for ti, time in enumerate(times): 52 | alts[ti] = src_astropy_altaz[ti].alt.radian 53 | azs[ti] = src_astropy_altaz[ti].az.radian 54 | 55 | # unit vectors to be transformed by astropy 56 | x_c = np.array([1.0, 0, 0]) 57 | y_c = np.array([0, 1.0, 0]) 58 | z_c = np.array([0, 0, 1.0]) 59 | 60 | # astropy 2 vs 3 use a different keyword name 61 | rep_keyword = "representation_type" 62 | 63 | rep_dict = {} 64 | rep_dict[rep_keyword] = "cartesian" 65 | axes_icrs = SkyCoord( 66 | x=x_c, 67 | y=y_c, 68 | z=z_c, 69 | obstime=time, 70 | location=array_location, 71 | frame="icrs", 72 | **rep_dict, 73 | ) 74 | 75 | axes_altaz = axes_icrs.transform_to("altaz") 76 | setattr(axes_altaz, rep_keyword, "cartesian") 77 | 78 | """ This transformation matrix is generally not orthogonal 79 | to better than 10^-7, so let's fix that. """ 80 | 81 | R_screwy = axes_altaz.cartesian.xyz 82 | R_avg, _ = ortho_procr(R_screwy, np.eye(3)) 83 | 84 | # Note the transpose, to be consistent with calculation in sct 85 | R_avg = np.array(R_avg).T 86 | 87 | # Find mathematical points and vectors for RA/Dec 88 | theta_radec = np.pi / 2.0 - src_astropy.dec.radian 89 | phi_radec = src_astropy.ra.radian 90 | radec_vec = sct.r_hat(theta_radec, phi_radec) 91 | assert radec_vec.shape == (3,) 92 | 93 | # Find mathematical points and vectors for Alt/Az 94 | theta_altaz = np.pi / 2.0 - alts[ti] 95 | phi_altaz = azs[ti] 96 | altaz_vec = sct.r_hat(theta_altaz, phi_altaz) 97 | assert altaz_vec.shape == (3,) 98 | 99 | intermediate_vec = np.matmul(R_avg, radec_vec) 100 | 101 | R_perturb = sct.vecs2rot(r1=intermediate_vec, r2=altaz_vec) 102 | 103 | intermediate_theta, intermediate_phi = sct.rotate_points_3d( 104 | R_avg, theta_radec, phi_radec 105 | ) 106 | R_perturb_pts = sct.vecs2rot( 107 | theta1=intermediate_theta, 108 | phi1=intermediate_phi, 109 | theta2=theta_altaz, 110 | phi2=phi_altaz, 111 | ) 112 | 113 | assert np.allclose(R_perturb, R_perturb_pts) 114 | 115 | R_exact = np.matmul(R_perturb, R_avg) 116 | 117 | calc_theta_altaz, calc_phi_altaz = sct.rotate_points_3d( 118 | R_exact, theta_radec, phi_radec 119 | ) 120 | 121 | if ti == zero_indx: 122 | assert np.isclose(calc_theta_altaz, theta_altaz, atol=1e-7) 123 | else: 124 | assert np.isclose(calc_theta_altaz, theta_altaz) 125 | 126 | if ti == zero_indx: 127 | assert np.isclose(calc_phi_altaz, phi_altaz, atol=2e-4) 128 | else: 129 | assert np.isclose(calc_phi_altaz, phi_altaz) 130 | 131 | v1 = sct.r_hat(theta_radec, phi_radec) 132 | v2 = sct.r_hat(theta_altaz, phi_altaz) 133 | Rv1 = np.matmul(R_exact, v1) 134 | assert np.allclose(Rv1, v2) 135 | 136 | coherency_rot_matrix_two_pt = sct.spherical_basis_vector_rotation_matrix( 137 | theta_radec, phi_radec, R_exact, theta_altaz, phi_altaz 138 | ) 139 | 140 | coherency_rot_matrix_one_pt = sct.spherical_basis_vector_rotation_matrix( 141 | theta_radec, phi_radec, R_exact 142 | ) 143 | 144 | if ti == zero_indx: 145 | assert np.allclose( 146 | coherency_rot_matrix_two_pt, coherency_rot_matrix_one_pt, atol=1e-3 147 | ) 148 | else: 149 | assert np.allclose( 150 | coherency_rot_matrix_two_pt, coherency_rot_matrix_one_pt, atol=1e-14 151 | ) 152 | 153 | # check errors are raised appropriately 154 | with pytest.raises(ValueError) as cm: 155 | sct.rotate_points_3d(R_exact[0:1, :], theta_radec, phi_radec) 156 | assert str(cm.value).startswith("rot_matrix must be a 3x3 array") 157 | 158 | with pytest.raises(ValueError) as cm: 159 | sct.vecs2rot(r1=intermediate_vec, theta2=theta_altaz, phi2=phi_altaz) 160 | assert str(cm.value).startswith("Either r1 and r2 must be supplied or all of") 161 | 162 | with pytest.raises(ValueError) as cm: 163 | sct.vecs2rot(r1=intermediate_vec[0:1], r2=altaz_vec) 164 | assert str(cm.value).startswith("r1 and r2 must be length 3 vectors") 165 | 166 | with pytest.raises(ValueError) as cm: 167 | sct.vecs2rot(r1=intermediate_vec * 2, r2=altaz_vec) 168 | assert str(cm.value).startswith("r1 and r2 must be unit vectors") 169 | 170 | norm = np.cross(intermediate_vec, altaz_vec) 171 | sinPsi = np.sqrt(np.dot(norm, norm)) 172 | n_hat = norm / sinPsi # Trouble lurks if Psi = 0. 173 | cosPsi = np.dot(intermediate_vec, altaz_vec) 174 | Psi = np.arctan2(sinPsi, cosPsi) 175 | 176 | with pytest.raises(ValueError) as cm: 177 | sct.axis_angle_rotation_matrix(n_hat[0:1], Psi) 178 | assert str(cm.value).startswith("axis must be a must be length 3 vector") 179 | 180 | with pytest.raises(ValueError) as cm: 181 | sct.axis_angle_rotation_matrix(n_hat * 2, Psi) 182 | assert str(cm.value).startswith("axis must be a unit vector") 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyradiosky 2 | ![](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/actions/workflows/testsuite.yaml/badge.svg?branch=main) 3 | [![codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyradiosky/branch/main/graph/badge.svg)](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyradiosky) 4 | [![](https://readthedocs.org/projects/pyradiosky/badge/?version=latest)](https://app.readthedocs.org/projects/pyradiosky/) 5 | [![DOI](https://joss.theoj.org/papers/10.21105/joss.06503/status.svg)](https://doi.org/10.21105/joss.06503) 6 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.11187469.svg)](https://doi.org/10.5281/zenodo.11187469) 7 | 8 | Python objects and interfaces for representing diffuse, extended and compact 9 | astrophysical radio sources. 10 | 11 | The primary user class is `SkyModel`, which supports: 12 | 13 | - catalogs of point sources (read/write to hd5 and text files, read VOTables) 14 | - diffuse models as HEALPix maps (read/write to hd5 files) 15 | - conversion between any astropy supported coordinates (e.g. J2000 RA/Dec) and 16 | Azimuth/Elevation including calculating full polarization coherencies in Az/El. 17 | 18 | # File formats 19 | 20 | pyradiosky supports reading in catalogs from several formats, including VO Table files, 21 | text files, [FHD](https://github.com/EoRImaging/FHD) catalog files and SkyH5 files and 22 | supports writing to SkyH5 and text files. SkyH5 is an HDF5-based file format defined by 23 | the pyradiosky team, a full description is in the [SkyH5 memo](docs/references/skyh5_memo.pdf). 24 | 25 | # Community Guidelines 26 | Contributions to this package to add new file formats or address any of the 27 | issues in the [issue log](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/issues) 28 | are very welcome, as are bug reports and feature requests. 29 | Please see our [guide on contributing](.github/CONTRIBUTING.md) 30 | 31 | # Versioning 32 | We use a `generation.major.minor` version number format. We use the `generation` 33 | number for very significant improvements or major rewrites, the `major` number 34 | to indicate substantial package changes and the `minor` number to release smaller 35 | incremental updates (which do not include breaking API changes). We do our best 36 | to provide a significant period (usually 2 major generations) of deprecation 37 | warnings for all breaking changes to the API. 38 | We track all changes in our [changelog](https://github.com/RadioAstronomySoftwareGroup/pyradiosky/blob/main/CHANGELOG.md). 39 | 40 | # Documentation 41 | Developer API documentation is hosted [here](https://pyradiosky.readthedocs.io/en/latest/). 42 | 43 | # Citation 44 | Please cite pyradiosky by citing our JOSS paper: 45 | 46 | Hazelton et al., (2024). pyradiosky: A Python package for Radio Sky Models. 47 | Journal of Open Source Software, 9(97), 6503, https://doi.org/10.21105/joss.06503 48 | 49 | [ADS Link](https://ui.adsabs.harvard.edu/abs/2024JOSS....9.6503H) 50 | 51 | # Installation 52 | Simple installation via pip is available for users, developers should follow 53 | the directions under [Developer Installation](#developer-installation) below. 54 | 55 | For simple installation, the latest stable version is available via pip with 56 | `pip install pyradiosky`. 57 | 58 | There are some optional dependencies that are required for specific functionality, 59 | which will not be installed automatically by pip. 60 | See [Dependencies](#dependencies) for details on installing optional dependencies. 61 | 62 | ## Dependencies 63 | 64 | If you are using `conda` to manage your environment, you may wish to install the 65 | following packages before installing `pyradiosky`: 66 | 67 | Required: 68 | 69 | * astropy>=6.0 70 | * h5py>=3.7 71 | * numpy>=1.23 72 | * scipy>=1.9 73 | * python>=3.11 74 | * pyuvdata>=3.2.3 75 | * setuptools_scm>=8.1 76 | 77 | Optional: 78 | 79 | * astropy-healpix>=1.0.2 (for working with beams in HEALPix formats) 80 | * astroquery>=0.4.4 (for downloading GLEAM and other VizieR catalogs) 81 | * lunarsky>=0.2.5 (for supporting telescope locations on the moon) 82 | 83 | We suggest using conda to install all the dependencies. To install 84 | pyuvdata, astropy-healpix and astroquery, you'll need to add conda-forge as a channel 85 | (```conda config --add channels conda-forge```). 86 | 87 | If you do not want to use conda, the packages are also available on PyPI. 88 | You can install the optional dependencies via pip by specifying an option 89 | when you install pyradiosky, as in ```pip install .[healpix]``` 90 | which will install all the required packages for using the HEALPix functionality 91 | in pyradiosky. The options that can be passed in this way are: 92 | [`healpix`, `astroquery`, `lunarsky`, `all`, `doc`, `test`, `dev`]. 93 | The first three (`healpix`, `astroquery`, `lunarsky`) enable various specific 94 | functionality while `all` will install all optional dependencies. 95 | The last three (`doc`, `test` and `dev`) may be useful for developers of pyradiosky. 96 | 97 | ## Developer Installation 98 | Clone the repository using 99 | ```git clone https://github.com/RadioAstronomySoftwareGroup/pyradiosky.git``` 100 | 101 | Navigate into the pyradiosky directory and run `pip install .` 102 | (note that `python setup.py install` does not work). 103 | Note that this will attempt to automatically install any missing dependencies. 104 | If you use anaconda or another package manager you might prefer to first install 105 | the dependencies as described in [Dependencies](#dependencies). 106 | 107 | To install without dependencies, run `pip install --no-deps` 108 | 109 | If you want to do development on pyradiosky, in addition to the other dependencies 110 | you will need the following packages: 111 | 112 | * pytest 113 | * pytest-cov 114 | * coverage 115 | * pre-commit 116 | * sphinx 117 | * pypandoc 118 | 119 | One way to ensure you have all the needed packages is to use the included 120 | `environment.yaml` file to create a new environment that will 121 | contain all the optional dependencies along with dependencies required for 122 | testing and development (```conda env create -f environment.yml```). 123 | Alternatively, you can specify `dev` when installing pyradiosky 124 | (as in `pip install pyradiosky[dev]`) to install the packages needed for testing 125 | and documentation development. 126 | 127 | To use pre-commit to prevent committing code that does not follow our style, 128 | you'll need to run `pre-commit install` in the top level `pyradiosky` directory. 129 | 130 | # Tests 131 | Uses the `pytest` package to execute test suite. 132 | From the pyradiosky directory run ```pytest``` or ```python -m pytest```. 133 | 134 | # Maintainers 135 | pyradiosky is maintained by the RASG Managers, which currently include: 136 | 137 | - Adam Beardsley (Arizona State University) 138 | - Bryna Hazelton (University of Washington) 139 | - Daniel Jacobs (Arizona State University) 140 | - Paul La Plante (University of California, Berkeley) 141 | - Jonathan Pober (Brown University) 142 | 143 | Please use the channels discussed in the [guide on contributing](.github/CONTRIBUTING.md) 144 | for code-related discussions. You can contact us privately if needed at 145 | [rasgmanagers@gmail.com](mailto:rasgmanagers@gmail.com). 146 | 147 | # Acknowledgments 148 | Support for pyradiosky was provided by NSF awards #1835421 and #1835120. 149 | -------------------------------------------------------------------------------- /.github/workflows/testsuite.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | 8 | jobs: 9 | tests: 10 | env: 11 | ENV_NAME: full_deps 12 | PYTHON: ${{ matrix.python-version }} 13 | OS: ${{ matrix.os }} 14 | name: Testing 15 | runs-on: ${{ matrix.os }} 16 | defaults: 17 | run: 18 | # Adding -l {0} helps ensure conda can be found properly. 19 | shell: bash -l {0} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ubuntu-latest, macos-latest, windows-latest] 24 | python-version: ["3.11", "3.12", "3.13"] 25 | steps: 26 | - uses: actions/checkout@main 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Setup Miniconda 31 | uses: conda-incubator/setup-miniconda@v3 32 | with: 33 | miniforge-version: latest 34 | python-version: ${{ env.PYTHON }} 35 | environment-file: ci/${{ env.ENV_NAME }}.yaml 36 | activate-environment: ${{ env.ENV_NAME }} 37 | conda-remove-defaults: "true" 38 | 39 | - name: Conda Info 40 | run: | 41 | conda info -a 42 | conda list 43 | PYVER=`python -c "import sys; print('{:d}.{:d}'.format(sys.version_info.major, sys.version_info.minor))"` 44 | if [[ $PYVER != ${{ env.PYTHON }} ]]; then 45 | exit 1; 46 | fi 47 | 48 | - name: Install 49 | # calling git right before the install seems to prevent a time-out within setuptools_scm on MacOS 50 | run: | 51 | git describe --tags 52 | git version 53 | SETUPTOOLS_SCM_DEBUG=1 pip install --no-deps . 54 | 55 | - name: Run Tests 56 | run: | 57 | python -m pytest --cov=pyradiosky --cov-config=.coveragerc --cov-report xml:./coverage.xml 58 | 59 | - name: check coverage report 60 | run: | 61 | ls 62 | cat coverage.xml 63 | 64 | - uses: codecov/codecov-action@v5 65 | if: success() 66 | with: 67 | token: ${{ secrets.CODECOV_TOKEN }} #required 68 | files: ./coverage.xml #optional 69 | 70 | min_deps: 71 | env: 72 | ENV_NAME: min_deps 73 | PYTHON: "3.13" 74 | name: Min Deps Testing 75 | runs-on: ubuntu-latest 76 | strategy: 77 | fail-fast: false 78 | defaults: 79 | run: 80 | # Adding -l {0} helps ensure conda can be found properly. 81 | shell: bash -l {0} 82 | steps: 83 | - uses: actions/checkout@main 84 | with: 85 | fetch-depth: 0 86 | 87 | - name: Setup Miniconda 88 | uses: conda-incubator/setup-miniconda@v3 89 | with: 90 | miniforge-version: latest 91 | python-version: ${{ env.PYTHON }} 92 | environment-file: ci/${{ env.ENV_NAME }}.yaml 93 | activate-environment: ${{ env.ENV_NAME }} 94 | conda-remove-defaults: "true" 95 | 96 | - name: Conda Info 97 | run: | 98 | conda info -a 99 | conda list 100 | PYVER=`python -c "import sys; print('{:d}.{:d}'.format(sys.version_info.major, sys.version_info.minor))"` 101 | if [[ $PYVER != ${{ env.PYTHON }} ]]; then 102 | exit 1; 103 | fi 104 | 105 | - name: Install 106 | run: | 107 | pip install --no-deps . 108 | 109 | - name: Run Tests 110 | run: | 111 | python -m pytest --cov=pyradiosky --cov-config=.coveragerc --cov-report xml:./coverage.xml 112 | 113 | - name: check coverage report 114 | run: | 115 | ls 116 | cat coverage.xml 117 | 118 | - uses: codecov/codecov-action@v5 119 | if: success() 120 | with: 121 | token: ${{secrets.CODECOV_TOKEN}} #required 122 | files: ./coverage.xml #optional 123 | 124 | min_versions: 125 | env: 126 | ENV_NAME: min_versions 127 | PYTHON: "3.11" 128 | name: Min Versions Testing 129 | runs-on: ubuntu-latest 130 | strategy: 131 | fail-fast: false 132 | defaults: 133 | run: 134 | # Adding -l {0} helps ensure conda can be found properly. 135 | shell: bash -l {0} 136 | steps: 137 | - uses: actions/checkout@main 138 | with: 139 | fetch-depth: 0 140 | 141 | - name: Setup Miniconda 142 | uses: conda-incubator/setup-miniconda@v3 143 | with: 144 | miniforge-version: latest 145 | python-version: ${{ env.PYTHON }} 146 | environment-file: ci/${{ env.ENV_NAME }}.yaml 147 | activate-environment: ${{ env.ENV_NAME }} 148 | conda-remove-defaults: "true" 149 | 150 | - name: Conda Info 151 | run: | 152 | conda info -a 153 | conda list 154 | PYVER=`python -c "import sys; print('{:d}.{:d}'.format(sys.version_info.major, sys.version_info.minor))"` 155 | if [[ $PYVER != ${{ env.PYTHON }} ]]; then 156 | exit 1; 157 | fi 158 | 159 | - name: Install 160 | run: | 161 | pip install --no-deps -e . 162 | 163 | - name: Run Tests 164 | run: | 165 | python -m pytest --cov=pyradiosky --cov-config=.coveragerc --cov-report xml:./coverage.xml 166 | 167 | - name: check coverage report 168 | run: | 169 | ls 170 | cat coverage.xml 171 | 172 | - uses: codecov/codecov-action@v5 173 | if: success() 174 | with: 175 | token: ${{secrets.CODECOV_TOKEN}} #required 176 | files: ./coverage.xml #optional 177 | 178 | # Use pip for diversity 179 | warning_test: 180 | env: 181 | PYTHON: "3.12" 182 | name: Warning Test 183 | runs-on: ubuntu-latest 184 | steps: 185 | - uses: actions/checkout@main 186 | with: 187 | fetch-depth: 0 188 | 189 | - name: Setup Python 190 | uses: actions/setup-python@v4 191 | with: 192 | python-version: ${{ env.PYTHON }} 193 | 194 | - name: Install 195 | run: pip install -e .[dev] 196 | 197 | - name: Environment Info 198 | run: | 199 | pip list 200 | PYVER=`python -c "import sys; print('{:d}.{:d}'.format(sys.version_info.major, sys.version_info.minor))"` 201 | if [[ $PYVER != ${{ env.PYTHON }} ]]; then 202 | exit 1; 203 | fi 204 | 205 | - name: Run Tests 206 | run: | 207 | python -m pytest -W error --cov=pyradiosky --cov-config=.coveragerc --cov-report xml:./coverage.xml 208 | 209 | - name: check coverage report 210 | run: | 211 | ls 212 | cat coverage.xml 213 | 214 | - uses: codecov/codecov-action@v5 215 | if: success() 216 | with: 217 | token: ${{secrets.CODECOV_TOKEN}} #required 218 | files: ./coverage.xml #optional 219 | 220 | docs_test: 221 | env: 222 | ENV_NAME: full_deps 223 | PYTHON: "3.12" 224 | name: Tutorial Testing 225 | runs-on: ubuntu-latest 226 | defaults: 227 | run: 228 | # Adding -l {0} helps ensure conda can be found properly. 229 | shell: bash -l {0} 230 | strategy: 231 | fail-fast: false 232 | steps: 233 | - uses: actions/checkout@main 234 | with: 235 | fetch-depth: 0 236 | 237 | - name: Setup Miniconda 238 | uses: conda-incubator/setup-miniconda@v3 239 | with: 240 | auto-update-conda: true 241 | miniconda-version: "latest" 242 | python-version: ${{ env.PYTHON }} 243 | environment-file: ci/${{ env.ENV_NAME }}.yaml 244 | activate-environment: ${{ env.ENV_NAME }} 245 | conda-remove-defaults: "true" 246 | 247 | - name: Conda Info 248 | run: | 249 | conda info -a 250 | conda list 251 | PYVER=`python -c "import sys; print('{:d}.{:d}'.format(sys.version_info.major, sys.version_info.minor))"` 252 | if [[ $PYVER != ${{ env.PYTHON }} ]]; then 253 | exit 1; 254 | fi 255 | 256 | - name: Install 257 | run: | 258 | pip install --no-deps . 259 | 260 | - name: Run Tests 261 | run: | 262 | cd docs 263 | python -m pytest --doctest-glob="*.rst" -W "error::DeprecationWarning" 264 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | import os 4 | import subprocess # nosec 5 | 6 | import astropy.units as units 7 | import numpy as np 8 | import pytest 9 | from astropy.cosmology import Planck15 10 | 11 | from pyradiosky import SkyModel, utils as skyutils 12 | 13 | 14 | def test_stokes_tofrom_coherency(): 15 | stokesI = 4.5 16 | stokesQ = -0.3 17 | stokesU = 1.2 18 | stokesV = -0.15 19 | stokes = np.array([stokesI, stokesQ, stokesU, stokesV]) 20 | 21 | expected_coherency = ( 22 | 0.5 * np.array([[4.2, 1.2 + 0.15j], [1.2 - 0.15j, 4.8]]) * units.Jy 23 | ) 24 | 25 | with pytest.raises(ValueError, match="stokes_arr must be an astropy Quantity."): 26 | skyutils.stokes_to_coherency(stokes) 27 | 28 | coherency = skyutils.stokes_to_coherency(stokes * units.Jy) 29 | 30 | with pytest.raises( 31 | ValueError, match="coherency_matrix must be an astropy Quantity." 32 | ): 33 | skyutils.coherency_to_stokes(coherency.value) 34 | 35 | back_to_stokes = skyutils.coherency_to_stokes(coherency) 36 | 37 | assert np.allclose(stokes * units.Jy, back_to_stokes) 38 | 39 | # again, with multiple sources and a frequency axis. 40 | stokes = ( 41 | np.array( 42 | [[stokesI, stokesQ, stokesU, stokesV], [stokesI, stokesQ, stokesU, stokesV]] 43 | ).T 44 | * units.Jy 45 | ) 46 | 47 | stokes = stokes[:, np.newaxis, :] 48 | 49 | coherency = skyutils.stokes_to_coherency(stokes) 50 | back_to_stokes = skyutils.coherency_to_stokes(coherency) 51 | 52 | assert units.quantity.allclose(stokes, back_to_stokes) 53 | 54 | with pytest.raises(ValueError) as cm: 55 | skyutils.stokes_to_coherency(stokes[0:2, :]) 56 | assert str(cm.value).startswith( 57 | "First dimension of stokes_vector must be length 4." 58 | ) 59 | 60 | with pytest.raises(ValueError) as cm: 61 | skyutils.coherency_to_stokes(expected_coherency[0, :]) 62 | assert str(cm.value).startswith( 63 | "First two dimensions of coherency_matrix must be length 2." 64 | ) 65 | 66 | 67 | @pytest.mark.filterwarnings("ignore:Some stokes I values are negative") 68 | @pytest.mark.filterwarnings("ignore:Some spectral index values are NaN") 69 | @pytest.mark.filterwarnings("ignore:Some stokes values are NaNs") 70 | @pytest.mark.parametrize("stype", ["subband", "spectral_index", "flat"]) 71 | def test_download_gleam(tmp_path, stype): 72 | pytest.importorskip("astroquery") 73 | import requests # a dependency of astroquery 74 | 75 | fname = "gleam_cat.vot" 76 | filename = os.path.join(tmp_path, fname) 77 | n_src = 50 78 | 79 | try: 80 | output = subprocess.check_output( # nosec 81 | [ 82 | "download_gleam", 83 | "--path", 84 | str(tmp_path), 85 | "--filename", 86 | fname, 87 | "--row_limit", 88 | str(n_src), 89 | ] 90 | ) 91 | assert output.decode("utf-8").startswith( 92 | "GLEAM catalog downloaded and saved to" 93 | ) 94 | except requests.exceptions.ConnectionError: 95 | pytest.skip("Connection error w/ Vizier") 96 | 97 | sky = SkyModel() 98 | sky.read_gleam_catalog(filename, spectral_type=stype) 99 | assert sky.Ncomponents == n_src 100 | 101 | # Cannot just compare to the file we have in our data folder because there 102 | # seems to be some variation in which sources you get when you just download 103 | # some of the sources, especially on CI (the comparison works locally for me). 104 | 105 | # check there's not an error if the file exists and overwrite is False 106 | # and that the file is not replaced 107 | skyutils.download_gleam(path=tmp_path, filename=fname, row_limit=5) 108 | sky.read_gleam_catalog(filename, spectral_type=stype) 109 | assert sky.Ncomponents == n_src 110 | 111 | # check that the file is replaced if overwrite is True 112 | try: 113 | skyutils.download_gleam( 114 | path=tmp_path, filename=fname, row_limit=5, overwrite=True 115 | ) 116 | except requests.exceptions.ConnectionError: 117 | pytest.skip("Connection error w/ Vizier") 118 | sky2 = SkyModel() 119 | sky2.read_gleam_catalog(filename, spectral_type=stype) 120 | assert sky2.Ncomponents == 5 121 | 122 | 123 | def test_astroquery_missing_error(tmp_path): 124 | fname = "gleam_cat.vot" 125 | 126 | try: 127 | import astroquery # noqa 128 | 129 | pass 130 | except ImportError: 131 | with pytest.raises( 132 | ImportError, 133 | match="The astroquery module is required to use the download_gleam " 134 | "function.", 135 | ): 136 | skyutils.download_gleam(path=tmp_path, filename=fname, row_limit=10) 137 | 138 | 139 | def test_jy_to_ksr(): 140 | Nfreqs = 200 141 | freqs = np.linspace(100, 200, Nfreqs) * units.MHz 142 | 143 | def jy2ksr_nonastropy(freq_arr): 144 | c_cmps = 29979245800.0 # cm/s 145 | k_boltz = 1.380658e-16 # erg/K 146 | lam = c_cmps / freq_arr.to_value("Hz") # cm 147 | return 1e-23 * lam**2 / (2 * k_boltz) 148 | 149 | conv0 = skyutils.jy_to_ksr(freqs) 150 | conv1 = jy2ksr_nonastropy(freqs) * units.K * units.sr / units.Jy 151 | 152 | assert np.allclose(conv0, conv1) 153 | 154 | 155 | @pytest.mark.filterwarnings("ignore:Some stokes I values are negative") 156 | @pytest.mark.parametrize( 157 | ("fspec", "use_cli"), [("freqs", True), ("freqs", False), ("redshifts", False)] 158 | ) 159 | def test_flat_spectrum_skymodel(fspec, use_cli, tmp_path): 160 | n_freq = 20 161 | freqs = np.linspace(150e6, 180e6, n_freq) 162 | nside = 256 163 | variance = 1e-6 164 | 165 | redshifts = skyutils.f21 / freqs - 1 166 | z_order = np.argsort(redshifts) 167 | redshifts = redshifts[z_order] 168 | 169 | if use_cli: 170 | # cli only accepts frequencies 171 | file_name = str(tmp_path) + "test_flat_spectrum.skyh5" 172 | output = subprocess.check_output( # nosec 173 | [ 174 | "make_flat_spectrum_eor", 175 | "-v", 176 | str(variance), 177 | "--nside", 178 | str(nside), 179 | "--filename", 180 | file_name, 181 | "-s", 182 | str(freqs[0]), 183 | "-e", 184 | str(freqs[-1]), 185 | "-N", 186 | str(n_freq), 187 | ] 188 | ) 189 | assert output.decode("utf-8").startswith( 190 | "Generating sky model, nside 256, and variance 1e-06 K^2 at channel 0." 191 | ) 192 | assert ( 193 | "Generated flat-spectrum model, with spectral amplitude 0.037 K$^2$ Mpc$^3$" 194 | in output.decode("utf-8") 195 | ) 196 | 197 | sky = SkyModel.from_file(file_name) 198 | else: 199 | fspec_kwargs = {} 200 | if fspec == "freqs": 201 | fspec_kwargs = {"freqs": freqs * units.Hz} 202 | else: 203 | fspec_kwargs = {"redshifts": redshifts, "ref_zbin": n_freq - 1} 204 | 205 | sky = skyutils.flat_spectrum_skymodel( 206 | variance=variance, nside=nside, **fspec_kwargs 207 | ) 208 | 209 | npix = 12 * nside**2 210 | assert sky.Ncomponents == npix 211 | np.testing.assert_allclose(sky.freq_array.value, freqs) 212 | calc_var = np.var(sky.stokes, axis=2) 213 | 214 | dz = redshifts[-1] - redshifts[-2] 215 | omega = 4 * np.pi / npix 216 | vol = ( 217 | Planck15.differential_comoving_volume(redshifts[-1]).to_value("Mpc^3/sr") 218 | * dz 219 | * omega 220 | ) 221 | 222 | pspec_amp = variance * vol 223 | assert f"{pspec_amp:.3f} " + r"K$^2$ Mpc$^3$" in sky.history 224 | 225 | assert np.isclose(calc_var[0, 0].value, variance) 226 | 227 | 228 | @pytest.mark.parametrize( 229 | ("kwargs", "msg"), 230 | [ 231 | ({}, "Either redshifts or freqs must be set."), 232 | ({"freqs": np.linspace(180e6, 150e6, 10)}, "freqs must be in ascending order."), 233 | ( 234 | {"redshifts": skyutils.f21 / np.linspace(150e6, 180e6, 10) - 1}, 235 | "redshifts must be in ascending order.", 236 | ), 237 | ], 238 | ) 239 | def test_flat_spectrum_skymodel_errors(kwargs, msg): 240 | with pytest.raises(ValueError, match=msg): 241 | skyutils.flat_spectrum_skymodel(variance=1e-6, nside=256, **kwargs) 242 | -------------------------------------------------------------------------------- /src/pyradiosky/spherical_coords_transforms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | """Methods for doing spherical coordinate transformations on vectors.""" 4 | 5 | import numpy as np 6 | 7 | 8 | def r_hat(theta, phi): 9 | """ 10 | Get the r hat unit vectors in cartesian coordinates for points on a sphere. 11 | 12 | Parameters 13 | ---------- 14 | theta, phi : float 15 | The co-latitude and azimuth coordinates, respectively, for a point 16 | on the sphere, in radians. Azimuth is defined with respect to the 17 | x axis, co-latitude is the angle with the positive z axis. 18 | 19 | Returns 20 | ------- 21 | array of float 22 | Array of r hat vectors, shape (3, Npoints) 23 | """ 24 | theta = np.array(theta) 25 | phi = np.array(phi) 26 | if theta.shape != phi.shape: 27 | raise ValueError("theta and phi must have the same shape") 28 | rhx = np.cos(phi) * np.sin(theta) 29 | rhy = np.sin(phi) * np.sin(theta) 30 | rhz = np.cos(theta) 31 | return np.stack((rhx, rhy, rhz)) 32 | 33 | 34 | def theta_hat(theta, phi): 35 | """ 36 | Get the theta hat unit vectors in cartesian coordinates for points on a sphere. 37 | 38 | Parameters 39 | ---------- 40 | theta, phi : float 41 | The co-latitude and azimuth coordinates, respectively, for a point 42 | on the sphere, in radians. Azimuth is defined with respect to the 43 | x axis, co-latitude is the angle with the positive z axis. 44 | 45 | Returns 46 | ------- 47 | array of float 48 | Array of theta hat vectors, shape (3, Npoints) 49 | """ 50 | theta = np.array(theta) 51 | phi = np.array(phi) 52 | if theta.shape != phi.shape: 53 | raise ValueError("theta and phi must have the same shape") 54 | thx = np.cos(phi) * np.cos(theta) 55 | thy = np.sin(phi) * np.cos(theta) 56 | thz = -np.sin(theta) 57 | return np.stack((thx, thy, thz)) 58 | 59 | 60 | def phi_hat(theta, phi): 61 | """ 62 | Get the phi hat unit vectors in cartesian coordinates for points on a sphere. 63 | 64 | Parameters 65 | ---------- 66 | theta, phi : float 67 | The co-latitude and azimuth coordinates, respectively, for a point 68 | on the sphere, in radians. Azimuth is defined with respect to the 69 | x axis, co-latitude is the angle with the positive z axis. 70 | 71 | Returns 72 | ------- 73 | array of float 74 | Array of phi hat vectors, shape (3, Npoints) 75 | """ 76 | theta = np.array(theta) 77 | phi = np.array(phi) 78 | if theta.shape != phi.shape: 79 | raise ValueError("theta and phi must have the same shape") 80 | phx = -np.sin(phi) 81 | phy = np.cos(phi) 82 | phz = np.zeros_like(phi) 83 | return np.stack((phx, phy, phz)) 84 | 85 | 86 | def rotate_points_3d(rot_matrix, theta, phi): 87 | """ 88 | Get the spherical coordinates of the point under a 3d rotation. 89 | 90 | Finds the spherical coordinates for point p specified by p = R . q, 91 | where q is the 3D position vector of the point specified by (theta,phi) and 92 | R is the 3D rotation matrix that relates two coordinate charts. 93 | 94 | The accuracy of this method may not be good enough near pols in either 95 | coordinate system. 96 | 97 | Parameters 98 | ---------- 99 | rot_matrix : array-like of float 100 | rotation matrix to use 101 | theta, phi : float 102 | The co-latitude and azimuth coordinates, respectively, for a point 103 | on the sphere, in radians. Azimuth is defined with respect to the 104 | x axis, co-latitude is the angle with the positive z axis. 105 | 106 | Returns 107 | ------- 108 | beta, alpha : float 109 | The theta, phi coordinates for the point on the sphere (using normal 110 | mathematical conventions) in the rotated frame. 111 | """ 112 | # This is NOT written to be vectorized for multiple (theta, phi) 113 | 114 | rot_matrix = np.array(rot_matrix) 115 | if rot_matrix.shape != (3, 3): 116 | raise ValueError("rot_matrix must be a 3x3 array") 117 | 118 | # Replace with function call? 119 | q_hat_1 = np.cos(phi) * np.sin(theta) 120 | q_hat_2 = np.sin(phi) * np.sin(theta) 121 | q_hat_3 = np.cos(theta) 122 | q_hat = np.stack((q_hat_1, q_hat_2, q_hat_3)) 123 | 124 | # Should test for shape of p_hat 125 | p_hat = np.einsum("ab...,b...->a...", rot_matrix, q_hat) 126 | 127 | # Should write a function to do this as well, i.e., pull back angles from 128 | # a vector 129 | if np.isclose(p_hat[2], 1.0, rtol=0.0, atol=1e-12): 130 | p_hat[2] = 1.0 131 | beta = np.arccos(p_hat[2]) 132 | alpha = np.arctan2(p_hat[1], p_hat[0]) 133 | 134 | if alpha < 0: 135 | alpha += 2.0 * np.pi 136 | 137 | return (beta, alpha) 138 | 139 | 140 | def spherical_basis_vector_rotation_matrix( 141 | theta, phi, rot_matrix, beta=None, alpha=None 142 | ): 143 | """ 144 | Get the rotation matrix for vectors in theta/phi basis to a new reference frame. 145 | 146 | Given a position (`theta`, `phi`) in “standard mathematical” coordinates 147 | (0 < `theta` < pi, 0 < `phi` < 2 pi) which will typically be an ICRS RA/Dec 148 | coordinate appropriately converted, and the point to which it is transformed 149 | in another standard mathematical coordinate system (`beta`, `alpha`), which 150 | will typically be local telescope Alt/Az appropriately converted, and a 151 | 3 x 3 rotation matrix `rot_matrix` which connects those two points, 152 | calculate the rotation matrix which rotates the basis vectors associated 153 | with (`theta`, `phi`) to those associated with (`beta`, `alpha`). 154 | 155 | Parameters 156 | ---------- 157 | theta, phi : float 158 | The co-latitude and azimuth coordinates, respectively, for a point 159 | on the sphere, in radians. Azimuth is defined with respect to the 160 | x axis, co-latitude is the angle with the positive z axis. 161 | rot_matrix : array-like of float 162 | Rotation matrix that takes 3-vectors from (theta, phi) to (beta, alpha) 163 | beta, alpha : float, optional 164 | The theta, phi coordinates for the point on the sphere (using normal 165 | mathematical conventions) in the rotated frame. If either is not provided, 166 | they are calculated using `rotate_points_3d`. Note these may 167 | not be as exact as values calculated from astropy. 168 | 169 | Returns 170 | ------- 171 | array of float 172 | 2 x 2 rotation matrix that takes vectors in the theta/phi basis to 173 | the beta/alpha basis. 174 | """ 175 | if alpha is None or beta is None: 176 | beta, alpha = rotate_points_3d(rot_matrix, theta, phi) 177 | 178 | th = theta_hat(theta, phi) 179 | ph = phi_hat(theta, phi) 180 | 181 | bh = np.einsum("ab...,b...->a...", rot_matrix.T, theta_hat(beta, alpha)) 182 | 183 | cosX = np.einsum("a...,a...", bh, th) 184 | sinX = np.einsum("a...,a...", bh, ph) 185 | 186 | return np.array([[cosX, sinX], [-sinX, cosX]]) 187 | 188 | 189 | def axis_angle_rotation_matrix(axis, angle): 190 | """ 191 | Get the rotation matrix using Rodrigues' rotation matrix formula. 192 | 193 | Parameters 194 | ---------- 195 | axis : array-like of float 196 | 3 element unit vector specifying the axis to rotate around. 197 | angle : float 198 | angle to rotate by in radians 199 | 200 | Returns 201 | ------- 202 | array 203 | 3x3 rotation matrix to rotate vectors by `angle` around `axis`. 204 | """ 205 | if axis.shape != (3,): 206 | raise ValueError("axis must be a must be length 3 vector") 207 | if not is_unit_vector(axis): 208 | raise ValueError("axis must be a unit vector") 209 | 210 | K_matrix = np.array( 211 | [[0.0, -axis[2], axis[1]], [axis[2], 0.0, -axis[0]], [-axis[1], axis[0], 0.0]] 212 | ) 213 | 214 | I_matrix = np.identity(3) 215 | 216 | rot_matrix = ( 217 | I_matrix 218 | + np.sin(angle) * K_matrix 219 | + (1.0 - np.cos(angle)) * np.dot(K_matrix, K_matrix) 220 | ) 221 | 222 | return rot_matrix 223 | 224 | 225 | def is_orthogonal(matrix, tol=1e-15): 226 | """ 227 | Test for matrix orthogonality. 228 | 229 | Parameters 230 | ---------- 231 | matrix : array-like of float 232 | square matrix to test 233 | 234 | Returns 235 | ------- 236 | bool 237 | True if `matrix` is orthogonal, False otherwise. 238 | """ 239 | return np.allclose(np.matmul(matrix, matrix.T), np.eye(3), atol=tol) 240 | 241 | 242 | def is_unit_vector(vec, tol=1e-15): 243 | """ 244 | Test for unit vectors. 245 | 246 | Parameters 247 | ---------- 248 | vec : array-like of float 249 | vector to test 250 | 251 | Returns 252 | ------- 253 | bool 254 | True if `vec` is a unit vector, False otherwise. 255 | """ 256 | return np.allclose(np.dot(vec, vec), 1, atol=tol) 257 | 258 | 259 | def vecs2rot(r1=None, r2=None, theta1=None, phi1=None, theta2=None, phi2=None): 260 | """ 261 | Get the rotation matrix that connects two points or unit vectors on the sphere. 262 | 263 | Parameters 264 | ---------- 265 | r1, r2 : array-like of float, optional 266 | length 3 unit vectors 267 | theta1, phi1, theta2, phi2 : float, optional 268 | The co-latitude and azimuth coordinates, respectively, for a point 269 | on the sphere, in radians. Azimuth is defined with respect to the 270 | x axis, co-latitude is the angle with the positive z axis. 271 | Ignored if r1 and r2 are supplied. 272 | 273 | Returns 274 | ------- 275 | array 276 | 3x3 rotation matrix that rotates the first point or vector into the other. 277 | """ 278 | if r1 is None or r2 is None: 279 | if theta1 is None or phi1 is None or theta2 is None or phi2 is None: 280 | raise ValueError( 281 | "Either r1 and r2 must be supplied or all of " 282 | "theta1, phi1, theta2 and phi2 must be supplied." 283 | ) 284 | r1 = r_hat(theta1, phi1) 285 | r2 = r_hat(theta2, phi2) 286 | 287 | assert is_unit_vector(r1), "r1 is not a unit vector: " + str(r1) 288 | assert is_unit_vector(r2), "r2 is not a unit vector: " + str(r2) 289 | else: 290 | r1 = np.array(r1) 291 | r2 = np.array(r2) 292 | if r1.shape != (3,) or r2.shape != (3,): 293 | raise ValueError("r1 and r2 must be length 3 vectors") 294 | if not is_unit_vector(r1) or not is_unit_vector(r2): 295 | raise ValueError("r1 and r2 must be unit vectors") 296 | 297 | norm = np.cross(r1, r2) 298 | # Note that Psi is between 0 and pi 299 | sinPsi = np.sqrt(np.dot(norm, norm)) 300 | n_hat = norm / sinPsi # Trouble lurks if Psi = 0. 301 | cosPsi = np.dot(r1, r2) 302 | Psi = np.arctan2(sinPsi, cosPsi) 303 | rotation = axis_angle_rotation_matrix(n_hat, Psi) 304 | 305 | assert is_unit_vector(n_hat), "n_hat is not a unit vector: " + str(n_hat) 306 | assert is_orthogonal(rotation), "rotation matrix is not orthogonal: " + str( 307 | rotation 308 | ) 309 | 310 | return rotation 311 | -------------------------------------------------------------------------------- /src/pyradiosky/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Radio Astronomy Software Group 2 | # Licensed under the 2-clause BSD License 3 | """Utility methods.""" 4 | 5 | import os 6 | 7 | import astropy.units as units 8 | import numpy as np 9 | from astropy.cosmology import Planck15 10 | from astropy.units import Quantity 11 | 12 | from pyradiosky.data import DATA_PATH as SKY_DATA_PATH 13 | 14 | f21 = 1.420405751e9 15 | 16 | 17 | def stokes_to_coherency(stokes_arr): 18 | """ 19 | Convert Stokes array to coherency matrix. 20 | 21 | Parameters 22 | ---------- 23 | stokes_arr : Quantity 24 | Array of stokes parameters in order [I, Q, U, V], shape(4,) or 25 | (4, Nfreqs, Ncomponents). 26 | 27 | Returns 28 | ------- 29 | coherency matrix : array of float 30 | Array of coherencies, shape (2, 2) or (2, 2, Nfreqs, Ncomponents) 31 | """ 32 | if not isinstance(stokes_arr, Quantity): 33 | raise ValueError("stokes_arr must be an astropy Quantity.") 34 | 35 | initial_shape = stokes_arr.shape 36 | if initial_shape[0] != 4: 37 | raise ValueError("First dimension of stokes_vector must be length 4.") 38 | 39 | if stokes_arr.size == 4 and len(initial_shape) == 1: 40 | stokes_arr = stokes_arr[:, np.newaxis, np.newaxis] 41 | 42 | coherency = ( 43 | 0.5 44 | * np.array( 45 | [ 46 | [ 47 | stokes_arr[0, :, :] + stokes_arr[1, :, :], 48 | stokes_arr[2, :, :] - 1j * stokes_arr[3, :, :], 49 | ], 50 | [ 51 | stokes_arr[2, :, :] + 1j * stokes_arr[3, :, :], 52 | stokes_arr[0, :, :] - stokes_arr[1, :, :], 53 | ], 54 | ] 55 | ) 56 | * stokes_arr.unit 57 | ) 58 | 59 | if stokes_arr.size == 4 and len(initial_shape) == 1: 60 | coherency = coherency.squeeze() 61 | return coherency 62 | 63 | 64 | def coherency_to_stokes(coherency_matrix): 65 | """ 66 | Convert coherency matrix to vector of 4 Stokes parameter in order [I, Q, U, V]. 67 | 68 | Parameters 69 | ---------- 70 | coherency matrix : Quantity 71 | Array of coherencies, shape (2, 2) or (2, 2, Ncomponents) 72 | 73 | Returns 74 | ------- 75 | stokes_arr : array of float 76 | Array of stokes parameters, shape(4,) or (4, Ncomponents) 77 | """ 78 | if not isinstance(coherency_matrix, Quantity): 79 | raise ValueError("coherency_matrix must be an astropy Quantity.") 80 | 81 | initial_shape = coherency_matrix.shape 82 | if len(initial_shape) < 2 or initial_shape[0] != 2 or initial_shape[1] != 2: 83 | raise ValueError("First two dimensions of coherency_matrix must be length 2.") 84 | 85 | if coherency_matrix.size == 4 and len(initial_shape) == 2: 86 | coherency_matrix = coherency_matrix[:, :, np.newaxis] 87 | 88 | stokes = ( 89 | np.array( 90 | [ 91 | coherency_matrix[0, 0, :] + coherency_matrix[1, 1, :], 92 | coherency_matrix[0, 0, :] - coherency_matrix[1, 1, :], 93 | coherency_matrix[0, 1, :] + coherency_matrix[1, 0, :], 94 | -(coherency_matrix[0, 1, :] - coherency_matrix[1, 0, :]).imag, 95 | ] 96 | ).real 97 | * coherency_matrix.unit 98 | ) 99 | if coherency_matrix.size == 4 and len(initial_shape) == 2: 100 | stokes = stokes.squeeze() 101 | 102 | return stokes 103 | 104 | 105 | def jy_to_ksr(freqs): 106 | """ 107 | Calculate multiplicative factors to convert [Jy] to [K sr]. 108 | 109 | Parameters 110 | ---------- 111 | freqs : :class:`astropy.Quantity` or array_like of float (Deprecated) 112 | Frequencies, assumed to be in Hz if not a Quantity. 113 | 114 | Returns 115 | ------- 116 | Quantity 117 | Conversion factor(s) to go from [Jy] to [K sr]. Shape equal to shape of freqs. 118 | """ 119 | freqs = np.atleast_1d(freqs) 120 | if not isinstance(freqs, Quantity): 121 | freqs = freqs * units.Hz 122 | 123 | equiv = units.brightness_temperature(freqs, beam_area=1 * units.sr) 124 | conv_factor = (1 * units.Jy).to(units.K, equivalencies=equiv) * units.sr / units.Jy 125 | 126 | return conv_factor 127 | 128 | 129 | def download_gleam( 130 | path=".", filename="gleam.vot", overwrite=False, row_limit=None, for_testing=False 131 | ): 132 | """ 133 | Download the GLEAM vot table from Vizier. 134 | 135 | Parameters 136 | ---------- 137 | path : str 138 | Folder location to save catalog to. 139 | filename : str 140 | Filename to save catalog to. 141 | overwrite : bool 142 | Option to download the file even if it already exists. 143 | row_limit : int, optional 144 | Max number of rows (sources) to download, default is None meaning download 145 | all rows. 146 | for_testing : bool 147 | Download a file to use for unit tests. If True, some additional columns are 148 | included, the rows are limited to 50, the path and filename are set to put 149 | the file in the correct location and the overwrite keyword is set to True. 150 | 151 | """ 152 | try: 153 | from astroquery.vizier import Vizier 154 | except ImportError as e: 155 | raise ImportError( 156 | "The astroquery module is required to use the download_gleam function." 157 | ) from e 158 | 159 | if for_testing: # pragma: no cover 160 | path = SKY_DATA_PATH 161 | filename = "gleam_50srcs.vot" 162 | overwrite = True 163 | row_limit = 50 164 | # We have frequent CI failures with messages like 165 | # "Failed to resolve 'vizier.u-strasbg.fr'" 166 | # Try using a US mirror to see if we have fewer errors 167 | Vizier.VIZIER_SERVER = "vizier.cfa.harvard.edu" 168 | 169 | opath = os.path.join(path, filename) 170 | if os.path.exists(opath) and not overwrite: 171 | print( 172 | f"GLEAM already downloaded to {opath}. Set overwrite=True to " 173 | "re-download it." 174 | ) 175 | return 176 | 177 | # full download is too slow for unit tests 178 | if row_limit is None: # pragma: no cover 179 | Vizier.ROW_LIMIT = -1 180 | else: 181 | Vizier.ROW_LIMIT = row_limit 182 | desired_columns = [ 183 | "GLEAM", 184 | "RAJ2000", 185 | "DEJ2000", 186 | "Fintwide", 187 | "e_Fintwide", 188 | "alpha", 189 | "e_alpha", 190 | "chi2", 191 | "Fintfit200", 192 | "e_Fintfit200", 193 | "Fint076", 194 | "Fint084", 195 | "Fint092", 196 | "Fint099", 197 | "Fint107", 198 | "Fint115", 199 | "Fint122", 200 | "Fint130", 201 | "Fint143", 202 | "Fint151", 203 | "Fint158", 204 | "Fint166", 205 | "Fint174", 206 | "Fint181", 207 | "Fint189", 208 | "Fint197", 209 | "Fint204", 210 | "Fint212", 211 | "Fint220", 212 | "Fint227", 213 | "e_Fint076", 214 | "e_Fint084", 215 | "e_Fint092", 216 | "e_Fint099", 217 | "e_Fint107", 218 | "e_Fint115", 219 | "e_Fint122", 220 | "e_Fint130", 221 | "e_Fint143", 222 | "e_Fint151", 223 | "e_Fint158", 224 | "e_Fint166", 225 | "e_Fint174", 226 | "e_Fint181", 227 | "e_Fint189", 228 | "e_Fint197", 229 | "e_Fint204", 230 | "e_Fint212", 231 | "e_Fint220", 232 | "e_Fint227", 233 | ] 234 | if for_testing: # pragma: no cover 235 | desired_columns.extend(["Fpwide", "e_Fp076"]) 236 | 237 | Vizier.columns = desired_columns 238 | catname = "VIII/100/gleamegc" 239 | table = Vizier.get_catalogs(catname)[0] 240 | 241 | for col in desired_columns: 242 | assert col in table.colnames, f"column {col} not in downloaded table." 243 | 244 | table.write(opath, format="votable", overwrite=overwrite) 245 | 246 | print("GLEAM catalog downloaded and saved to " + opath) 247 | 248 | 249 | def flat_spectrum_skymodel( 250 | *, variance, nside, ref_chan=0, ref_zbin=0, redshifts=None, freqs=None, frame="icrs" 251 | ): 252 | """ 253 | Generate a full-frequency SkyModel of a flat-spectrum (noiselike) EoR signal. 254 | 255 | The amplitude of this signal is variance * vol(ref_chan), where vol() gives 256 | the voxel volume and ref_chan is a chosen reference point. The generated 257 | SkyModel has healpix component type. 258 | 259 | Parameters 260 | ---------- 261 | variance: float 262 | Variance of the signal, in Kelvin^2, at the reference channel. 263 | nside: int 264 | HEALPix NSIDE parameter. Must be a power of 2. 265 | ref_chan: int 266 | Frequency channel to set as reference, if using freqs. 267 | ref_zbin: int 268 | Redshift bin number to use as reference, if using redshifts. 269 | redshifts: numpy.ndarray 270 | Redshifts at which to generate maps. Ignored if freqs is provided. 271 | freqs: numpy.ndarray 272 | Frequencies in Hz, not required if redshifts is passed. Overrides 273 | redshifts if passed. 274 | 275 | Returns 276 | ------- 277 | pyradiosky.SkyModel 278 | A SkyModel instance corresponding a white noise-like EoR signal. 279 | 280 | Notes 281 | ----- 282 | Either redshifts or freqs must be provided. 283 | The history string of the returned SkyModel gives the expected amplitude. 284 | """ 285 | # import here to avoid circular imports 286 | from pyradiosky import SkyModel 287 | 288 | if freqs is not None: 289 | if not isinstance(freqs, units.Quantity): 290 | freqs *= units.Hz 291 | 292 | if np.any(np.diff(freqs.value) < 0): 293 | raise ValueError("freqs must be in ascending order.") 294 | redshifts = f21 / freqs.to("Hz").value - 1 295 | ref_z = redshifts[ref_chan] 296 | # must sort so that redshifts go in ascending order (opposite freq order) 297 | z_order = np.argsort(redshifts) 298 | redshifts = redshifts[z_order] 299 | freqs = freqs[z_order] 300 | ref_zbin = np.where(np.isclose(redshifts, ref_z))[0][0] 301 | elif redshifts is not None: 302 | if np.any(np.diff(redshifts) < 0): 303 | raise ValueError("redshifts must be in ascending order.") 304 | freqs = (f21 / (redshifts + 1)) * units.Hz 305 | else: 306 | raise ValueError("Either redshifts or freqs must be set.") 307 | 308 | npix = 12 * nside**2 309 | nfreqs = freqs.size 310 | omega = 4 * np.pi / npix 311 | 312 | # Make some noise. 313 | stokes = np.zeros((4, npix, nfreqs)) 314 | stokes[0, :, :] = np.random.normal(0.0, np.sqrt(variance), (npix, nfreqs)) 315 | 316 | voxvols = np.zeros(nfreqs) 317 | for zi in range(nfreqs - 1): 318 | dz = redshifts[zi + 1] - redshifts[zi] 319 | 320 | vol = ( 321 | Planck15.differential_comoving_volume(redshifts[zi]).to_value("Mpc^3/sr") 322 | * dz 323 | * omega 324 | ) 325 | voxvols[zi] = vol 326 | voxvols[nfreqs - 1] = ( 327 | vol # Assume the last redshift bin is the same width as the next-to-last. 328 | ) 329 | 330 | scale = np.sqrt(voxvols / voxvols[ref_zbin]) 331 | stokes /= scale 332 | stokes = np.swapaxes(stokes, 1, 2) # Put npix in last place again. 333 | 334 | # sort back to freq order 335 | f_order = np.argsort(freqs) 336 | freqs = freqs[f_order] 337 | stokes = stokes[:, f_order] 338 | 339 | # The true power spectrum amplitude is variance * reference voxel volume. 340 | pspec_amp = variance * voxvols[ref_zbin] 341 | history_string = ( 342 | f"Generated flat-spectrum model, with spectral amplitude {pspec_amp:.3f} " 343 | ) 344 | history_string += r"K$^2$ Mpc$^3$" 345 | 346 | return SkyModel( 347 | freq_array=freqs, 348 | hpx_inds=np.arange(npix), 349 | spectral_type="full", 350 | nside=nside, 351 | stokes=stokes * units.K, 352 | history=history_string, 353 | frame=frame, 354 | ) 355 | -------------------------------------------------------------------------------- /paper.bib: -------------------------------------------------------------------------------- 1 | @article{pyuvdata2017, 2 | doi = {10.21105/joss.00140}, 3 | url = {https://doi.org/10.21105/joss.00140}, 4 | year = {2017}, 5 | publisher = {The Open Journal}, 6 | volume = {2}, 7 | number = {10}, 8 | pages = {140}, 9 | author = {Bryna J. Hazelton and Daniel C. Jacobs and Jonathan C. Pober and Adam P. Beardsley}, 10 | title = {pyuvdata: an interface for astronomical interferometeric datasets in python}, 11 | journal = {Journal of Open Source Software} 12 | } 13 | 14 | @article{pyuvsim2019, 15 | doi = {10.21105/joss.01234}, 16 | url = {https://doi.org/10.21105/joss.01234}, 17 | year = {2019}, 18 | publisher = {The Open Journal}, 19 | volume = {4}, 20 | number = {37}, 21 | pages = {1234}, 22 | author = {Adam E. Lanman and Bryna J. Hazelton and Daniel C. Jacobs and 23 | Matthew J. Kolopanis and Jonathan C. Pober and James E. Aguirre 24 | and Nithyanandan Thyagarajan}, 25 | title = {pyuvsim: A comprehensive simulation package for radio interferometers in python}, 26 | journal = {Journal of Open Source Software} 27 | } 28 | 29 | 30 | @article{astropy:2013, 31 | Adsnote = {Provided by the SAO/NASA Astrophysics Data System}, 32 | Adsurl = {http://adsabs.harvard.edu/abs/2013A%26A...558A..33A}, 33 | Archiveprefix = {arXiv}, 34 | Author = {{Astropy Collaboration} and {Robitaille}, T.~P. and {Tollerud}, E.~J. 35 | and {Greenfield}, P. and {Droettboom}, M. and {Bray}, E. and {Aldcroft}, T. 36 | and {Davis}, M. and {Ginsburg}, A. and {Price-Whelan}, A.~M. and {Kerzendorf}, W.~E. 37 | and {Conley}, A. and {Crighton}, N. and {Barbary}, K. and {Muna}, D. 38 | and {Ferguson}, H. and {Grollier}, F. and {Parikh}, M.~M. and {Nair}, P.~H. 39 | and {Unther}, H.~M. and {Deil}, C. and {Woillez}, J. and {Conseil}, S. 40 | and {Kramer}, R. and {Turner}, J.~E.~H. and {Singer}, L. and {Fox}, R. 41 | and {Weaver}, B.~A. and {Zabalza}, V. and {Edwards}, Z.~I. 42 | and {Azalee Bostroem}, K. and {Burke}, D.~J. and {Casey}, A.~R. 43 | and {Crawford}, S.~M. and {Dencheva}, N. and {Ely}, J. and {Jenness}, T. 44 | and {Labrie}, K. and {Lim}, P.~L. and {Pierfederici}, F. and {Pontzen}, A. 45 | and {Ptak}, A. and {Refsdal}, B. and {Servillat}, M. and {Streicher}, O. 46 | }, 47 | Doi = {10.1051/0004-6361/201322068}, 48 | Eid = {A33}, 49 | Eprint = {1307.6212}, 50 | Journal = {Astronomy and Astrophysics}, 51 | Keywords = {methods: data analysis, methods: miscellaneous, virtual observatory tools}, 52 | Month = oct, 53 | Pages = {A33}, 54 | Primaryclass = {astro-ph.IM}, 55 | Title = {{Astropy: A community Python package for astronomy}}, 56 | Volume = 558, 57 | Year = 2013, 58 | Bdsk-Url-1 = {https://dx.doi.org/10.1051/0004-6361/201322068} 59 | } 60 | 61 | @article{astropy:2018, 62 | author = {{Astropy Collaboration} and {Price-Whelan}, A.~M. and 63 | {Sip{\H{o}}cz}, B.~M. and {G{\"u}nther}, H.~M. and {Lim}, P.~L. and 64 | {Crawford}, S.~M. and {Conseil}, S. and {Shupe}, D.~L. and 65 | {Craig}, M.~W. and {Dencheva}, N. and {Ginsburg}, A. and {VanderPlas}, J.~T. 66 | and {Bradley}, L.~D. and {P{\'e}rez-Su{\'a}rez}, D. and 67 | {de Val-Borro}, M. and {Aldcroft}, T.~L. and {Cruz}, K.~L. and 68 | {Robitaille}, T.~P. and {Tollerud}, E.~J. and {Ardelean}, C. and 69 | {Babej}, T. and {Bach}, Y.~P. and {Bachetti}, M. and {Bakanov}, A.~V. and 70 | {Bamford}, S.~P. and {Barentsen}, G. and {Barmby}, P. and 71 | {Baumbach}, A. and {Berry}, K.~L. and {Biscani}, F. and {Boquien}, M. and 72 | {Bostroem}, K.~A. and {Bouma}, L.~G. and {Brammer}, G.~B. and 73 | {Bray}, E.~M. and {Breytenbach}, H. and {Buddelmeijer}, H. and 74 | {Burke}, D.~J. and {Calderone}, G. and {Cano Rodr{\'\i}guez}, J.~L. and 75 | {Cara}, M. and {Cardoso}, J.~V.~M. and {Cheedella}, S. and {Copin}, Y. and 76 | {Corrales}, L. and {Crichton}, D. and {D'Avella}, D. and {Deil}, C. and 77 | {Depagne}, {\'E}. and {Dietrich}, J.~P. and {Donath}, A. and 78 | {Droettboom}, M. and {Earl}, N. and {Erben}, T. and {Fabbro}, S. and 79 | {Ferreira}, L.~A. and {Finethy}, T. and {Fox}, R.~T. and 80 | {Garrison}, L.~H. and {Gibbons}, S.~L.~J. and {Goldstein}, D.~A. and 81 | {Gommers}, R. and {Greco}, J.~P. and {Greenfield}, P. and 82 | {Groener}, A.~M. and {Grollier}, F. and {Hagen}, A. and {Hirst}, P. and 83 | {Homeier}, D. and {Horton}, A.~J. and {Hosseinzadeh}, G. and {Hu}, L. and 84 | {Hunkeler}, J.~S. and {Ivezi{\'c}}, {\v{Z}}. and {Jain}, A. and 85 | {Jenness}, T. and {Kanarek}, G. and {Kendrew}, S. and {Kern}, N.~S. and 86 | {Kerzendorf}, W.~E. and {Khvalko}, A. and {King}, J. and {Kirkby}, D. and 87 | {Kulkarni}, A.~M. and {Kumar}, A. and {Lee}, A. and {Lenz}, D. and 88 | {Littlefair}, S.~P. and {Ma}, Z. and {Macleod}, D.~M. and 89 | {Mastropietro}, M. and {McCully}, C. and {Montagnac}, S. and 90 | {Morris}, B.~M. and {Mueller}, M. and {Mumford}, S.~J. and {Muna}, D. and 91 | {Murphy}, N.~A. and {Nelson}, S. and {Nguyen}, G.~H. and 92 | {Ninan}, J.~P. and {N{\"o}the}, M. and {Ogaz}, S. and {Oh}, S. and 93 | {Parejko}, J.~K. and {Parley}, N. and {Pascual}, S. and {Patil}, R. and 94 | {Patil}, A.~A. and {Plunkett}, A.~L. and {Prochaska}, J.~X. and 95 | {Rastogi}, T. and {Reddy Janga}, V. and {Sabater}, J. and 96 | {Sakurikar}, P. and {Seifert}, M. and {Sherbert}, L.~E. and 97 | {Sherwood-Taylor}, H. and {Shih}, A.~Y. and {Sick}, J. and 98 | {Silbiger}, M.~T. and {Singanamalla}, S. and {Singer}, L.~P. and 99 | {Sladen}, P.~H. and {Sooley}, K.~A. and {Sornarajah}, S. and 100 | {Streicher}, O. and {Teuben}, P. and {Thomas}, S.~W. and 101 | {Tremblay}, G.~R. and {Turner}, J.~E.~H. and {Terr{\'o}n}, V. and 102 | {van Kerkwijk}, M.~H. and {de la Vega}, A. and {Watkins}, L.~L. and 103 | {Weaver}, B.~A. and {Whitmore}, J.~B. and {Woillez}, J. and 104 | {Zabalza}, V. and {Astropy Contributors} 105 | }, 106 | title = "{The Astropy Project: Building an Open-science Project and Status of the v2.0 Core Package}", 107 | journal = {Astronomical Journal}, 108 | keywords = {methods: data analysis, methods: miscellaneous, methods: statistical, reference systems, Astrophysics - Instrumentation and Methods for Astrophysics}, 109 | year = 2018, 110 | month = sep, 111 | volume = {156}, 112 | number = {3}, 113 | eid = {123}, 114 | pages = {123}, 115 | doi = {10.3847/1538-3881/aabc4f}, 116 | archivePrefix = {arXiv}, 117 | eprint = {1801.02634}, 118 | primaryClass = {astro-ph.IM}, 119 | adsurl = {https://ui.adsabs.harvard.edu/abs/2018AJ....156..123A}, 120 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 121 | } 122 | 123 | @article{astropy:2022, 124 | author = {{Astropy Collaboration} and {Price-Whelan}, Adrian M. and 125 | {Lim}, Pey Lian and {Earl}, Nicholas and {Starkman}, Nathaniel and 126 | {Bradley}, Larry and {Shupe}, David L. and {Patil}, Aarya A. and 127 | {Corrales}, Lia and {Brasseur}, C.~E. and {N{"o}the}, Maximilian and 128 | {Donath}, Axel and {Tollerud}, Erik and {Morris}, Brett M. and 129 | {Ginsburg}, Adam and {Vaher}, Eero and {Weaver}, Benjamin A. and 130 | {Tocknell}, James and {Jamieson}, William and {van Kerkwijk}, Marten H. 131 | and {Robitaille}, Thomas P. and {Merry}, Bruce and {Bachetti}, Matteo and 132 | {G{"u}nther}, H. Moritz and {Aldcroft}, Thomas L. and {Alvarado-Montes}, Jaime A. 133 | and {Archibald}, Anne M. and {B{'o}di}, Attila and {Bapat}, Shreyas and 134 | {Barentsen}, Geert and {Baz{'a}n}, Juanjo and {Biswas}, Manish and 135 | {Boquien}, M{'e}d{'e}ric and {Burke}, D.~J. and {Cara}, Daria and 136 | {Cara}, Mihai and {Conroy}, Kyle E. and {Conseil}, Simon and {Craig}, Matthew W. 137 | and {Cross}, Robert M. and {Cruz}, Kelle L. and {D'Eugenio}, Francesco 138 | and {Dencheva}, Nadia and {Devillepoix}, Hadrien A.~R. and {Dietrich}, J{"o}rg P. 139 | and {Eigenbrot}, Arthur Davis and {Erben}, Thomas and {Ferreira}, Leonardo 140 | and {Foreman-Mackey}, Daniel and {Fox}, Ryan and {Freij}, Nabil and 141 | {Garg}, Suyog and {Geda}, Robel and {Glattly}, Lauren and {Gondhalekar}, Yash 142 | and {Gordon}, Karl D. and {Grant}, David and {Greenfield}, Perry and 143 | {Groener}, Austen M. and {Guest}, Steve and {Gurovich}, Sebastian and 144 | {Handberg}, Rasmus and {Hart}, Akeem and {Hatfield-Dodds}, Zac and 145 | {Homeier}, Derek and {Hosseinzadeh}, Griffin and {Jenness}, Tim and 146 | {Jones}, Craig K. and {Joseph}, Prajwel and {Kalmbach}, J. Bryce and 147 | {Karamehmetoglu}, Emir and {Ka{l}uszy{'n}ski}, Miko{l}aj and 148 | {Kelley}, Michael S.~P. and {Kern}, Nicholas and {Kerzendorf}, Wolfgang E. 149 | and {Koch}, Eric W. and {Kulumani}, Shankar and {Lee}, Antony and {Ly}, Chun 150 | and {Ma}, Zhiyuan and {MacBride}, Conor and {Maljaars}, Jakob M. and 151 | {Muna}, Demitri and {Murphy}, N.~A. and {Norman}, Henrik and {O'Steen}, Richard 152 | and {Oman}, Kyle A. and {Pacifici}, Camilla and {Pascual}, Sergio and 153 | {Pascual-Granado}, J. and {Patil}, Rohit R. and {Perren}, Gabriel I. and 154 | {Pickering}, Timothy E. and {Rastogi}, Tanuj and {Roulston}, Benjamin R. 155 | and {Ryan}, Daniel F. and {Rykoff}, Eli S. and {Sabater}, Jose and 156 | {Sakurikar}, Parikshit and {Salgado}, Jes{'u}s and {Sanghi}, Aniket and 157 | {Saunders}, Nicholas and {Savchenko}, Volodymyr and {Schwardt}, Ludwig and 158 | {Seifert-Eckert}, Michael and {Shih}, Albert Y. and {Jain}, Anany Shrey 159 | and {Shukla}, Gyanendra and {Sick}, Jonathan and {Simpson}, Chris and 160 | {Singanamalla}, Sudheesh and {Singer}, Leo P. and {Singhal}, Jaladh and 161 | {Sinha}, Manodeep and {Sip{H{o}}cz}, Brigitta M. and {Spitler}, Lee R. 162 | and {Stansby}, David and {Streicher}, Ole and {{ {S}}umak}, Jani and 163 | {Swinbank}, John D. and {Taranu}, Dan S. and {Tewary}, Nikita and 164 | {Tremblay}, Grant R. and {Val-Borro}, Miguel de and {Van Kooten}, Samuel J. 165 | and {Vasovi{'c}}, Zlatan and {Verma}, Shresth and {de Miranda Cardoso}, Jos{'e} Vin{'i}cius 166 | and {Williams}, Peter K.~G. and {Wilson}, Tom J. and {Winkel}, Benjamin 167 | and {Wood-Vasey}, W.~M. and {Xue}, Rui and {Yoachim}, Peter and {Zhang}, Chen 168 | and {Zonca}, Andrea and {Astropy Project Contributors} 169 | }, 170 | title = "{The Astropy Project: Sustaining and Growing a Community-oriented Open-source Project and the Latest Major Release (v5.0) of the Core Package}", 171 | journal = {Astrophysical Journal}, 172 | keywords = {Astronomy software, Open source software, Astronomy data analysis, 1855, 1866, 1858, Astrophysics - Instrumentation and Methods for Astrophysics}, 173 | year = 2022, 174 | month = aug, 175 | volume = {935}, 176 | number = {2}, 177 | eid = {167}, 178 | pages = {167}, 179 | doi = {10.3847/1538-4357/ac7c74}, 180 | archivePrefix = {arXiv}, 181 | eprint = {2206.14220}, 182 | primaryClass = {astro-ph.IM}, 183 | adsurl = {https://ui.adsabs.harvard.edu/abs/2022ApJ...935..167A}, 184 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 185 | } 186 | 187 | 188 | @misc{lunarsky, 189 | author = {Adam E. Lanman}, 190 | title = {lunarsky: An astropy extension to describe observations from the surface of the Moon.}, 191 | year = {2020}, 192 | publisher = {GitHub}, 193 | journal = {GitHub repository}, 194 | url = {https://github.com/aelanman/lunarsky} 195 | } 196 | 197 | 198 | @inproceedings{topcat, 199 | author = {{Taylor}, M.~B.}, 200 | title = "{TOPCAT \& STIL: Starlink Table/VOTable Processing Software}", 201 | booktitle = {Astronomical Data Analysis Software and Systems XIV}, 202 | year = 2005, 203 | editor = {{Shopbell}, P. and {Britton}, M. and {Ebert}, R.}, 204 | series = {Astronomical Society of the Pacific Conference Series}, 205 | volume = {347}, 206 | month = dec, 207 | pages = {29}, 208 | adsurl = {https://ui.adsabs.harvard.edu/abs/2005ASPC..347...29T}, 209 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 210 | } 211 | 212 | 213 | @ARTICLE{healpix, 214 | author = {{G{\'o}rski}, K.~M. and {Hivon}, E. and {Banday}, A.~J. and {Wandelt}, B.~D. and {Hansen}, F.~K. and {Reinecke}, M. and {Bartelmann}, M.}, 215 | title = "{HEALPix: A Framework for High-Resolution Discretization and Fast Analysis of Data Distributed on the Sphere}", 216 | journal = {The Astrophysical Journal}, 217 | keywords = {Cosmology: Cosmic Microwave Background, Cosmology: Observations, Methods: Statistical, Astrophysics}, 218 | year = 2005, 219 | month = apr, 220 | volume = {622}, 221 | number = {2}, 222 | pages = {759-771}, 223 | doi = {10.1086/427976}, 224 | archivePrefix = {arXiv}, 225 | eprint = {astro-ph/0409513}, 226 | primaryClass = {astro-ph}, 227 | adsurl = {https://ui.adsabs.harvard.edu/abs/2005ApJ...622..759G}, 228 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 229 | } 230 | 231 | @misc{astropy-healpix, 232 | author = {{Astropy Developers}}, 233 | title = {astropy healpix: BSD-licensed HEALPix for Astropy}, 234 | year = {2016}, 235 | publisher = {GitHub}, 236 | journal = {GitHub repository}, 237 | url = {https://github.com/astropy/astropy-healpix} 238 | } 239 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ### Changed 6 | - Updated minimum dependency versions: pyuvdata>=3.2.3 7 | - Use `pyuvdata.UVBase._select_along_param_axis` rather than custom logic in 8 | `SkyModel.select`. 9 | 10 | ### Fixed 11 | - Entry point scripts are now properly installed. 12 | 13 | ## [1.1.0] - 2025-06-26 14 | 15 | ### Added 16 | - New option to `SkyModel.select` to select components that do not have NaN 17 | values in the `stokes` parameter at either any or all frequencies. 18 | - New option to `SkyModel.select` to select components with no negative Stokes I 19 | values. 20 | - New check warnings about NaN stokes and spectral index values and negative 21 | Stokes I values. 22 | - An option to skip optional parameters when reading in skyh5 files. 23 | 24 | ### Changed 25 | - Updated minimum dependency versions: h5py>=3.7, python>=3.11, scipy>=1.9 26 | - Converted scripts to entry points to be better aligned with the pyproject.toml 27 | approach. 28 | - Only import lunarsky if needed. 29 | - Use the new pyuvdata analytic short dipole beam for polarized source testing. 30 | - Updated minimum dependency versions: pyuvdata>=3.1.0 31 | - Updated minimum optional dependency versions: lunarsky>=0.2.5 32 | 33 | ### Fixed 34 | - A bug where running check on read was impossible to turn off. 35 | 36 | ## [1.0.1] - 2024-07-01 37 | 38 | ### Changed 39 | - Updated minimum dependency versions: setuptools_scm>=8.1, scipy>=1.8 40 | - pyuvdata utils imports to ensure compatibility with version 3.0. 41 | - `numpy.string_` calls to `numpy.bytes_`, `np.in1d` to `np.isin` and a few other 42 | changes for numpy 2.0 compatibility. 43 | 44 | ### Fixed 45 | - An error when Skymodel.concat was called serially. 46 | 47 | ## [1.0.0] - 2024-05-09 48 | 49 | ### Changed 50 | - Updated minimum dependency versions: astropy>=6.0, h5py>=3.4, numpy>=1.23, 51 | pyuvdata>=2.4.3, scipy>=1.7.3, python>=3.10 52 | - Updated minimum optional dependency versions: astropy-healpix>=1.0.2, lunarsky>=0.2.2 53 | 54 | ## [0.3.1] - 2024-02-22 55 | 56 | ### Added 57 | - A memo describing the SkyH5 format in docs/references. 58 | 59 | ### Changed 60 | - Added support for more types of FHD catalog files and better error messages. 61 | - Added support (and testing) for python 3.12, dropped support for python 3.8 62 | - Moved the optional data-sized arrays (stokes_error and beam_amp) from the Header 63 | datagroup to the Data datagroup in SkyH5 files. 64 | - Added an `extra_columns` attribute to SkyModel, a recarray to store other catalog 65 | information with a value per component. 66 | - Updated the pyuvdata requirement to >= 2.4.1 67 | - Updated the scipy requirement to >= 1.5 68 | 69 | ### Fixed 70 | - A bug where FHD frequencies were interpreted as being in Hz rather than MHz. 71 | 72 | ## [0.3.0] - 2023-04-10 73 | 74 | ### Added 75 | - A `freq_edge_array` parameter to store the top and bottom of the sub-bands with shape 76 | (2, Nfreqs). This will become required for objects with "subband" spectral types in 77 | version 0.5. A `freq_edge_array` parameter was also added to the `read_votable_file`, 78 | `read` and `from_file` methods. 79 | 80 | ### Changed 81 | - When writing to text files, "subband" spectral types are represented in the same 82 | way as "full" spectral types (losing the frequency edge array information). 83 | - Increase the minimum compatible version for lunarsky to 0.2.1 to fix astropy 84 | deprecation warnings. 85 | 86 | ### Deprecated 87 | - Not setting the `freq_edge_array` parameter for subband spectral types. 88 | 89 | ### Removed 90 | - Defaulting for the coordinate frame, `frame` is now required to be passed to 91 | the `__init__` method for Healpix maps and point-like components when passing individual 92 | components rather than a SkyCoord object. It is also required to be passed to the `read_votable_file` method. 93 | - Handling for accessing the deprecated `ra_dec_coherency`, `lon`, `lat` attributes. 94 | - Handling for accessing galactic `l` and `b` coordinates as `gl` and `gb`. 95 | - The `point_to_healpix` method, use the `assign_to_healpix` instead. 96 | - The `frame` parameter to the `assign_to_healpix` method. The frame from the object's 97 | skycoord parameter is now always used for the Healpix frame, use `transform_to` to 98 | convert to the desired frame before calling `assign_to_healpix`. 99 | - The `source_cuts` method and the associated `source_select_keywords` parameters from 100 | the various `read` methods. 101 | - The `to_recarray` and `from_recarray` methods. 102 | - Support for reading old `healvis` style Healpix hdf5 files (via the associated 103 | `read_healpix_hdf5`, `from_healpix_hdf5` and `write_healpix_hdf5` methods). skyh5 104 | files are now the only supported hdf5 file format. 105 | - The `ra_column` and `dec_column` parameters to the `read` and `from_file` methods. Use 106 | the more generic `lon_column` and `lat_column` parameters instead. 107 | 108 | 109 | ## [0.2.0] - 2023-02-01 110 | 111 | ### Added 112 | - Add support for all astropy coordinate frames with the new `skycoord` attribute (which 113 | contains an astropy SkyCoord object) on point objects and the new `hpx_frame` attribute 114 | on healpix objects. Users can interact with the SkyCoord object directly or use 115 | convenience methods on SkyModel as appropriate. References to older SkyModel attributes 116 | are handled for backwards compatibility. 117 | - Read/write methods for text and votable files now support more astropy frames (not 118 | just `icrs`). 119 | - New `lon_column`, `lat_column` and `frame` parameters to the `read` and `from_file` 120 | methods which apply when reading votable files. 121 | - Added `calc_frame_coherency` method to calculate and optionally store the 122 | frame_coherency on the object. 123 | 124 | ### Changed 125 | - Updated the astropy requirement to >= 5.2 126 | - No longer calculate frame_coherency (previously ra_dec_coherency) on SkyModel 127 | initialization to save memory. 128 | - `J2000` in ra and dec columns names of text files are now properly identified as 129 | indicating that the coordinates are in the FK5 frame rather than ICRS. 130 | - Added handling for the new `skycoord` and `hpx_frame` parameters in skyh5 read/write 131 | methods. 132 | - Updated the pyuvdata requirement to >= 2.2.10 133 | - Added support for `allowed_failures` keyword in `SkyModel.__eq__` to match 134 | `pyuvdata.UVBase`, update pyuvdata minimum version to 2.2.1 when that keyword was 135 | introduced. 136 | - Updated the astropy requirement to >= 5.0.4 137 | - Dropped support for python 3.7 138 | 139 | ### Deprecated 140 | - The `ra_dec_coherency` attribute in favor of `frame_coherency` because SkyModel 141 | objects can be in frames that do not use ra/dec coordinates. 142 | - The `lon`, `lat` and `frame` attributes, in favor of the `skycoord` attribute for 143 | point objects and the `hpx_inds` and `hpx_frame` attributes for healpix objects. 144 | - The `ra_column` and `dec_column` parameters in the `read` and `from_file` methods in 145 | favor of the `lon_column` and `lat_column` as a part of supporting more frames in 146 | votable files. 147 | - The `to_recarray` and `from_recarray` methods. 148 | 149 | ### Removed 150 | - Removed support for the older input parameter order to `SkyModel.__init__`. 151 | - Removed support for passing `freq_array`, `reference_freq` or `stokes` to 152 | `SkyModel.__init__` as anything other than appropriate Quantities. 153 | - `set_spectral_type_params` and `read_idl_catalog` methods have been 154 | removed from `SkyModel` 155 | - Removed the `read_healpix_hdf5`, `healpix_to_sky`, `write_healpix_hdf5`, 156 | `skymodel_to_array`, `array_to_skymodel`, `source_cuts`, `read_votable_catalog`, 157 | `read_gleam_catalog`, `read_text_catalog`, `read_idl_catalog`, `write_catalog_to_file` 158 | functions (many similar methods on `SkyModel` remain). 159 | - Removed support for passing telescope_location directly to the 160 | `SkyModel.coherency_calc` method. 161 | - Removed support for votable_files with tables with no name or ID. 162 | - Removed `frequency` column and alias like `flux_density_I` for flux columns in output 163 | of `SkyModel.to_recarray`, 164 | 165 | ## [0.1.3] - 2022-02-22 166 | 167 | ### Added 168 | - Generic `read` and `from_file` methods to SkyModel objects that accept any file type 169 | supported by SkyModel. 170 | 171 | ### Added 172 | - A `filename` attribute to SkyModel objects. 173 | - The `nan_handling` keyword to the `at_frequencies` method to control how NaNs in the 174 | stokes array on subband objects are handled during interpolation. 175 | - The `lat_range` and `lon_range` keywords to the `select` method to support selecting 176 | on fields. 177 | - The `min_brightness`, `max_brightness` and `brightness_freq_range` keywords to the 178 | `select` method to support selecting on brightness. 179 | - The `cut_nonrising` method to remove sources that never rise given a telescope 180 | location, replacing functionality that was in the `source_cuts` method. 181 | - The `calculate_rise_set_lsts` method to calculate and set the `_rise_lst` and 182 | `_set_lst` attributes on the object, replacing functionality that was in the 183 | `source_cuts` method. 184 | - The `get_lon_lat` method which computes the values from `hpx_inds` on healpix objects 185 | and just returns the parameter values on point objects. 186 | - The `assign_to_healpix` method to assign point component objects to a healpix grid 187 | (using the nearest neighbor approach). 188 | 189 | ### Fixed 190 | - A bug that caused the stokes array to be all NaNs after using the `at_frequencies` 191 | method on a subband spectral_type object if any NaNs appeared in the input stokes. 192 | 193 | ### Changed 194 | - `ra` and `dec` are no longer required parameters on healpix objects (since that 195 | information is encoded in the `hpx_inds` parameter). When objects are initialized as 196 | healpix objects the `ra` and `dec` parameters are no longer populated. 197 | - `point_to_healpix` has been renamed to `_point_to_healpix` because it's only intended 198 | to be used internally to undo the `healpix_to_point` method. 199 | 200 | ## Fixed 201 | - A bug in `concat` when optional spectral paramters are not None on one of the objects. 202 | 203 | ### Deprecated 204 | - The `source_cuts` method and the `source_select_kwds` keywords to the reading methods. 205 | - The `point_to_healpix` method. 206 | 207 | ## [0.1.2] - 2021-07-06 208 | 209 | ### Added 210 | - Ability to overwrite default ra/dec when importing a healpix map 211 | - `clobber` keyword to allow overwriting of skyh5 files 212 | - Support for ring / nested healpix ordering. 213 | - New `SkyModel.concat` method to support concatenating catalogs. 214 | - A new optional parameters `stokes_error` to track errors on the fluxes reported by catalogs. 215 | 216 | ### Fixed 217 | - Fix bug in writing skyh5 files with composite stokes units (e.g. Jy/sr) 218 | - Fix bugs causing healpix ordering to not be round tripped properly. 219 | - Some bugs related to writing & reading skyh5 files after converting object using `healpix_to_point` method. 220 | 221 | ## [0.1.1] - 2021-02-17 222 | 223 | ### Added 224 | - Read and write methods for skyh5 -- our newly defined hdf5 file format that fully 225 | supports all SkyModel types. 226 | - Classmethods `from_recarray`, `from_healpix_hdf5`, `from_votable_catalog`, 227 | `from_text_catalog`, `from_gleam_catalog` and `from_idl_catalog` to enable 228 | instantiation from different formats directly. 229 | 230 | ### Changed 231 | - Changed default `spectral_type` for `read_gleam_catalog` to `subband` rather than `flat`. 232 | - Changed `extended_model_group` in `read_fhd_catalog` to match the parent source name. 233 | 234 | ### Fixed 235 | - Improved handling of lists passed to `SkyModel.__init__`. 236 | - A bug in the `download_gleam` utility method that caused missing columns for the 237 | `subband` spectral type 238 | - Enabled subselecting to a given tolerance in at_frequencies (for `full` spectral type). 239 | - A bug in `Skymodel.__init__` that caused extended_model_group and beam_amps 240 | to not be added to the object. 241 | - Enabled proper handling of duplicated source IDs in `read_fhd_catalog`. 242 | - A bug in `read_fhd_catalog` did not order the `name` and `beam_amp` attributes correctly for extended sources. 243 | 244 | ## [0.1.0] - 2020-6-29 245 | 246 | ### Added 247 | - The methods `jansky_to_kelvin` and 'kelvin_to_jansky' to convert between Jy 248 | and K based units. 249 | - The methods `healpix_to_point` and `point_to_healpix` to convert between 250 | component types. 251 | 252 | ### Changed 253 | - Renamed the `read_idl_catalog` method to `read_fhd_catalog`. 254 | - The `stokes` and `coherency_radec` parameters are now Quantity objects and must 255 | have units of 'Jy' or 'K sr' if `component_type` is 'point' and 'Jy/sr' or 'K' 256 | if the `component_type` is 'healpix'. 257 | - The inputs to the utility functions `stokes_to_coherency` and `coherency_to_stokes` 258 | must now be Quantity objects. 259 | - The utility function `jy_to_ksr` now uses astropy's built in conversion methods. 260 | 261 | ### Deprecated 262 | - The `read_idl_catalog` method. 263 | - Initializing a SkyModel object with a float array rather than a Quantity for 264 | `stokes` is deprecated and support will be removed in a future version. 265 | - Passing floats rather than Quantity objects as inputs to the 266 | `stokes_to_coherency` and `coherency_to_stokes` utility functions. 267 | 268 | ## [0.0.2] - 2020-6-4 269 | 270 | ### Added 271 | - Documentation is now hosted on ReadTheDocs at https://pyradiosky.readthedocs.io/en/latest/ 272 | - A utility function and a script to download the GLEAM catalog. 273 | - An `at_frequencies` function that will return a new SkyModel at requested frequencies. 274 | - A new `component_type` parameter that can be set to "healpix" or "point". 275 | - Better support for HEALPix maps, with new `nside` and `hpx_inds` parameters. 276 | - Added the following methods to the `SkyModel` class: `select`, `source_cuts`, 277 | `to_recarray`, `from_recarray`, `read_healpix_hdf5`, `read_votable_catalog`, 278 | `read_gleam_catalog`, `read_text_catalog`, `read_idl_catalog`, `write_healpix_hdf5` 279 | `write_text_catalog`. 280 | 281 | ### Fixed 282 | - A bug in `spherical_coords_transforms.rotate_points_3d` where an arccos 283 | calculation failed for a value larger than one by ~1e-12. 284 | 285 | ### Deprecated 286 | - The following functions in the `skymodel` module were deprecated: `read_healpix_hdf5`, 287 | `write_healpix_hdf5`, `healpix_to_sky`, `skymodel_to_array`, `array_to_skymodel`, 288 | `source_cuts`, `read_votable_catalog`, `read_gleam_catalog`, `read_text_catalog`, 289 | `read_idl_catalog`, `write_catalog_to_file`. 290 | 291 | ## [0.0.1] - 2020-4-6 292 | 293 | ### Added 294 | - SkyModel object inherits from UVBase. 295 | - Moved existing code from pyuvsim, got package set up. 296 | -------------------------------------------------------------------------------- /docs/references/skyh5_memo.tex: -------------------------------------------------------------------------------- 1 | \documentclass[11pt, oneside]{article} 2 | \usepackage{geometry} 3 | \geometry{letterpaper} 4 | \usepackage{graphicx} 5 | \usepackage[titletoc,toc,title]{appendix} 6 | \usepackage{amssymb} 7 | \usepackage{physics} 8 | \usepackage{array} 9 | \usepackage{makecell} 10 | 11 | \usepackage{hyperref} 12 | \hypersetup{ 13 | colorlinks = true 14 | } 15 | 16 | \usepackage{cleveref} 17 | 18 | \title{Memo: SkyH5 file format} 19 | \author{Bryna Hazelton, and the pyradiosky team} 20 | \date{October 5, 2023} 21 | 22 | \begin{document} 23 | \maketitle 24 | \tableofcontents 25 | \section{Introduction} 26 | \label{sec:intro} 27 | 28 | This memo introduces a new HDF5\footnote{\url{https://www.hdfgroup.org/}} based 29 | file format of a \texttt{SkyModel} object in 30 | \texttt{pyradiosky}\footnote{\url{https://github.com/RadioAstronomySoftwareGroup/pyradiosky}}, 31 | a python package that provides objects and interfaces for representing diffuse, 32 | extended and compact astrophysical radio sources. Here, we describe the required 33 | and optional elements and the structure of this file format, called \textit{SkyH5}. 34 | 35 | We assume that the user has a working knowledge of HDF5 and the associated 36 | python bindings in the package \texttt{h5py}\footnote{\url{https://www.h5py.org/}}, as 37 | well as \texttt{SkyModel} objects in \texttt{pyradiosky}. For more information about 38 | HDF5, please visit \url{https://portal.hdfgroup.org/display/HDF5/HDF5}. For more 39 | information about the parameters present in a \texttt{SkyModel} object, please visit 40 | \url{https://pyradiosky.readthedocs.io/en/latest/skymodel.html}. 41 | Examples of how to interact with \texttt{SkyModel} objects in \texttt{pyradiosky} are 42 | available at \url{http://pyradiosky.readthedocs.io/en/latest/tutorial.html}. 43 | 44 | Note that throughout the documentation, we assume a row-major convention (i.e., 45 | C-ordering) for the dimension specification of multi-dimensional arrays. For 46 | example, for a two-dimensional array with shape ($N$, $M$), the $M$-dimension is 47 | varying fastest, and is contiguous in memory. This convention is the same as 48 | Python and the underlying C-based HDF5 library. Users of languages with the 49 | opposite column-major convention (i.e., Fortran-ordering, seen also in MATLAB 50 | and Julia) must transpose these axes. 51 | 52 | \section{Overview} 53 | \label{sec:overview} 54 | A SkyH5 object contains data representing catalogs and maps of 55 | astrophysical radio sources, including the associated metadata necessary to interpret 56 | them. A SkyH5 file contains two primary HDF5 groups: the \texttt{Header} group, 57 | which contains the metadata, and the \texttt{Data} group, which contains the Stokes 58 | parameters representing the flux densities or brightness temperatures of the sources 59 | (as well as some optional arrays the same size as the stokes parameters data). 60 | Datasets in the \texttt{Data} group 61 | are can be passed through HDF5's compression 62 | pipeline, to reduce the amount of on-disk space required to store the data. 63 | However, because HDF5 is aware of any compression applied to a dataset, there is 64 | little that the user has to explicitly do when reading data. For users 65 | interested in creating new files, the use of compression is optional in the 66 | SkyH5 format, because the HDF5 file is self-documenting in this regard. 67 | 68 | Many of the datasets in SkyH5 files have units associated with them (represented 69 | as \texttt{astropy Quantity} objects on the \texttt{SkyModel} object). 70 | The units are stored as attributes on the datasets with the name ``unit''. 71 | Datasets that derive from other \texttt{astropy} objects (e.g. \texttt{astropy Time, 72 | astropy EarthLocation, astropy Latitude, astropy Longitude}) 73 | also have an ``object\_type'' attribute indicating the object type. 74 | 75 | In the discussion below, we discuss required and optional datasets in the 76 | various groups. We note in parenthesis the corresponding attribute of a \texttt{SkyModel} 77 | object. Note that in nearly all cases, the names are coincident, to make things 78 | as transparent as possible to the user. 79 | 80 | \section{Header} 81 | \label{sec:header} 82 | The \texttt{Header} group of the file contains the metadata necessary to interpret 83 | the data. We begin with the required parameters, then continue to optional 84 | ones. Unless otherwise noted, all datasets are scalars (i.e., not arrays). The 85 | precision of the data type is also not specified as part of the format, because 86 | in general the user is free to set it according to the desired use case (and 87 | HDF5 records the precision and endianness when generating datasets). When using 88 | the standard \texttt{h5py}-based implementation in \texttt{pyradiosky}, this typically 89 | results in 32-bit integers and double precision floating point numbers. Each 90 | entry in the list contains \textbf{(1)} the exact name of the dataset in the 91 | HDF5 file, in boldface, \textbf{(2)} the expected datatype of the dataset, in 92 | italics, \textbf{(3)} a brief description of the data, and \textbf{(4)} the name 93 | of the corresponding attribute on a \texttt{SkyModel} object, italicized and in 94 | parentheses at the end of the entry. 95 | 96 | Note that string datatypes should be handled with care. See 97 | the Appendix in the UVH5 memo 98 | (\url{https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/main/docs/references/uvh5_memo.pdf}) 99 | for appropriately defining them for interoperability between different HDF5 100 | implementations. 101 | 102 | \subsection{Required Parameters} 103 | \label{sec:req_params} 104 | \begin{itemize} 105 | 106 | \item \textbf{component\_type}: \textit{string} The type of components in the sky model. 107 | The options are: ``healpix'' and ``point''. If component\_type is ``healpix'', the components 108 | are the pixels in a HEALPix map in units compatible with K or Jy/sr. If the 109 | component\_type is ``point'', the components are point-like sources, or point like 110 | components of extended sources, in units compatible with Jy or K sr. Some additional 111 | parameters are required depending on the component type. (\textit{component\_type}) 112 | 113 | \item \textbf{Ncomponents}: \textit{int} The number of components in the sky model. This 114 | can be the number of individual compact sources, or it can include components of 115 | extended sources, or the number of pixels in a map. (\textit{Ncomponents}) 116 | 117 | \item \textbf{spectral\_type}: \textit{string} This describes the type of spectral model for 118 | the components. The options are: 119 | \begin{enumerate} 120 | \item \textbf{spectral\_index} The convention for the spectral index is 121 | $I=I_0 \frac{f}{f_0}^{\alpha}$, where $I_0$ is the stokes parameter at the 122 | reference\_frequency parameter $f_0$ and $\alpha$ is the spectral\_index 123 | parameter. Note that the spectral index is assumed to apply in the units of the 124 | stokes parameter (i.e. there is no additive factor of 2 applied to convert between 125 | brightness temperature and flux density units). If the spectral model uses a 126 | spectral index, the reference\_frequency and spectral\_index parameters are 127 | required. 128 | \item \textbf{subband} The subband spectral model is used for catalogs with 129 | multiple flux measurements at different frequencies (i.e. GLEAM 130 | \url{https://www.mwatelescope.org/science/galactic-science/gleam/}). For subband 131 | spectral models, the freq\_array and freq\_edge\_array parameters are required 132 | to give the nominal (usually the central) frequency and the top and bottom of 133 | each subband respectively. 134 | \item \textbf{flat} The flat spectral model assumes no spectral flux dependence, 135 | which can be useful for testing. Since the flux is assumed to be the same at all 136 | frequencies it does not require any extra parameters to be defined. 137 | \item \textbf{full} The full spectral model is used for catalogs with flux values at 138 | multiple frequencies that are not expected to have flux correlations as a function 139 | of frequency, so cannot not be interpolated to frequencies not included in the 140 | catalog. This is a good representation for e.g. Epoch of Reionization signal cubes. 141 | For full spectral models, the freq\_array parameter is required to give the frequencies. 142 | \end{enumerate} 143 | (\textit{spectral\_type}) 144 | 145 | \item \textbf{Nfreqs}: \textit{int} 146 | Number of frequencies if spectral\_type is ``full'' or ``subband'', 1 otherwise. 147 | (\textit{Nfreqs}) 148 | 149 | \item \textbf{history}: \textit{string} The history of the catalog. (\textit{history}) 150 | \end{itemize} 151 | 152 | 153 | \subsection{Optional Parameters} 154 | \label{sec:opt_params} 155 | \begin{itemize} 156 | \item \textbf{name}: \textit{string} The name for each component. This is a 157 | one-dimensional array of size (Ncomponents). 158 | Note this is \textbf{required} if the component\_type is ``point''. (\textit{name}) 159 | 160 | \item \textbf{skycoord}: 161 | A nested dataset that contains the information to create an \texttt{astropy SkyCoord} 162 | object representing the component positions. Note this is \textbf{required} if the 163 | component\_type is ``point''. The keys must include: 164 | \begin{itemize} 165 | \item \textbf{frame}: \textit{string} The name of the coordinate frame 166 | (e.g. ``icrs'', ``fk5'', ``galactic''). Must be a frame supported by \texttt{astropy}. 167 | Note that only one frame is allowed, which applies to all the components. 168 | \item \textbf{representation\_type}: \textit{string} The representation type, one 169 | of ``spherical'', ``cartesian'' or ``cylindrical''. This sets what the coordinate 170 | names can be. It is most common to set this to ``spherical" and 171 | specify latitudinal and longitudinal coordinates (e.g. \textbf{ra}, \textbf{dec}) 172 | and optionally distance coordinates. It is also possible to use other 173 | representations such as cartesian or cylindrical, e.g. for ``cartesian" the 174 | coordinates would be specified in x, y, and z. See the \texttt{astropy SkyCoord} 175 | docs for more details. 176 | \item Coordinate names (e.g. \textbf{ra}, \textbf{dec}, \textbf{alt}, \textbf{az}): 177 | \textit{float} Two or three such components must be present, which ones are 178 | required depend on the frame and representation\_type. These are 179 | one-dimensional arrays of size (Ncomponents). 180 | \end{itemize} 181 | And may include any other attributes accepted as input parameters for an 182 | \texttt{astropy SkyCoord} object (e.g. obstime, equinox, location). 183 | Each of these datasets may have ``unit'' and ``object\_type'' attributes and may 184 | be either a scalar or a one-dimensional array of size (Ncomponents) as 185 | appropriate. 186 | (\textit{skycoord}) 187 | 188 | \item \textbf{nside}: \textit{int} 189 | The HEALPix nside parameter. Note this is \textbf{required} if the 190 | component\_type is ``healpix'' and should not be defined otherwise. 191 | (\textit{nside}) 192 | 193 | \item \textbf{hpx\_order}: \textit{string} 194 | The HEALPix pixel ordering convention, either ``ring'' or ``nested''. 195 | Note this is \textbf{required} if the component\_type is ``healpix'' and should 196 | not be defined otherwise. (\textit{hpx\_order}) 197 | 198 | \item \textbf{hpx\_frame}: A nested dataset that contains the information to 199 | describe an \texttt{astropy} coordinate frame giving the HEALPix coordinate 200 | frame. This is similar to the skycoord dataset described above but it does not 201 | contain the representation\_type or the coordinate names. 202 | Note this is \textbf{required} if the component\_type is ``healpix'' and should 203 | not be defined otherwise. The keys must include: 204 | \begin{itemize} 205 | \item \textbf{frame}: \textit{string} The name of the coordinate frame 206 | (e.g. ``icrs'', ``fk5'', ``galactic''). Must be a frame supported by 207 | \texttt{astropy}. 208 | \end{itemize} 209 | And may include any other scalar attributes accepted as input parameters 210 | for an \texttt{astropy SkyCoord} object (e.g. obstime, equinox, location). Each 211 | of these datasets may have ``unit'' and ``object\_type'' attributes as appropriate. 212 | (\textit{hpx\_frame}) 213 | 214 | \item \textbf{hpx\_inds}: \textit{int} 215 | The HEALPix indices for the included components. Does not need to include 216 | all the HEALPix pixels in the map. This is a one-dimensional array of size 217 | (Ncomponents). Note this is \textbf{required} if the component\_type is 218 | ``healpix'' and should not be defined otherwise. (\textit{hpx\_inds}) 219 | 220 | \item \textbf{freq\_array}: \textit{float} 221 | Frequency array giving the nominal (or central) frequency in a unit that can be 222 | converted to Hz. Note this is \textbf{required} if the spectral\_type is ``full'' or 223 | ``subband'' and should not be defined otherwise. (\textit{freq\_array}) 224 | 225 | \item \textbf{freq\_edge\_array}: \textit{float} 226 | Array giving the frequency band edges in a unit that can be converted to Hz. 227 | This is a two dimensional array with shape (2, Nfreqs). The zeroth index in 228 | the first dimension holds the lower band edge and the first index holds the 229 | upper band edge. Note this is \textbf{required} if the spectral\_type is 230 | ``subband'' and should not be defined otherwise. (\textit{freq\_edge\_array}) 231 | 232 | \item \textbf{reference\_frequency}: \textit{float} 233 | Reference frequency giving the frequency at which the flux in the stokes 234 | parameter was measured in a unit that can be converted to Hz. This is a 235 | one-dimensional array of size (Ncomponents). Note this is \textbf{required} if 236 | the spectral\_type is ``spectral\_index'' and should not be defined if the 237 | spectral\_type is ``full'' or ``subband''. 238 | (\textit{reference\_frequency}) 239 | 240 | \item \textbf{spectral\_index}: \textit{float} 241 | The spectral index describing the flux evolution with frequency, see details 242 | in the spectral\_type description above. This is a one-dimensional array 243 | of size (Ncomponents). Note this is \textbf{required} if the spectral\_type is 244 | ``spectral\_index'' and should not be defined otherwise. 245 | (\textit{spectral\_index}) 246 | 247 | \item \textbf{extended\_model\_group}: \textit{string} 248 | Identifier that groups components of an extended source model. 249 | This is a one-dimensional array of size (Ncomponents), with empty strings 250 | for point sources that are not components of an extended source. 251 | (\textit{extended\_model\_group}) 252 | \end{itemize} 253 | 254 | \subsection{Extra Columns} 255 | \label{sec:extra-columns} 256 | \texttt{SkyModel} objects support ``extra columns'', which are additional arbitrary 257 | per-component arrays of metadata that are useful to carryaround with the data but 258 | which are not formally supported as a reserved keyword in the \texttt{Header}. In a 259 | SkyH5 file, extra columns are handled by creating a datagroup called 260 | \textbf{extra\_columns} inside the \texttt{Data} datagroup. When possible, these 261 | quantities should be HDF5 datatypes, to support interoperability between SkyH5 262 | readers. Inside of the extra\_columns datagroup, each extra columns is saved as 263 | a key-value pair using a dataset, where the name of the extra columns is the name 264 | of the dataset and its corresponding array is saved in the dataset. The ``unit'' and 265 | ``object\_type'' HDF5 attributes are used in the same way as for the other header 266 | items (see \ref{sec:overview}), but it is not recommended to use other attribute 267 | names due to the lack of support inside of \texttt{pyradiosky} for ensuring the 268 | attributes are properly saved when reading and writing SkyH5 files. 269 | 270 | \section{Data} 271 | \label{sec:data} 272 | In addition to the \texttt{Data} datagroup in the root namespace, there must be 273 | one called \texttt{Data}. This datagroup saves the Stokes parameters representing 274 | the flux densities or brightness temperatures of the sources and some optional 275 | arrays that are the same size. They are also all expected to be the same shape: 276 | (4, Nfreqs, Ncomponents) where the first dimension indexes the polarization 277 | direction, ordered (I, Q, U, V). The \textbf{stokes} dataset must be present in this 278 | datagroup and it must have a ``unit'' attribute that is equivalent to Jy or K str if the 279 | component\_type is ``point'' or equivalent to Jy/str or K if the component\_type is 280 | ``healpix''. In addition, this datagroup may also contain a \textbf{stokes\_error} 281 | dataset that gives the standard error on the stokes values and should have the 282 | same ``unit'' attribute as the stokes dataset and a \textbf{beam\_amp} dataset 283 | that gives the beam amplitude at the source position for the instrument that made 284 | the measurement. 285 | 286 | \end{document} 287 | -------------------------------------------------------------------------------- /src/pyradiosky/data/single_source_old.vot: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | GLEAM first year observing parameters 9 | 10 | 11 | 12 | Observation date 13 | 14 | 15 | 16 | 17 | 18 | RA range 19 | 20 | 21 | 22 | 23 | RA range 24 | 25 | 26 | 27 | 28 | Declination 29 | 30 | 31 | 32 | 33 | Number of flagged tiles out of the 128 available 34 | 35 | 36 | 37 | 38 | Calibrator (1) 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 |
2013-08-0919.5 3.5-55103C444
2013-08-1019.5 3.5-2743C444
2013-08-1819.5 3.5-727Pictor A
2013-08-2219.5 3.5-1333C444
2013-08-2519.5 3.5-4053C444
2013-11-05 0.0 8.0-134Hydra A
2013-11-06 0.0 8.0-404Hydra A
2013-11-07 0.0 8.024Hydra A
2013-11-08 0.0 8.0-554Hydra A
2013-11-11 0.0 8.0194Hydra A
2013-11-12 0.0 8.0-724Hydra A
2013-11-25 0.0 8.0-274Hydra A
2014-03-03 6.016.0-274Hydra A
2014-03-04 6.016.0-134Hydra A
2014-03-06 6.016.024Hydra A
2014-03-08 6.016.0194Hydra A
2014-03-09 6.016.0-724Virgo A
2014-03-16 6.016.0-404Virgo A
2014-03-17 6.016.0-554Hydra A
2014-06-0912.022.0-2783C444
2014-06-1012.022.0-4083C444
2014-06-1112.022.028Hercules A
2014-06-1212.018.5-5583C444
2014-06-1312.019.0-138Centaurus A
2014-06-1412.022.0-729Hercules A
2014-06-1512.022.01813Virgo A
2014-06-1618.522.0-1383C444
2014-06-1818.522.0-5583C444
270 | 271 | 272 | 273 | Right ascension (FK5, Equinox=J2000.0) (computed by VizieR, not 274 | part of the original data. The format may include more digits 275 | than the original data because of internal accuracy requirements 276 | in VizieR and across other CDS services) 277 | 278 | 279 | 280 | 281 | Declination (FK5, Equinox=J2000.0) (computed by VizieR, not part 282 | of the original data. The format may include more digits than the 283 | original data because of internal accuracy requirements in VizieR 284 | and across other CDS services) 285 | 286 | 287 | 288 | 289 | GLEAM name (JHHMMSS+DDMMSS) (Name) 290 | 291 | 292 | 293 | 294 | Right Ascension J2000 (RAJ2000) 295 | 296 | 297 | 298 | 299 | Declination J2000 (DEJ2000) 300 | 301 | 302 | 303 | 304 | Peak flux in wide (170-231MHz) image (peak_flux_wide) 305 | 306 | 307 | 308 | 309 | Integrated flux in wide (170-231MHz) image (int_flux_wide) 310 | 311 | 312 | 313 | 314 | Percent error in absolute flux scale - all frequencies 315 | (err_abs_flux_pct) 316 | 317 | 318 | 319 | 320 | Percent error on internal flux scale - all frequencies 321 | (err_fit_flux_pct) 322 | 323 | 324 | 325 | 326 | ? Peak flux in 072-080MHz image (peak_flux_076) 327 | 328 | 329 | 330 | 331 | 332 | ? Integrated flux in 072-080MHz image (int_flux_076) 333 | 334 | 335 | 336 | 337 | 338 | ? Peak flux in 080-088MHz image (peak_flux_084) 339 | 340 | 341 | 342 | 343 | 344 | ? Integrated flux in 080-088MHz image (int_flux_084) 345 | 346 | 347 | 348 | 349 | 350 | ? Peak flux in 088-095MHz image (peak_flux_092) 351 | 352 | 353 | 354 | 355 | 356 | ? Integrated flux in 088-095MHz image (int_flux_092) 357 | 358 | 359 | 360 | 361 | 362 | ? Peak flux in 095-103MHz image (peak_flux_099) 363 | 364 | 365 | 366 | 367 | 368 | ? Integrated flux in 095-103MHz image (int_flux_099) 369 | 370 | 371 | 372 | 373 | 374 | ? Peak flux in 103-111MHz image (peak_flux_107) 375 | 376 | 377 | 378 | 379 | 380 | ? Integrated flux in 103-111MHz image (int_flux_107) 381 | 382 | 383 | 384 | 385 | 386 | ? Peak flux in 111-118MHz image (peak_flux_115) 387 | 388 | 389 | 390 | 391 | 392 | ? Integrated flux in 111-118MHz image (int_flux_115) 393 | 394 | 395 | 396 | 397 | 398 | ? Peak flux in 118-126MHz image (peak_flux_122) 399 | 400 | 401 | 402 | 403 | 404 | ? Integrated flux in 118-126MHz image (int_flux_122) 405 | 406 | 407 | 408 | 409 | 410 | ? Peak flux in 126-134MHz image (peak_flux_130) 411 | 412 | 413 | 414 | 415 | 416 | ? Integrated flux in 126-134MHz image (int_flux_130) 417 | 418 | 419 | 420 | 421 | 422 | ? Peak flux in 139-147MHz image (peak_flux_143) 423 | 424 | 425 | 426 | 427 | 428 | ? Integrated flux in 139-147MHz image (int_flux_143) 429 | 430 | 431 | 432 | 433 | 434 | ? Peak flux in 147-154MHz image (peak_flux_151) 435 | 436 | 437 | 438 | 439 | 440 | ? Integrated flux in 147-154MHz image (int_flux_151) 441 | 442 | 443 | 444 | 445 | 446 | ? Peak flux in 154-162MHz image (peak_flux_158) 447 | 448 | 449 | 450 | 451 | 452 | ? Integrated flux in 154-162MHz image (int_flux_158) 453 | 454 | 455 | 456 | 457 | 458 | ? Peak flux in 162-170MHz image (peak_flux_166) 459 | 460 | 461 | 462 | 463 | 464 | ? Integrated flux in 162-170MHz image (int_flux_166) 465 | 466 | 467 | 468 | 469 | 470 | ? Peak flux in 170-177MHz image (peak_flux_174) 471 | 472 | 473 | 474 | 475 | 476 | ? Integrated flux in 170-177MHz image (int_flux_174) 477 | 478 | 479 | 480 | 481 | 482 | ? Peak flux in 177-185MHz image (peak_flux_181) 483 | 484 | 485 | 486 | 487 | 488 | ? Integrated flux in 177-185MHz image (int_flux_181) 489 | 490 | 491 | 492 | 493 | 494 | ? Peak flux in 185-193MHz image (peak_flux_189) 495 | 496 | 497 | 498 | 499 | 500 | ? Integrated flux in 185-193MHz image (int_flux_189) 501 | 502 | 503 | 504 | 505 | 506 | ? Peak flux in 193-200MHz image (peak_flux_197) 507 | 508 | 509 | 510 | 511 | 512 | ? Integrated flux in 193-200MHz image (int_flux_197) 513 | 514 | 515 | 516 | 517 | 518 | ? Peak flux in 200-208MHz image (peak_flux_204) 519 | 520 | 521 | 522 | 523 | 524 | ? Integrated flux in 200-208MHz image (int_flux_204) 525 | 526 | 527 | 528 | 529 | 530 | ? Peak flux in 208-216MHz image (peak_flux_212) 531 | 532 | 533 | 534 | 535 | 536 | ? Integrated flux in 208-216MHz image (int_flux_212) 537 | 538 | 539 | 540 | 541 | 542 | ? Peak flux in 216-223MHz image (peak_flux_220) 543 | 544 | 545 | 546 | 547 | 548 | ? Integrated flux in 216-223MHz image (int_flux_220) 549 | 550 | 551 | 552 | 553 | 554 | ? Peak flux in 223-231MHz image (peak_flux_227) 555 | 556 | 557 | 558 | 559 | 560 | ? Integrated flux in 223-231MHz image (int_flux_227) 561 | 562 | 563 | 564 | 565 | 566 | ? Fitted spectral index (alpha) 567 | 568 | 569 | 570 | 571 | 572 | ? Fitted 200MHz integrated flux density (int_flux_fit_200) 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 |
0.000000000.0000000TEST0.000000-30.0000000.000000 1.00000016959630.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.000000
634 |
635 |
636 | --------------------------------------------------------------------------------