diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..7da1f960 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 100 diff --git a/.github/workflows/cron-tests.yml b/.github/workflows/cron-tests.yml index 84530154..2bb4da17 100644 --- a/.github/workflows/cron-tests.yml +++ b/.github/workflows/cron-tests.yml @@ -5,48 +5,31 @@ name: Weekly Tests on: + pull_request: + # We also want this workflow triggered if the 'Extra CI' label is added + # or present when PR is updated + types: + - synchronize + - labeled schedule: # run every Monday at 6am UTC - cron: '0 6 * * 1' -env: - TOXARGS: '-v' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - # Set up matrix to run tox tests across lists of os, python version, and tox environment - matrix_tests: - runs-on: ${{ matrix.os }} - strategy: - matrix: - # Github actions supports ubuntu, windows, and macos virtual environments: - # https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners - # - # Only run on ubuntu by default, but can add other os's to the test matrix here. - # For example -- os: [ubuntu-latest, macos-latest, windows-latest] - include: - - os: ubuntu-latest - python: '3.11' - tox_env: 'linkcheck' - - os: ubuntu-latest - python: '3.12' - tox_env: 'py312-test-devdeps' - - os: ubuntu-latest - python: '3.12' - tox_env: 'py312-test-predeps' + tests: + if: (github.repository == 'astropy/specreduce' && (github.event_name == 'schedule' || github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'Extra CI'))) + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 + with: + submodules: false + coverage: '' + envs: | + - name: Check URLs in docs + linux: linkcheck - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up python ${{ matrix.python }} with tox environment ${{ matrix.tox_env }} on ${{ matrix.os }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python }} - - name: Install base dependencies - run: | - python -m pip install --upgrade pip - python -m pip install tox - - name: Test with tox - run: | - tox -e ${{ matrix.tox_env }} + - name: Python 3.12 on Linux with pre-releases + linux: py312-test-alldeps-predeps + toxargs: -v diff --git a/.github/workflows/tox-tests.yml b/.github/workflows/tox-tests.yml index 3005622d..cc2ee2fa 100644 --- a/.github/workflows/tox-tests.yml +++ b/.github/workflows/tox-tests.yml @@ -11,64 +11,44 @@ on: tags: - '*' pull_request: + schedule: + # run every Monday at 6am UTC + - cron: '0 6 * * 1' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - TOXARGS: '-v' - jobs: - # Set up matrix to run tox tests across lists of os, python version, and tox environment - matrix_tests: - runs-on: ${{ matrix.os }} - strategy: - matrix: - # Github actions supports ubuntu, windows, and macos virtual environments: - # https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners - # - # Only run on ubuntu by default, but can add other os's to the test matrix here. - # For example -- os: [ubuntu-latest, macos-latest, windows-latest] - include: - - os: ubuntu-latest - python: '3.10' - tox_env: 'py310-test-cov' - - os: ubuntu-latest - python: '3.11' - tox_env: 'py311-test' - - os: ubuntu-latest - python: '3.12' - tox_env: 'py312-test' - - os: macos-latest - python: '3.12' - tox_env: 'py312-test-devdeps' - - os: ubuntu-latest - python: '3.12' - tox_env: 'codestyle' - - os: ubuntu-latest - python: '3.10' - tox_env: 'py310-test-oldestdeps' + tests: + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV }} + with: + submodules: false + coverage: '' + envs: | + - name: Codestyle + linux: codestyle + + - name: Python 3.8 on Linux with oldest supported dependencies + linux: py38-test-alldeps-oldestdeps + toxargs: -v + + - name: Python 3.9 on Windows with minimal dependencies + windows: py39-test + toxargs: -v + + - name: Python 3.10 on OSX with minimal dependencies + macos: py310-test + toxargs: -v + + - name: Python 3.11 on Linux with all dependencies, remote data, and coverage + linux: py311-test-alldeps-cov + coverage: codecov + toxargs: -v + posargs: --remote-data=any - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up python ${{ matrix.python }} with tox environment ${{ matrix.tox_env }} on ${{ matrix.os }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python }} - - name: Install base dependencies - run: | - python -m pip install --upgrade pip - python -m pip install tox - - name: Test with tox - run: | - tox -e ${{ matrix.tox_env }} - - name: Upload coverage to codecov - if: "contains(matrix.tox_env, '-cov')" - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - verbose: true + - name: (Allowed Failure) Python 3.12 on Linux with dev dependencies + linux: py312-test-devdeps + toxargs: -v diff --git a/.readthedocs.yml b/.readthedocs.yaml similarity index 87% rename from .readthedocs.yml rename to .readthedocs.yaml index b9cf0085..16476ed2 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yaml @@ -1,11 +1,11 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-22.04 apt_packages: - graphviz tools: - python: "3.10" + python: "3.11" sphinx: builder: html diff --git a/CHANGES.rst b/CHANGES.rst index 9d005521..4ce17f25 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,30 +5,37 @@ New Features ^^^^^^^^^^^^ - Added 'interpolated_profile' option for HorneExtract. If The ``interpolated_profile`` option -is used, the image will be sampled in various wavelength bins (set by -``n_bins_interpolated_profile``), averaged in those bins, and samples are then -interpolated between (linear by default, interpolation degree can be set with -the ``interp_degree_interpolated_profile`` parameter) to generate a continuously varying -spatial profile that can be evaluated at any wavelength.[ #173] + is used, the image will be sampled in various wavelength bins (set by + ``n_bins_interpolated_profile``), averaged in those bins, and samples are then + interpolated between (linear by default, interpolation degree can be set with + the ``interp_degree_interpolated_profile`` parameter) to generate a continuously varying + spatial profile that can be evaluated at any wavelength. [#173] API Changes ^^^^^^^^^^^ -- Fit residuals exposed for wavelength calibration in WavelengthCalibration1D.fit_residuals. [#446] +- Fit residuals exposed for wavelength calibration in ``WavelengthCalibration1D.fit_residuals``. [#446] Bug Fixes ^^^^^^^^^ - Output 1D spectra from Background no longer include NaNs. Output 1D -spectra from BoxcarExtract no longer include NaNs when none are present -in the extraction window. NaNs in the window will still propagate to -BoxcarExtract's extracted 1D spectrum. [#159] + spectra from BoxcarExtract no longer include NaNs when none are present + in the extraction window. NaNs in the window will still propagate to + BoxcarExtract's extracted 1D spectrum. [#159] -- Backgrounds using median statistic properly ignore zero-weighted pixels -[#159] +- Backgrounds using median statistic properly ignore zero-weighted pixels. + [#159] -- HorneExtract now accepts 'None' as a vaild option for bkgrd_prof [#171] +- HorneExtract now accepts 'None' as a vaild option for ``bkgrd_prof``. [#171] +Other changes +^^^^^^^^^^^^^ + +- The following packages are now optional dependencies because they are not + required for core functionality: ``matplotlib``, ``photutils``, ``synphot``. + To install them anyway, use the ``[all]`` specifier when you install specreduce; e.g.: + ``pip install specreduce[all]`` [#202] 1.3.0 (2022-12-05) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index 8c13ad01..a43828c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,16 +1,11 @@ include README.rst include CHANGES.rst -include setup.cfg -include LICENSE.rst include pyproject.toml -recursive-include specreduce *.pyx *.c *.pxd recursive-include docs * recursive-include licenses * -recursive-include scripts * +prune notebook_sandbox prune build prune docs/_build prune docs/api - -global-exclude *.pyc *.o diff --git a/README.rst b/README.rst index f58f086d..b13adbf3 100644 --- a/README.rst +++ b/README.rst @@ -1,22 +1,27 @@ Specreduce ========== -.. image:: https://github.com/astropy/specreduce/workflows/Python%20Tests/badge.svg - :target: https://github.com/astropy/specreduce/actions +.. image:: https://github.com/astropy/specreduce/actions/workflows/tox-tests.yml/badge.svg?branch=main + :target: https://github.com/astropy/specreduce/actions/workflows/tox-tests.yml :alt: CI Status +.. image:: https://codecov.io/gh/astropy/specreduce/graph/badge.svg?token=3fLGjZ2Pe0 + :target: https://codecov.io/gh/astropy/specreduce + :alt: Coverage + .. image:: https://readthedocs.org/projects/specreduce/badge/?version=latest - :target: http://specreduce.readthedocs.io/en/latest/?badge=latest + :target: http://specreduce.readthedocs.io/en/latest/ :alt: Documentation Status -.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.6608788.svg - :target: https://doi.org/10.5281/zenodo.6608788 - :alt: Zenodo DOI 10.5281/zenodo.6608788 +.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.6608787.svg + :target: https://zenodo.org/doi/10.5281/zenodo.6608787 + :alt: Zenodo DOI 10.5281/zenodo.6608787 .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat - :target: http://www.astropy.org/ + :target: http://www.astropy.org/ + :alt: Powered by Astropy -Specreduce is an Astropy affiliated package with the goal of providing a shared +Specreduce is an Astropy coordinated package with the goal of providing a shared set of Python utilities that can be used to reduce and calibrate spectroscopic data. License diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..3d6b45de --- /dev/null +++ b/conftest.py @@ -0,0 +1,26 @@ +"""Need to repeat the astropy header config here for tox.""" + +try: + from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS + ASTROPY_HEADER = True +except ImportError: + ASTROPY_HEADER = False + + +def pytest_configure(config): + + if ASTROPY_HEADER: + + config.option.astropy_header = True + + # Customize the following lines to add/remove entries from the list of + # packages for which version numbers are displayed when running the tests. + PYTEST_HEADER_MODULES.pop('Pandas', None) + PYTEST_HEADER_MODULES.pop('h5py', None) + PYTEST_HEADER_MODULES['astropy'] = 'astropy' + PYTEST_HEADER_MODULES['specutils'] = 'specutils' + PYTEST_HEADER_MODULES['photutils'] = 'photutils' + PYTEST_HEADER_MODULES['synphot'] = 'synphot' + + from specreduce import __version__ + TESTED_VERSIONS["specreduce"] = __version__ diff --git a/docs/_static/logo_icon.ico b/docs/_static/logo_icon.ico new file mode 100644 index 00000000..468670e7 Binary files /dev/null and b/docs/_static/logo_icon.ico differ diff --git a/docs/_static/logo_icon.png b/docs/_static/logo_icon.png new file mode 100644 index 00000000..7ea90d6e Binary files /dev/null and b/docs/_static/logo_icon.png differ diff --git a/docs/_static/specreduce.css b/docs/_static/specreduce.css new file mode 100644 index 00000000..229d8787 --- /dev/null +++ b/docs/_static/specreduce.css @@ -0,0 +1,15 @@ +@import url("bootstrap-astropy.css"); + +div.topbar a.brand { + background: transparent url("logo_icon.png") no-repeat 8px 3px; + background-image: url("logo_icon.png"), none; + background-size: 32px 32px; +} + +#logotext1 { + color: #519EA8; +} + +#logotext2 { + color: #FF5000; +} diff --git a/docs/conf.py b/docs/conf.py index ea4f7f0b..6649e7f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,10 +25,10 @@ # Thus, any C-extensions that are needed to build the documentation will *not* # be accessible, and the documentation will not build correctly. -import os import sys import datetime -from importlib import import_module + +from specreduce import __version__ try: from sphinx_astropy.conf.v1 import * # noqa @@ -36,13 +36,6 @@ print('ERROR: the documentation requires the sphinx-astropy package to be installed') sys.exit(1) -# Get configuration information from setup.cfg -from configparser import ConfigParser -conf = ConfigParser() - -conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) -setup_cfg = dict(conf.items('metadata')) - # -- General configuration ---------------------------------------------------- # By default, highlight as Python 3. @@ -67,22 +60,19 @@ # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does -project = setup_cfg['name'] -author = setup_cfg['author'] +project = "specreduce" +author = "Astropy Specreduce contributors" copyright = '{0}, {1}'.format( - datetime.datetime.now().year, setup_cfg['author']) + datetime.datetime.now().year, author) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -import_module(setup_cfg['name']) -package = sys.modules[setup_cfg['name']] - # The short X.Y version. -version = package.__version__.split('-', 1)[0] +version = __version__.split('-', 1)[0] # The full version, including alpha/beta/rc tags. -release = package.__version__ +release = __version__ # -- Options for HTML output -------------------------------------------------- @@ -103,17 +93,20 @@ # a list of builtin themes. To override the custom theme, set this to the # name of a builtin theme or the name of a custom theme in html_theme_path. #html_theme = None +html_static_path = ['_static'] # html_theme = None +html_style = 'specreduce.css' html_theme_options = { - 'logotext1': 'specreduce', # white, semi-bold - 'logotext2': '', # orange, light + 'logotext1': 'spec', # white, semi-bold + 'logotext2': 'reduce', # orange, light 'logotext3': ':docs' # white, light } - # Custom sidebar templates, maps document names to template names. #html_sidebars = {} +html_sidebars['**'] = ['localtoc.html'] +html_sidebars['index'] = ['globaltoc.html', 'localtoc.html'] # The name of an image file (relative to this directory) to place at the top # of the sidebar. @@ -122,7 +115,7 @@ # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = '' +html_favicon = '_static/logo_icon.ico' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -135,6 +128,8 @@ # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' +# Prefixes that are ignored for sorting the Python module index +modindex_common_prefix = ["specreduce."] # -- Options for LaTeX output ------------------------------------------------- @@ -154,19 +149,6 @@ # -- Options for the edit_on_github extension --------------------------------- -if setup_cfg.get('edit_on_github').lower() == 'true': - - extensions += ['sphinx_astropy.ext.edit_on_github'] - - edit_on_github_project = setup_cfg['github_project'] - edit_on_github_branch = "main" - - edit_on_github_source_root = "" - edit_on_github_doc_root = "docs" - -# -- Resolving issue number to links in changelog ----------------------------- -github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project']) - # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- # nitpicky = True @@ -199,3 +181,11 @@ # dtype, target = line.split(None, 1) # target = target.strip() # nitpick_ignore.append((dtype, six.u(target))) + +# -- Options for linkcheck output ------------------------------------------- +linkcheck_retry = 5 +linkcheck_ignore = [ + "https://www.aanda.org/", +] +linkcheck_timeout = 180 +linkcheck_anchors = False diff --git a/pyproject.toml b/pyproject.toml index c83a0e56..7d46af60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,110 @@ -[build-system] +[project] +name = "specreduce" +dynamic = ["version"] +authors = [ + { name = "Astropy Specreduce contributors", email = "astropy-dev@googlegroups.com" } +] +license = {file = "licenses/LICENSE.rst"} +description = "Astropy coordinated package for Spectroscopic Reductions" +readme = "README.rst" +requires-python = ">=3.8" +dependencies = [ + "numpy", + "astropy", + "scipy", + "specutils>=1.9.1", +] + +[project.optional-dependencies] +test = [ + "pytest-astropy", + "photutils", +] +docs = [ + "sphinx-astropy", + "matplotlib", + "photutils", + "synphot", +] +all = [ + "matplotlib", + "photutils", + "synphot", +] + +[project.urls] +Homepage = "http://astropy.org/" +Repository = "https://github.com/astropy/specreduce.git" +Documentation = "https://specreduce.readthedocs.io/" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages] +find = {} # Scanning implicit namespaces is active by default +[tool.setuptools.package-data] +"specreduce.tests" = ["data/*.fits"] + +[tool.setuptools_scm] +version_file = "specreduce/version.py" + +[build-system] requires = ["setuptools", "setuptools_scm", ] - build-backend = 'setuptools.build_meta' [tool.pytest.ini_options] +minversion = 7.0 +testpaths = [ + "specreduce", + "docs", +] +astropy_header = true +doctest_plus = "enabled" +text_file_format = "rst" +addopts = [ + "--color=yes", + "--doctest-rst", +] +xfail_strict = true +filterwarnings = [ + "error", + "ignore:numpy\\.ufunc size changed:RuntimeWarning", + "ignore:numpy\\.ndarray size changed:RuntimeWarning", + "ignore:Can\\'t import specreduce_data package", + # Python 3.12 warning from dateutil imported by matplotlib + "ignore:.*utcfromtimestamp:DeprecationWarning", +] + +[tool.coverage] + + [tool.coverage.run] + omit = [ + "specreduce/_astropy_init*", + "specreduce/conftest.py", + "specreduce/tests/*", + "specreduce/version*", + "*/specreduce/_astropy_init*", + "*/specreduce/conftest.py", + "*/specreduce/tests/*", + "*/specreduce/version*", + ] -filterwarnings = ["ignore::DeprecationWarning:datetime",] + [tool.coverage.report] + exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + # Don't complain about packages we have installed + "except ImportError", + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", + "raise NotImplementedError", + # Don't complain about script hooks + "'def main(.*):'", + # Ignore branches that don't pertain to this version of Python + "pragma: py{ignore_python_version}", + # Don't complain about IPython completion helper + "def _ipython_key_completions_", + ] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3f0fd7d5..00000000 --- a/setup.cfg +++ /dev/null @@ -1,107 +0,0 @@ -[metadata] -name = specreduce -author = Astropy Specreduce contributors -author_email = astropy-dev@googlegroups.com -license = BSD 3-Clause -license_file = licenses/LICENSE.rst -url = http://astropy.org/ -description = Astropy affiliated package for Spectroscopic Reductions -long_description = file: README.rst -long_description_content_type = text/x-rst -edit_on_github = False -github_project = astropy/specreduce - -[options] -zip_safe = False -packages = find: -python_requires = >=3.8 -setup_requires = setuptools_scm -install_requires = - astropy - specutils>=1.9.1 - synphot - matplotlib - photutils - pyparsing - -[options.entry_points] - -[options.extras_require] -test = - pytest-astropy -docs = - sphinx-astropy - -[options.package_data] -specreduce = - datasets/* - datasets/line_lists/* - datasets/line_lists/NIST/* - datasets/line_lists/iraf/* - datasets/line_lists/iraf/air/* - datasets/line_lists/iraf/vacuum/* - datasets/onedstds/* - datasets/onedstds/blackbody/* - datasets/onedstds/bstdscal/* - datasets/onedstds/ctio/* - datasets/onedstds/ctiocal/* - datasets/onedstds/ctionewcal/* - datasets/onedstds/iidscal/* - datasets/onedstds/irscal/* - datasets/onedstds/oke1990/* - datasets/onedstds/redcal/* - datasets/onedstds/spec16cal/* - datasets/onedstds/spec50cal/* - datasets/onedstds/spechayescal/* - datasets/onedstds/snfactory/* - datasets/onedstds/gemini/* - datasets/onedstds/eso/* - datasets/onedstds/eso/Xshooter/* - datasets/onedstds/eso/ctiostan/* - datasets/onedstds/eso/hststan/* - datasets/onedstds/eso/okestan/* - datasets/onedstds/eso/wdstan/* - -[tool:pytest] -testpaths = "specreduce" "docs" -astropy_header = true -doctest_plus = enabled -text_file_format = rst -addopts = --doctest-rst -filterwarnings = - error - ignore:Can\'t import specreduce_data package: - ignore:numpy.ndarray size changed: - -[coverage:run] -omit = - specreduce/_astropy_init* - specreduce/conftest.py - specreduce/*setup_package* - specreduce/tests/* - specreduce/*/tests/* - specreduce/extern/* - specreduce/version* - */specreduce/_astropy_init* - */specreduce/conftest.py - */specreduce/*setup_package* - */specreduce/tests/* - */specreduce/*/tests/* - */specreduce/extern/* - */specreduce/version* - -[coverage:report] -exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - # Don't complain about packages we have installed - except ImportError - # Don't complain if tests don't hit assertions - raise AssertionError - raise NotImplementedError - # Don't complain about script hooks - def main\(.*\): - # Ignore branches that don't pertain to this version of Python - pragma: py{ignore_python_version} - # Don't complain about IPython completion helper - def _ipython_key_completions_ diff --git a/setup.py b/setup.py deleted file mode 100755 index 7d32a44d..00000000 --- a/setup.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# Licensed under a 3-clause BSD style license - see LICENSE.rst - -# NOTE: The configuration for the package, including the name, version, and -# other information are set in the setup.cfg file. - -import os -import sys - -from setuptools import setup - - -# First provide helpful messages if contributors try and run legacy commands -# for tests or docs. - -TEST_HELP = """ -Note: running tests is no longer done using 'python setup.py test'. Instead -you will need to run: - - tox -e test - -If you don't already have tox installed, you can install it with: - - pip install tox - -If you only want to run part of the test suite, you can also use pytest -directly with:: - - pip install -e .[test] - pytest - -For more information, see: - - http://docs.astropy.org/en/latest/development/testguide.html#running-tests -""" - -if 'test' in sys.argv: - print(TEST_HELP) - sys.exit(1) - -DOCS_HELP = """ -Note: building the documentation is no longer done using -'python setup.py build_docs'. Instead you will need to run: - - tox -e build_docs - -If you don't already have tox installed, you can install it with: - - pip install tox - -You can also build the documentation with Sphinx directly using:: - - pip install -e .[docs] - cd docs - make html - -For more information, see: - - http://docs.astropy.org/en/latest/install.html#builddocs -""" - -if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv: - print(DOCS_HELP) - sys.exit(1) - -setup(use_scm_version={'write_to': os.path.join('specreduce', 'version.py')}) diff --git a/specreduce/background.py b/specreduce/background.py index 526f46c6..77c6a980 100644 --- a/specreduce/background.py +++ b/specreduce/background.py @@ -4,9 +4,9 @@ from dataclasses import dataclass, field import numpy as np +from astropy import units as u from astropy.nddata import NDData from astropy.utils.decorators import deprecated_attribute -from astropy import units as u from specutils import Spectrum1D from specreduce.core import _ImageParser diff --git a/specreduce/calibration_data.py b/specreduce/calibration_data.py index 339feb89..f90a4a46 100644 --- a/specreduce/calibration_data.py +++ b/specreduce/calibration_data.py @@ -6,13 +6,11 @@ import warnings import numpy as np - -import astropy.units as u +from astropy import units as u from astropy.table import Table, vstack, QTable from astropy.utils.data import download_file from astropy.utils.exceptions import AstropyUserWarning -import synphot from specutils import Spectrum1D from specutils.utils.wcs_utils import vac_to_air @@ -111,7 +109,7 @@ def get_reference_file_path( cache : bool (default: False) Set whether file is cached if file is downloaded. - repo_url : str (default: https://raw.githubusercontent.com/astropy/specreduce-data) + repo_url : str Base repository URL for the reference data. repo_branch : str (default: main) @@ -298,6 +296,8 @@ def load_MAST_calspec(filename, cache=True, show_progress=False): If ``remote`` is True, the spectrum will be downloaded from MAST. Set ``remote`` to False to load a local file. + .. note:: This function requires ``synphot`` to be installed separately. + Parameters ---------- filename : str @@ -333,8 +333,10 @@ def load_MAST_calspec(filename, cache=True, show_progress=False): if file_path is None: return None else: + import synphot _, wave, flux = synphot.specio.read_fits_spec(file_path) + # DEV: pllim does not think this is necessary at all but whatever. # the calspec data stores flux in synphot's FLAM units. convert to flux units # supported directly by astropy.units. mJy is chosen since it's the JWST # standard and can easily be converted to/from AB magnitudes. @@ -524,7 +526,7 @@ class AtmosphericTransmission(AtmosphericExtinction): two columns, wavelength and transmission (unscaled dimensionless). If this isn't provided, a model is built from a pre-calculated table of values from 0.9 to 5.6 microns. The values were generated by the ATRAN model, - https://atran.arc.nasa.gov/cgi-bin/atran/atran.cgi (Lord, S. D., 1992, NASA + https://ntrs.nasa.gov/citations/19930010877 (Lord, S. D., 1992, NASA Technical Memorandum 103957). The extinction is given as a linear transmission fraction at an airmass of 1 and 1 mm of precipitable water. diff --git a/specreduce/conftest.py b/specreduce/conftest.py index 559241af..4bbe96f5 100644 --- a/specreduce/conftest.py +++ b/specreduce/conftest.py @@ -3,18 +3,87 @@ # get picked up when running the tests inside an interpreter using # packagename.test -import astropy.units as u import numpy as np import pytest -from specutils import Spectrum1D +from astropy import units as u +from astropy.io import fits +from astropy.nddata import CCDData, NDData, VarianceUncertainty +from astropy.utils.data import get_pkg_data_filename +from specutils import Spectrum1D, SpectralAxis try: - from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS # noqa: E501 + from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS ASTROPY_HEADER = True except ImportError: ASTROPY_HEADER = False +# Test image is comprised of 30 rows with 10 columns each. Row content +# is row index itself. This makes it easy to predict what should be the +# value extracted from a region centered at any arbitrary Y position. +def _mk_test_data(imgtype, nrows=30, ncols=10): + image_ones = np.ones(shape=(nrows, ncols)) + image = image_ones.copy() + for j in range(nrows): + image[j, ::] *= j + if imgtype == "raw": + pass # no extra processing + elif imgtype == "ccddata": + image = CCDData(image, unit=u.Jy) + else: # spectrum + flux = image * u.DN + uncert = VarianceUncertainty(image_ones) + if imgtype == "spec_no_axis": + image = Spectrum1D(flux, uncertainty=uncert) + else: # "spec" + image = Spectrum1D(flux, spectral_axis=np.arange(ncols) * u.um, uncertainty=uncert) + return image + + +@pytest.fixture +def mk_test_img_raw(): + return _mk_test_data("raw") + + +@pytest.fixture +def mk_test_img(): + return _mk_test_data("ccddata") + + +@pytest.fixture +def mk_test_spec_no_spectral_axis(): + return _mk_test_data("spec_no_axis") + + +@pytest.fixture +def mk_test_spec_with_spectral_axis(): + return _mk_test_data("spec") + + +# Test data file already transposed like this: +# fn = download_file('https://stsci.box.com/shared/static/exnkul627fcuhy5akf2gswytud5tazmw.fits', cache=True) # noqa: E501 +# img = fits.getdata(fn).T +@pytest.fixture +def all_images(): + np.random.seed(7) + + filename = get_pkg_data_filename( + "data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits", package="specreduce.tests") + img = fits.getdata(filename) + flux = img * (u.MJy / u.sr) + sax = SpectralAxis(np.linspace(14.377, 3.677, flux.shape[-1]) * u.um) + unc = VarianceUncertainty(np.random.rand(*flux.shape)) + + all_images = {} + all_images['arr'] = img + all_images['s1d'] = Spectrum1D(flux, spectral_axis=sax, uncertainty=unc) + all_images['s1d_pix'] = Spectrum1D(flux, uncertainty=unc) + all_images['ccd'] = CCDData(img, uncertainty=unc, unit=flux.unit) + all_images['ndd'] = NDData(img, uncertainty=unc, unit=flux.unit) + all_images['qnt'] = img * flux.unit + return all_images + + @pytest.fixture def spec1d(): np.random.seed(7) @@ -53,9 +122,13 @@ def pytest_configure(config): config.option.astropy_header = True # Customize the following lines to add/remove entries from the list of - # packages for which version numbers are displayed when running the tests. # noqa: E501 + # packages for which version numbers are displayed when running the tests. PYTEST_HEADER_MODULES.pop('Pandas', None) - PYTEST_HEADER_MODULES['scikit-image'] = 'skimage' + PYTEST_HEADER_MODULES.pop('h5py', None) + PYTEST_HEADER_MODULES['astropy'] = 'astropy' + PYTEST_HEADER_MODULES['specutils'] = 'specutils' + PYTEST_HEADER_MODULES['photutils'] = 'photutils' + PYTEST_HEADER_MODULES['synphot'] = 'synphot' - from . import __version__ + from specreduce import __version__ TESTED_VERSIONS["specreduce"] = __version__ diff --git a/specreduce/core.py b/specreduce/core.py index 69fe01ca..a6ed2a29 100644 --- a/specreduce/core.py +++ b/specreduce/core.py @@ -1,11 +1,11 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst import inspect -import numpy as np +from dataclasses import dataclass +import numpy as np from astropy import units as u from astropy.nddata import VarianceUncertainty -from dataclasses import dataclass from specutils import Spectrum1D __all__ = ['SpecreduceOperation'] diff --git a/specreduce/extract.py b/specreduce/extract.py index 07b74c7e..cabe856f 100644 --- a/specreduce/extract.py +++ b/specreduce/extract.py @@ -4,15 +4,15 @@ from dataclasses import dataclass, field import numpy as np - from astropy import units as u from astropy.modeling import Model, models, fitting from astropy.nddata import NDData, VarianceUncertainty +from scipy.integrate import trapezoid from scipy.interpolate import RectBivariateSpline +from specutils import Spectrum1D from specreduce.core import SpecreduceOperation from specreduce.tracing import Trace, FlatTrace -from specutils import Spectrum1D __all__ = ['BoxcarExtract', 'HorneExtract', 'OptimalExtract'] @@ -795,7 +795,7 @@ def __call__(self, image=None, trace_object=None, else: # interpolated_profile fitted_col = interp_spatial_prof(col_pix, xd_pixels) kernel_vals.append(fitted_col) - norms.append(np.trapz(fitted_col, dx=1)[0]) + norms.append(trapezoid(fitted_col, dx=1)[0]) # transform fit-specific information kernel_vals = np.vstack(kernel_vals).T diff --git a/specreduce/fluxcal.py b/specreduce/fluxcal.py index 02d98fd9..aee181fb 100644 --- a/specreduce/fluxcal.py +++ b/specreduce/fluxcal.py @@ -1,16 +1,12 @@ import os import numpy as np - -import matplotlib.pyplot as plt - +from astropy import units as u from astropy.constants import c as cc from astropy.table import Table -import astropy.units as u - from scipy.interpolate import UnivariateSpline - from specutils import Spectrum1D + from specreduce.core import SpecreduceOperation @@ -151,8 +147,8 @@ def onedstd(self, stdstar): subdirectory and file name. For example: - >>> standard_sensfunc(obj_wave, obj_flux, stdstar='spec50cal/bd284211.dat', \ - mode='spline') # doctest: +SKIP + + >>> standard_sensfunc(obj_wave, obj_flux, stdstar='spec50cal/bd284211.dat', mode='spline') # doctest: +SKIP If no std is supplied, or an improper path is given, raises a ValueError. @@ -160,7 +156,7 @@ def onedstd(self, stdstar): ------- standard: astropy.talbe.Table A table with the onedstd data. - """ + """ # noqa: E501 std_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'datasets', 'onedstds') @@ -198,6 +194,7 @@ def standard_sensfunc(self, standard, mode='linear', polydeg=9, (Default is 9.) display : bool, optional If True, plot the sensfunc. (Default is False.) + This requires ``matplotlib`` to be installed. badlines : array-like list A list of values (lines) to mask-out of when generating sensfunc. @@ -260,6 +257,7 @@ def standard_sensfunc(self, standard, mode='linear', polydeg=9, sensfunc_spec = Spectrum1D(spectral_axis=obj_wave, flux=sensfunc_out) if display is True: + import matplotlib.pyplot as plt plt.figure() plt.plot(obj_wave, obj_flux * sensfunc_out, c="C0", label="Observed x sensfunc", alpha=0.5) diff --git a/specreduce/table_utils.py b/specreduce/table_utils.py index 118bc552..76fa5346 100644 --- a/specreduce/table_utils.py +++ b/specreduce/table_utils.py @@ -1,8 +1,9 @@ -"""Utility functions to parse main NIST table. -""" +"""Utility functions to parse main NIST table.""" -from astropy.table import Table, vstack import numpy as np +from astropy.table import Table, vstack + +__all__ = [] def sort_table_by_element(table, elem_list): diff --git a/specreduce/tests/data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits b/specreduce/tests/data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits new file mode 100644 index 00000000..5a7fb248 Binary files /dev/null and b/specreduce/tests/data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits differ diff --git a/specreduce/tests/test_background.py b/specreduce/tests/test_background.py index 6302facd..11095d59 100644 --- a/specreduce/tests/test_background.py +++ b/specreduce/tests/test_background.py @@ -1,29 +1,16 @@ -import pytest import numpy as np - -import astropy.units as u -from astropy.nddata import VarianceUncertainty +import pytest from specutils import Spectrum1D from specreduce.background import Background from specreduce.tracing import FlatTrace, ArrayTrace -# NOTE: same test image as in test_extract.py -# Test image is comprised of 30 rows with 10 columns each. Row content -# is row index itself. This makes it easy to predict what should be the -# value extracted from a region centered at any arbitrary Y position. -img = np.ones(shape=(30, 10)) -for j in range(img.shape[0]): - img[j, ::] *= j -image = Spectrum1D(img * u.DN, - uncertainty=VarianceUncertainty(np.ones_like(img))) -image_um = Spectrum1D(image.flux, - spectral_axis=np.arange(image.data.shape[1]) * u.um, - uncertainty=VarianceUncertainty(np.ones_like(image.data))) - - -def test_background(): +def test_background(mk_test_img_raw, mk_test_spec_no_spectral_axis, + mk_test_spec_with_spectral_axis): + img = mk_test_img_raw + image = mk_test_spec_no_spectral_axis + image_um = mk_test_spec_with_spectral_axis # # Try combinations of extraction center, and even/odd # extraction aperture sizes. @@ -92,7 +79,9 @@ def test_background(): assert np.isnan(bg.sub_spectrum().flux).sum() == 0 -def test_warnings_errors(): +def test_warnings_errors(mk_test_spec_no_spectral_axis): + image = mk_test_spec_no_spectral_axis + # image.shape (30, 10) with pytest.warns(match="background window extends beyond image boundaries"): Background.two_sided(image, 25, 4, width=3) diff --git a/specreduce/tests/test_extinction.py b/specreduce/tests/test_extinction.py index 000587c4..53b6b1a0 100644 --- a/specreduce/tests/test_extinction.py +++ b/specreduce/tests/test_extinction.py @@ -1,11 +1,9 @@ -import pytest - import numpy as np - -import astropy.units as u +import pytest +from astropy import units as u from astropy.utils.exceptions import AstropyUserWarning -from ..calibration_data import ( +from specreduce.calibration_data import ( AtmosphericExtinction, AtmosphericTransmission, SUPPORTED_EXTINCTION_MODELS diff --git a/specreduce/tests/test_extract.py b/specreduce/tests/test_extract.py index da3d4c88..4465a1bf 100644 --- a/specreduce/tests/test_extract.py +++ b/specreduce/tests/test_extract.py @@ -1,9 +1,8 @@ import numpy as np import pytest - -import astropy.units as u +from astropy import units as u from astropy.modeling import models -from astropy.nddata import CCDData, VarianceUncertainty, UnknownUncertainty +from astropy.nddata import VarianceUncertainty, UnknownUncertainty from astropy.tests.helper import assert_quantity_allclose from specreduce.extract import ( @@ -12,20 +11,6 @@ from specreduce.tracing import FlatTrace, ArrayTrace -# Test image is comprised of 30 rows with 10 columns each. Row content -# is row index itself. This makes it easy to predict what should be the -# value extracted from a region centered at any arbitrary Y position. - - -@pytest.fixture -def mk_test_img(nrows=30, ncols=10): - image = np.ones(shape=(nrows, ncols)) - for j in range(image.shape[0]): - image[j, ::] *= j - image = CCDData(image, unit=u.Jy) - return image - - def add_gaussian_source(image, amps=2, stddevs=2, means=None): """ Modify `image.data` to add a horizontal spectrum across the image. diff --git a/specreduce/tests/test_get_reference_file_path.py b/specreduce/tests/test_get_reference_file_path.py index bea0d823..d619f2c5 100644 --- a/specreduce/tests/test_get_reference_file_path.py +++ b/specreduce/tests/test_get_reference_file_path.py @@ -1,6 +1,6 @@ import pytest -from ..calibration_data import get_reference_file_path, get_pypeit_data_path +from specreduce.calibration_data import get_reference_file_path, get_pypeit_data_path @pytest.mark.remote_data diff --git a/specreduce/tests/test_image_parsing.py b/specreduce/tests/test_image_parsing.py index 8f83210f..39765f37 100644 --- a/specreduce/tests/test_image_parsing.py +++ b/specreduce/tests/test_image_parsing.py @@ -1,42 +1,18 @@ import numpy as np - from astropy import units as u -from astropy.io import fits -from astropy.nddata import CCDData, NDData, VarianceUncertainty -from astropy.utils.data import download_file +from specutils import Spectrum1D from specreduce.extract import HorneExtract from specreduce.tracing import FlatTrace -from specutils import Spectrum1D, SpectralAxis - - -# fetch test image -fn = download_file('https://stsci.box.com/shared/static/exnkul627fcuhy5akf2gswytud5tazmw.fits', - cache=True) - -# duplicate image in all accepted formats -# (one Spectrum1D variant has a physical spectral axis; the other is in pixels) -img = fits.getdata(fn).T -flux = img * u.MJy / u.sr -sax = SpectralAxis(np.linspace(14.377, 3.677, flux.shape[-1]) * u.um) -unc = VarianceUncertainty(np.random.rand(*flux.shape)) - -all_images = {} -all_images['arr'] = img -all_images['s1d'] = Spectrum1D(flux, spectral_axis=sax, uncertainty=unc) -all_images['s1d_pix'] = Spectrum1D(flux, uncertainty=unc) -all_images['ccd'] = CCDData(img, uncertainty=unc, unit=flux.unit) -all_images['ndd'] = NDData(img, uncertainty=unc, unit=flux.unit) -all_images['qnt'] = img * flux.unit - -# save default values used for spectral axis and uncertainty when they are not -# available from the image object or provided by the user -sax_def = np.arange(img.shape[1]) * u.pix -unc_def = np.ones_like(img) # (for use inside tests) -def compare_images(key, collection, compare='s1d'): +def compare_images(all_images, key, collection, compare='s1d'): + # save default values used for spectral axis and uncertainty when they are not + # available from the image object or provided by the user + unc_def = np.ones_like(all_images['arr']) + sax_def = np.arange(unc_def.shape[1]) * u.pix + # was input converted to Spectrum1D? assert isinstance(collection[key], Spectrum1D), (f"image '{key}' not " "of type Spectrum1D") @@ -71,16 +47,19 @@ def compare_images(key, collection, compare='s1d'): # test consistency of general image parser results -def test_parse_general(): +def test_parse_general(all_images): all_images_parsed = {k: FlatTrace._parse_image(object, im) for k, im in all_images.items()} - for key in all_images_parsed.keys(): - compare_images(key, all_images_parsed) + compare_images(all_images, key, all_images_parsed) # use verified general image parser results to check HorneExtract's image parser -def test_parse_horne(): +def test_parse_horne(all_images): + # save default values used for uncertainty when it is + # available from the image object or provided by the user + unc_def = np.ones_like(all_images['arr']) + # HorneExtract's parser is more stringent than the general one, hence the # separate test. Given proper inputs, both should produce the same results. images_collection = {k: {} for k in all_images.keys()} @@ -102,4 +81,4 @@ def test_parse_horne(): col[key] = HorneExtract._parse_image(object, img, **defaults) - compare_images(key, col, compare='general') + compare_images(all_images, key, col, compare='general') diff --git a/specreduce/tests/test_linelists.py b/specreduce/tests/test_linelists.py index 61e1ca45..70b89f66 100644 --- a/specreduce/tests/test_linelists.py +++ b/specreduce/tests/test_linelists.py @@ -1,6 +1,6 @@ import pytest -from ..calibration_data import load_pypeit_calibration_lines +from specreduce.calibration_data import load_pypeit_calibration_lines @pytest.mark.remote_data @@ -10,16 +10,15 @@ def test_pypeit_single(): """ line_tab = load_pypeit_calibration_lines('HeI', cache=True, show_progress=False) assert line_tab is not None - if line_tab is not None: - assert "HeI" in line_tab['ion'] - assert sorted(list(line_tab.columns)) == [ - 'Instr', - 'NIST', - 'Source', - 'amplitude', - 'ion', - 'wave' - ] + assert "HeI" in line_tab['ion'] + assert sorted(list(line_tab.columns)) == [ + 'Instr', + 'NIST', + 'Source', + 'amplitude', + 'ion', + 'wave' + ] @pytest.mark.remote_data @@ -29,9 +28,8 @@ def test_pypeit_list(): """ line_tab = load_pypeit_calibration_lines(['HeI', 'NeI'], cache=True, show_progress=False) assert line_tab is not None - if line_tab is not None: - assert "HeI" in line_tab['ion'] - assert "NeI" in line_tab['ion'] + assert "HeI" in line_tab['ion'] + assert "NeI" in line_tab['ion'] @pytest.mark.remote_data @@ -39,10 +37,9 @@ def test_pypeit_empty(): """ Test to make sure None is returned if an empty list is passed. """ - with pytest.warns() as record: + with pytest.warns(UserWarning, match='No calibration lines'): line_tab = load_pypeit_calibration_lines([], cache=True, show_progress=False) - assert line_tab is None - assert 'No calibration lines' in record[0].message.args[0] + assert line_tab is None @pytest.mark.remote_data @@ -50,10 +47,8 @@ def test_pypeit_input_validation(): """ Check that bad inputs for ``pypeit`` linelists raise the appropriate warnings and exceptions """ - with pytest.raises(ValueError, match=r'.*Invalid calibration lamps specification.*'): - _ = load_pypeit_calibration_lines({}, cache=True, show_progress=False) + with pytest.raises(ValueError, match='.*Invalid calibration lamps specification.*'): + load_pypeit_calibration_lines({}, cache=True, show_progress=False) - with pytest.warns() as record: - _ = load_pypeit_calibration_lines(['HeI', 'ArIII'], cache=True, show_progress=False) - if not record: - pytest.fails("Expected warning about nonexistant linelist for ArIII.") + with pytest.warns(UserWarning, match="ArIII not in the list of supported calibration line lists"): # noqa: E501 + load_pypeit_calibration_lines(['HeI', 'ArIII'], cache=True, show_progress=False) diff --git a/specreduce/tests/test_specphot_stds.py b/specreduce/tests/test_specphot_stds.py index 868315ba..ea153264 100644 --- a/specreduce/tests/test_specphot_stds.py +++ b/specreduce/tests/test_specphot_stds.py @@ -1,9 +1,6 @@ import pytest -from ..calibration_data import ( - load_MAST_calspec, - load_onedstds -) +from specreduce.calibration_data import load_MAST_calspec, load_onedstds @pytest.mark.remote_data diff --git a/specreduce/tests/test_synth_data.py b/specreduce/tests/test_synth_data.py index 276be972..a90efe15 100644 --- a/specreduce/tests/test_synth_data.py +++ b/specreduce/tests/test_synth_data.py @@ -1,10 +1,10 @@ import pytest - -from specreduce.utils.synth_data import make_2d_trace_image, make_2d_arc_image, make_2d_spec_image -from astropy.nddata import CCDData +from astropy import units as u from astropy.modeling import models +from astropy.nddata import CCDData from astropy.wcs import WCS -import astropy.units as u + +from specreduce.utils.synth_data import make_2d_trace_image, make_2d_arc_image, make_2d_spec_image def test_make_2d_trace_image(): diff --git a/specreduce/tests/test_tracing.py b/specreduce/tests/test_tracing.py index a609677b..63bcabfe 100644 --- a/specreduce/tests/test_tracing.py +++ b/specreduce/tests/test_tracing.py @@ -1,7 +1,7 @@ import numpy as np import pytest - from astropy.modeling import models + from specreduce.utils.synth_data import make_2d_trace_image from specreduce.tracing import Trace, FlatTrace, ArrayTrace, FitTrace diff --git a/specreduce/tests/test_wavelength_calibration.py b/specreduce/tests/test_wavelength_calibration.py index 7accf9dd..539e2ea3 100644 --- a/specreduce/tests/test_wavelength_calibration.py +++ b/specreduce/tests/test_wavelength_calibration.py @@ -1,12 +1,11 @@ -from numpy.testing import assert_allclose import numpy as np import pytest - -from astropy.table import QTable -import astropy.units as u -from astropy.modeling.models import Polynomial1D +from astropy import units as u from astropy.modeling.fitting import LinearLSQFitter +from astropy.modeling.models import Polynomial1D +from astropy.table import QTable from astropy.tests.helper import assert_quantity_allclose +from numpy.testing import assert_allclose from specreduce import WavelengthCalibration1D diff --git a/specreduce/tracing.py b/specreduce/tracing.py index eadbbf37..a508dd20 100644 --- a/specreduce/tracing.py +++ b/specreduce/tracing.py @@ -1,14 +1,14 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst +import warnings from copy import deepcopy from dataclasses import dataclass, field -import warnings +import numpy as np from astropy.modeling import Model, fitting, models from astropy.nddata import NDData from astropy.stats import gaussian_sigma_to_fwhm from astropy.utils.decorators import deprecated -import numpy as np from specreduce.core import _ImageParser diff --git a/specreduce/utils/synth_data.py b/specreduce/utils/synth_data.py index cf5b73fd..06dc199e 100644 --- a/specreduce/utils/synth_data.py +++ b/specreduce/utils/synth_data.py @@ -1,14 +1,11 @@ # Licensed under a 3-clause BSD style license - see ../../licenses/LICENSE.rst import numpy as np - -from photutils.datasets import apply_poisson_noise - -import astropy.units as u +from astropy import units as u from astropy.modeling import models from astropy.nddata import CCDData -from astropy.wcs import WCS from astropy.stats import gaussian_fwhm_to_sigma +from astropy.wcs import WCS from specreduce.calibration_data import load_pypeit_calibration_lines @@ -54,7 +51,7 @@ def make_2d_trace_image( Power index of the source's Moffat profile. Use small number here to emulate extended source. add_noise : bool (default=True) - If True, add Poisson noise to the image + If True, add Poisson noise to the image; requires ``photutils`` to be installed. Returns ------- ccd_im : `~astropy.nddata.CCDData` @@ -76,6 +73,7 @@ def make_2d_trace_image( z = background + profile(trace) if add_noise: + from photutils.datasets import apply_poisson_noise trace_image = apply_poisson_noise(z) else: trace_image = z @@ -135,7 +133,7 @@ def make_2d_arc_image( The tilt function to apply along the cross-dispersion axis to simulate tilted or curved emission lines. add_noise : bool (default=True) - If True, add Poisson noise to the image + If True, add Poisson noise to the image; requires ``photutils`` to be installed. Returns ------- @@ -315,6 +313,7 @@ def make_2d_arc_image( z += line_mod(yy) if add_noise: + from photutils.datasets import apply_poisson_noise arc_image = apply_poisson_noise(z) else: arc_image = z @@ -387,7 +386,7 @@ def make_2d_spec_image( Power index of the source's Moffat profile. Use small number here to emulate extended source. add_noise : bool (default=True) - If True, add Poisson noise to the image + If True, add Poisson noise to the image; requires ``photutils`` to be installed. """ arc_image = make_2d_arc_image( nx=nx, @@ -419,6 +418,7 @@ def make_2d_spec_image( spec_image = arc_image.data + trace_image.data + background if add_noise: + from photutils.datasets import apply_poisson_noise spec_image = apply_poisson_noise(spec_image) ccd_im = CCDData(spec_image, unit=u.count, wcs=arc_image.wcs) diff --git a/specreduce/wavelength_calibration.py b/specreduce/wavelength_calibration.py index 43696c92..e52ec206 100644 --- a/specreduce/wavelength_calibration.py +++ b/specreduce/wavelength_calibration.py @@ -1,14 +1,14 @@ -from astropy.modeling.models import Linear1D +from functools import cached_property + +import numpy as np +from astropy import units as u from astropy.modeling.fitting import LMLSQFitter, LinearLSQFitter +from astropy.modeling.models import Linear1D from astropy.table import QTable, hstack -import astropy.units as u -from functools import cached_property -from gwcs import wcs from gwcs import coordinate_frames as cf -import numpy as np +from gwcs import wcs from specutils import Spectrum1D - __all__ = ['WavelengthCalibration1D'] diff --git a/tox.ini b/tox.ini index 29a45ded..e4798867 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,16 @@ [tox] envlist = - py{310,311,312}-test{,-devdeps,-predeps}{,-cov} - build_docs + py{38,39,310,311,312}-test{,-alldeps}{,-oldestdeps,-devdeps,-predeps}{,-cov} + linkcheck codestyle -requires = - setuptools - pip >= 19.3.1 -isolated_build = true [testenv] # Pass through the following environment variables which may be needed for the CI -passenv = HOME,WINDIR,LC_ALL,LC_CTYPE,CC,CI +passenv = HOME,WINDIR,CI setenv = - devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - py312: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple + devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple # Run the tests in a temporary directory to make sure that we don't import # this package from the source tree @@ -31,56 +26,50 @@ changedir = .tmp/{envname} # description = run tests - devdeps: with the latest developer version of key dependencies + alldeps: with optional dependencies oldestdeps: with the oldest supported version of key dependencies - cov: enable remote data and measure test coverage + devdeps: with the latest developer version of key dependencies + predeps: with pre-releases of key dependencies + cov: with test coverage # The following provides some specific pinnings for key packages deps = devdeps: numpy>=0.0.dev0 devdeps: scipy>=0.0.dev0 + devdeps: pyerfa>=0.0.dev0 devdeps: astropy>=0.0.dev0 devdeps: git+https://github.com/astropy/specutils.git#egg=specutils devdeps: git+https://github.com/astropy/photutils.git#egg=photutils + devdeps: git+https://github.com/spacetelescope/synphot_refactor.git#egg=synphot - oldestdeps: numpy==1.22.4 - oldestdeps: astropy==5.1 - oldestdeps: scipy==1.8.0 - oldestdeps: matplotlib==3.5 - oldestdeps: photutils==1.0.0 - oldestdeps: specutils==1.9.1 - - # Currently need dev astropy with python 3.12 as well - py312: astropy>=0.0.dev0 + oldestdeps: numpy==1.22.* + oldestdeps: astropy==5.1.* + oldestdeps: scipy==1.8.* + oldestdeps: matplotlib==3.5.* + oldestdeps: photutils==1.0.* + oldestdeps: specutils==1.9.* # The following indicates which extras_require from setup.cfg will be installed extras = - test: test - build_docs: docs + test + alldeps: all + +install_command = + !devdeps: python -I -m pip install + # Force dev dependency with C-extension (synphot) to also build with numpy-dev + devdeps: python -I -m pip install -v --pre commands = - # Force numpy-dev after matplotlib downgrades it (https://github.com/matplotlib/matplotlib/issues/26847) - devdeps: python -m pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - # Maybe we also have to do this for scipy? - devdeps: python -m pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple scipy pip freeze !cov: pytest --pyargs specreduce {toxinidir}/docs {posargs} - cov: pytest --pyargs specreduce {toxinidir}/docs --cov specreduce --cov-config={toxinidir}/setup.cfg --remote-data {posargs} + cov: pytest --pyargs specreduce {toxinidir}/docs --cov specreduce --cov-config={toxinidir}/pyproject.toml {posargs} cov: coverage xml -o {toxinidir}/coverage.xml pip_pre = predeps: true !predeps: false -[testenv:build_docs] -changedir = docs -description = invoke sphinx-build to build the HTML docs -extras = docs -commands = - pip freeze - sphinx-build -W -b html . _build/html - [testenv:linkcheck] changedir = docs description = check the links in the HTML docs @@ -92,6 +81,6 @@ commands = [testenv:codestyle] skip_install = true changedir = . -description = check code style, e.g. with flake8 +description = check code style, e.g., with flake8 deps = flake8 -commands = flake8 specreduce --count --max-line-length=100 +commands = flake8 specreduce --count