././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741790933.7294972 specreduce-1.5.1/0000755000175100001660000000000014764317326013260 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.flake80000644000175100001660000000003714764317313014427 0ustar00runnerdocker[flake8] max-line-length = 100 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.714497 specreduce-1.5.1/.github/0000755000175100001660000000000014764317326014620 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.github/dependabot.yml0000644000175100001660000000111714764317313017444 0ustar00runnerdocker# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" # See documentation for possible values directory: ".github/workflows" # Location of package manifests schedule: interval: "monthly" groups: actions: patterns: - "*" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.715497 specreduce-1.5.1/.github/workflows/0000755000175100001660000000000014764317326016655 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.github/workflows/cron-tests.yml0000644000175100001660000000212214764317313021472 0ustar00runnerdocker# GitHub Actions workflow for testing and continuous integration. # # This file performs testing using tox and tox.ini to define and configure the test environments. 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' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: 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@8c0fde6f7e926df6ed7057255d29afa9c1ad5320 # v1.16.0 with: submodules: false coverage: '' envs: | - name: Check URLs in docs linux: linkcheck - name: Python 3.12 on Linux with pre-releases linux: py312-test-alldeps-predeps toxargs: -v ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.github/workflows/publish-to-pypi.yml0000644000175100001660000000113514764317313022441 0ustar00runnerdockername: Release on: pull_request: push: tags: - '*' jobs: publish: uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@8c0fde6f7e926df6ed7057255d29afa9c1ad5320 # v1.16.0 # NOTE: Uncomment "if" if you do not want this to run for every PR. # if: ((github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || contains(github.event.pull_request.labels.*.name, 'Build wheels')) with: test_extras: test test_command: pytest $GITHUB_WORKSPACE/specreduce/tests secrets: pypi_token: ${{ secrets.PYPI_API_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.github/workflows/tox-tests.yml0000644000175100001660000000266314764317313021355 0ustar00runnerdocker# GitHub Actions workflow for testing and continuous integration. # # This file performs testing using tox and tox.ini to define and configure the test environments. name: Python Tests on: push: branches: - main tags: - '*' pull_request: schedule: # run every Monday at 6am UTC - cron: '0 6 * * 1' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@8c0fde6f7e926df6ed7057255d29afa9c1ad5320 # v1.16.0 secrets: CODECOV_TOKEN: ${{ secrets.CODECOV }} with: submodules: false coverage: '' envs: | - name: Codestyle linux: codestyle - name: Python 3.10 on Linux with oldest supported dependencies linux: py310-test-oldestdeps toxargs: -v - name: Python 3.11 on Windows with minimal dependencies windows: py311-test toxargs: -v - name: Python 3.11 on OSX with minimal dependencies macos: py311-test toxargs: -v - name: Python 3.12 on Linux with all dependencies, remote data, and coverage linux: py312-test-alldeps-cov coverage: codecov toxargs: -v posargs: --remote-data=any - name: (Allowed Failure) Python 3.13 on Linux with dev dependencies linux: py313-test-devdeps toxargs: -v ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.gitignore0000644000175100001660000000133714764317313015250 0ustar00runnerdocker# Compiled files *.py[cod] *.a *.o *.so __pycache__ # Ignore .c files by default to avoid including generated code. If you want to # add a non-generated .c extension, use `git add -f filename.c`. *.c # Other generated files */version.py */cython_version.py htmlcov .coverage MANIFEST .ipynb_checkpoints # Sphinx docs/api docs/_build # Eclipse editor project files .project .pydevproject .settings .vscode/ # Pycharm editor project files .idea # Floobits project files .floo .flooignore # Packages/installer info .eggs/ *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg distribute-*.tar.gz # Other .cache .tox .*.sw[op] *~ .project .pydevproject .settings pip-wheel-metadata/ # Mac OSX .DS_Store ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/.readthedocs.yaml0000644000175100001660000000045014764317313016502 0ustar00runnerdockerversion: 2 build: os: ubuntu-22.04 apt_packages: - graphviz tools: python: "3.13" sphinx: builder: html configuration: docs/conf.py fail_on_warning: true python: install: - method: pip path: . extra_requirements: - docs - all formats: [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/CHANGES.rst0000644000175100001660000001556314764317313015070 0ustar00runnerdocker1.5.1 (unreleased) ------------------ New Features ^^^^^^^^^^^^ API Changes ^^^^^^^^^^^ Bug Fixes ^^^^^^^^^ Other changes ^^^^^^^^^^^^^ 1.5.1 (2024-03-08) ------------------ Bug Fixes ^^^^^^^^^ - Changed Horne extraction to behave as before when using an interpolated spatial profile and not explicitly setting `bkgrd_prof` to `None`. The changed default behavior in 1.5.0 caused problems in codes using specreduce. 1.5.0 (2024-03-06) ------------------ New Features ^^^^^^^^^^^^ - Added the ``mask_treatment`` parameter to Background, Trace, and Boxcar Extract operations to handle non-finite data and boolean masks. Available options are ``apply``, ``ignore``, ``propagate``, ``zero_fill``, ``nan_fill``, ``apply_mask_only``, or ``apply_nan_only``. [#216, #254] - Modified ``background.Background.bgk_spectrum`` to allow the user to select the statistic used for background estimation between ``median`` or ``average``. [#253] - Modified ``extract.BoxcarExtract`` to ignore non-finite pixels when ``mask_treatment`` is set to ``apply``; otherwise, non-finite values are propagated. Boxcar extraction is now carried out as a weighed sum over the window. When no non-finite values are present, the extracted spectra remain unchanged from the previous behaviour. API Changes ^^^^^^^^^^^ Bug Fixes ^^^^^^^^^ - Fixed Astropy v7.0 incompatibility bug in ``tracing.FitTrace``: changed to use ``astropy.modeling.fitting.DogBoxLSQFitter`` when fitting a Gaussian peak model instead of ``astropy.modeling.fitting.LevMarLSQFitter`` that may be deprecated in the future. Also changed to use ``fitting.LMLSQFitter`` instead of ``fitting.LevMarLSQFitter`` when fitting a generic nonlinear trace model. [#229] Other changes ^^^^^^^^^^^^^ - Changed ``tracing.FitTrace`` to use ``astropy.modeling.fitting.LinearLSQFitter`` if the trace model is linear. 1.4.1 (2024-06-20) ------------------ Bug Fixes ^^^^^^^^^ - Fix bug where Background one sided / two sided was not correctly assigning units to data. [#221] 1.4.0 (2024-05-29) ------------------ 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] - Added a function to measure a cross-dispersion profile. A profile can be obtained at a single pixel/wavelength, or an average profile can be obtained from a range/set of wavelengths. [#214] API Changes ^^^^^^^^^^^ - 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] - Backgrounds using median statistic properly ignore zero-weighted pixels. [#159] - HorneExtract now accepts 'None' as a vaild option for ``bkgrd_prof``. [#171] - Fix in FitTrace to set fully-masked column bin peaks to NaN. Previously, for peak_method='max' these were set to 0.0, and for peak_method='centroid' they were set to the number of rows in the image, biasing the final fit to all bin peaks. Previously for Gaussian, the entire fit failed. [#205, #206] - Fixed input of `traces` in `Background`. Added a condition to 'FlatTrace' that trace position must be a positive number. [#211] 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) ------------------ New Features ^^^^^^^^^^^^ - The new FitTrace class (see "API Changes" below) introduces the ability to take a polynomial trace of an image [#128] API Changes ^^^^^^^^^^^ - Renamed KosmosTrace as FitTrace, a conglomerate class for traces that are fit to images instead of predetermined [#128] - The default number of bins for FitTrace is now its associated image's number of dispersion pixels instead of 20. Its default peak_method is now 'max' [#128] - All operations now accept Spectrum1D and Quantity-type images. All accepted image types are now processed internally as Spectrum1D objects [#144, #154] - All operations' ``image`` attributes are now coerced Spectrum1D objects [#144, #154] - HorneExtract can now handle non-flat traces [#148] Bug Fixes ^^^^^^^^^ - Fixed passing a single ``Trace`` object to ``Background`` [#146] - Moved away from creating image masks with numpy's ``mask_invalid()`` function after change to upstream API. This will make specreduce be compatible with numpy 1.24 or later. [#155] 1.2.0 (2022-10-04) ------------------ New Features ^^^^^^^^^^^^ - ``Background`` has new methods for exposing the 1D spectrum of the background or background-subtracted regions [#143] Bug Fixes ^^^^^^^^^ - Improved errors/warnings when background region extends beyond bounds of image [#127] - Fixed boxcar weighting bug that often resulted in peak pixels having weight above 1 and erroneously triggered overlapping background errors [#125] - Fixed boxcar weighting to handle zero width and edge of image cases [#141] 1.1.0 (2022-08-18) ------------------ New Features ^^^^^^^^^^^^ - ``peak_method`` as an optional argument to ``KosmosTrace`` [#115] API Changes ^^^^^^^^^^^ - ``HorneExtract`` no longer requires ``mask`` and ``unit`` arguments [#105] - ``BoxcarExtract`` and ``HorneExtract`` now accept parameters (and require the image and trace) at initialization, and allow overriding any input parameters when calling [#117] Bug Fixes ^^^^^^^^^ - Corrected the default mask created in ``HorneExtract``/``OptimalExtract`` when a user doesn't specify one and gives their image as a numpy array [#118] 1.0.0 (2022-03-29) ------------------ New Features ^^^^^^^^^^^^ - Added ``Trace`` classes - Added basic synthetic data routines - Added ``BoxcarExtract`` - Added ``HorneExtract``, a.k.a. ``OptimalExtract`` - Added basic ``Background`` subtraction Bug Fixes ^^^^^^^^^ - Update ``codecov-action`` to ``v2`` - Change default branch from ``master`` to ``main`` - Test fixes; bump CI to python 3.8 and 3.9 and deprecate support for 3.7 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/CITATION.cff0000755000175100001660000000245114764317313015153 0ustar00runnerdocker# This CITATION.cff file was generated with cffinit. # Visit https://bit.ly/cffinit to generate yours today! cff-version: 1.2.0 title: Specreduce message: >- If you use Specreduce for work/research presented in a publication (whether directly, or as a dependency to another package), please cite the Zenodo DOI for the appropriate version of Specreduce. type: software authors: - given-names: Timothy family-names: Pickering email: te.pickering@gmail.com affiliation: MMT Observatory orcid: 'https://orcid.org/0000-0002-9427-5448' - given-names: Kyle family-names: Conroy affiliation: STScI orcid: 'https://orcid.org/0000-0002-5442-8550' - given-names: O. Justin family-names: Otor affiliation: Space Telescope Science Institute orcid: 'https://orcid.org/0000-0002-4679-5692' - given-names: Erik family-names: Tollerud email: erik.tollerud@gmail.com affiliation: Space Telescope Science Institute orcid: 'https://orcid.org/0000-0002-9599-310X' - given-names: Clare family-names: Shanahan email: cshanahan@stsci.edu affiliation: Space Telescope Science Institute orcid: 'https://orcid.org/0009-0008-4112-7418X' identifiers: - type: doi value: 10.5281/zenodo.6608788 description: 'Version 1.0.0: First Official Release' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/MANIFEST.in0000644000175100001660000000027214764317313015013 0ustar00runnerdockerinclude README.rst include CHANGES.rst include pyproject.toml recursive-include docs * recursive-include licenses * prune notebook_sandbox prune build prune docs/_build prune docs/api ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741790933.7294972 specreduce-1.5.1/PKG-INFO0000644000175100001660000000747114764317326014366 0ustar00runnerdockerMetadata-Version: 2.2 Name: specreduce Version: 1.5.1 Summary: Astropy coordinated package for Spectroscopic Reductions Author-email: Astropy Specreduce contributors License: Copyright (c) 2017, Astropy-specreduce Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Project-URL: Homepage, http://astropy.org/ Project-URL: Repository, https://github.com/astropy/specreduce.git Project-URL: Documentation, https://specreduce.readthedocs.io/ Requires-Python: >=3.10 Description-Content-Type: text/x-rst Requires-Dist: numpy Requires-Dist: astropy Requires-Dist: scipy Requires-Dist: specutils>=1.9.1 Requires-Dist: gwcs Provides-Extra: test Requires-Dist: pytest-astropy; extra == "test" Requires-Dist: photutils; extra == "test" Requires-Dist: tox; extra == "test" Provides-Extra: docs Requires-Dist: sphinx-astropy; extra == "docs" Requires-Dist: matplotlib; extra == "docs" Requires-Dist: photutils; extra == "docs" Requires-Dist: synphot; extra == "docs" Provides-Extra: all Requires-Dist: matplotlib; extra == "all" Requires-Dist: photutils; extra == "all" Requires-Dist: synphot; extra == "all" Specreduce ========== .. 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/ :alt: Documentation Status .. 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/ :alt: Powered by Astropy 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 ------- Specreduce is licensed under a 3-clause BSD style license. Please see the licenses/LICENSE.rst file. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/README.rst0000644000175100001660000000220414764317313014741 0ustar00runnerdockerSpecreduce ========== .. 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/ :alt: Documentation Status .. 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/ :alt: Powered by Astropy 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 ------- Specreduce is licensed under a 3-clause BSD style license. Please see the licenses/LICENSE.rst file. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/conftest.py0000644000175100001660000000776614764317313015473 0ustar00runnerdocker# This file is used to configure the behavior of pytest import numpy as np import pytest 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 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) flux = np.random.random(50)*u.Jy sa = np.arange(0, 50)*u.pix spec = Spectrum1D(flux, spectral_axis=sa) return spec @pytest.fixture def spec1d_with_emission_line(): np.random.seed(7) sa = np.arange(0, 200)*u.pix flux = (np.random.randn(200) + 10*np.exp(-0.01*((sa.value-130)**2)) + sa.value/100) * u.Jy spec = Spectrum1D(flux, spectral_axis=sa) return spec @pytest.fixture def spec1d_with_absorption_line(): np.random.seed(7) sa = np.arange(0, 200)*u.pix flux = (np.random.randn(200) - 10*np.exp(-0.01*((sa.value-130)**2)) + sa.value/100) * u.Jy spec = Spectrum1D(flux, spectral_axis=sa) return spec 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__ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.717497 specreduce-1.5.1/docs/0000755000175100001660000000000014764317326014210 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/Makefile0000644000175100001660000001074514764317313015653 0ustar00runnerdocker# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest #This is needed with git because git doesn't create a dir if it's empty $(shell [ -d "_static" ] || mkdir -p _static) help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf $(BUILDDIR) -rm -rf api -rm -rf generated html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Astropy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Astropy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Astropy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Astropy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: @echo "Run 'python setup.py test' in the root directory to run doctests " \ @echo "in the documentation." ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.718497 specreduce-1.5.1/docs/_static/0000755000175100001660000000000014764317326015636 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/_static/logo_icon.ico0000644000175100001660000001047614764317313020306 0ustar00runnerdocker  (( @   ߿߾Oضn}ڷsٵpڹx׳iZ۶s*Uյo7Գiٺ|ʍtypɆ͏uڷsٶpݽŒ#̙fUͰg׷mݻm׹xؽұjѮ[ǚհbضrߺn̍ݼֱgֵcU yϴrϴoԺ~бiؾӰb׹{ضs‹ݿ}ڹzܽtUθ~ԢѺѻѺƘеp׾ؾؽǗœڿϒ‹Ȥ@ĬcԺjι~Ī^ī`ĩ\ʱoй5”Լ˫[ͮcԹ}ӷwϯcҳmѯcȇپϟ8 êd3Įcd̺bìeªaȲr͹OǭbϯƩS˯iҺѸ{̯fˬ]ίfջ‚бhع|!Sư][Ƕ{X[Vïkdzsìgϰ5=ū`ζy̳rȬaƝ˯gжwзwԴdήe㴇̿s²r²s̾±ròt±qɹʹ˜Ɲí_Ưgʵw͸z̩Էк}϶xăӼgHĵrmpȼnpmƷĴvҷƶ}cƳtŰoϿȴr͸zѾ˴tͶzĀθz}TtVOSvPTNiUDOбijyFƴx]ƱmDzrXë_¨ZϺҿ(U[UYyVYTl`1dŠ?Ĵy°pʹűq^d_ćưnULPJOrKOJ^nԽ dpֿoõ}bV[Uȶz¬c7.cmfhfhgm™ǤT{sN|Şϱбrorm̾ijthvnfifijcպa{obsѴźmprnʽǸ{m⟟@>NFBGmCGI*8F_U l?BDRSLt[U\:M!IKNqKNNCSaho¡)NWYSxa]ZnCǛACj>D4dĦHUkSHOHqWRObroliqU̱ekvKy5ӻmupüz¹yujKHnGA "YOat=$6YNu]WUTftXcF;QeBAF;AbZ{fbbƩPBE?Tg?DCͳ.Sƨd׻I'_*91J`.?.=K8>8L¤˯ŧЯɮqRHHWqy\c_m|^e_}v`g`{mEOGrYJ2UєSH[iDMHZlGPGicJRJhjSZTwd\UYf}0၎*HX{%5~,G])9*YQ.;.XV1=4eL)^@Iy+ՐN[y+9}1I^~.3ZX5A7kM@O^w(|6y-G\z+9|+YQ/;/XV2=8brUfl`c\ky[b[wq]d^vu`ifh1@v7v1s)H]r%z6u'VNx+~8y*UU099@f3fu#IUs'~6y%YPz)8}*QUsY@@u00x1bnUKr$v1w/g@,././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/_static/logo_icon.png0000644000175100001660000017126614764317313020325 0ustar00runnerdockerPNG  IHDR9JgAMA a cHRMz&u0`:pQ<eXIfMM*JR(iZ,,K pHYs.#.#x?vYiTXtXML:com.adobe.xmp 1 ^@IDATx ]E?~{/BJBXPpTEg>? ("8aOD23ttVe\q6q]pA@VYd [^u>]]]}-IN'VuշUݷwZmkgvhgvhgvhgvhg`Bg3d 3l֭:t3\u9Ӧufm*fGͽiud<4Ӟ{_<>.3 @aH8`;vg,m޼KSR.EYfYJx˚MlŽ?~ƢS<^' ;Nѻ}Ϳ׭{$m7#[ߐ303p2mc V :+{`EWa7zb(fݮ1WLdsf1$\IFQTMYPpžPm --Enw{0 ƶ8SVwVsbyp@"[ь܊`(v\74R{O喢ۻ6ouԋ"];Q;E-xusGf> ͹su6/ włmN'g z_;F򞛋^:ser]gsy-Wby30,jg`}_24\hw+7~MZᱝ"e30A3_V m2Boyh9(`9AiΆ!*d+ xt]^N_ݑonONB23?SĶv;wCMל_a6d C>5V3u~|< ߵ7o_mT ]`og`Kp;z9t2ϻ>1|̍mp/ĿK}޽*>ޓ NsX2Q(B&omCđw]S>5ONO hm71qsfXu{zךlm!6 [!M5 fybKg7 D@aLl>]LsG'ÁW0Vxtg O- ^Pghh8q \(~|~$Uy c' uۯy O=}Ʊsg} 68ņٰGxt'&Jd Q4|։'/7>0Ǥ!7]pykG{_kҎ\&[bA'ȉ \XN'B)NK|橪U?8cj7xNZ't;EooBl55)'j_bMEJQ9A]rnf1' urІ3 W~sod ,NnF௶X"뗭Pά']I٘'BQR&V<3͕ɽnfǕ+wʺ|@ahqU2\uhCx\\l55('6f L13E$ˣ"E@;u͛|駆ɺuqPm{ n9*iGuo' 2[slg~cH~=;YūjD'ґeS$< xWeުݻt_pwg `Nw-5X'pH:jpb@k%E$kͣ2{i({2XZ2i/3?;6>g=+׿we{kt&$P>֪ $[rJАʫ'D,wEј" 5G*f:lQ]g_ mn?v?-?6Ãa4|xtgrNVnL2 HM֚GUjIKA*EU](ןj6h_AwO~yrY|dnSd |C-irwWbp9+:9A@O7Z'qr@d+roeG3j:5Prn?_wtۘA w9>7RdO ApDP N @HL~9yZLrlAꞔLO&nnl|Ѻ#7w[por۠n;ݺCCo3/aͼ_dQNH?aS j9@bUe{k ͢^qz'[0x#o{vGu;2l(sC6wMC#.'NYH 'bI.%)̆J /3 sqS:lݖ5=wu7e>tw!^zCÇ6xnV9֜/"Q#u"$o ^i| ODr9™JEctxεm8p6 3zO9/5Q\9", 'i˻ړs}}8"*Qj$Pco'HchQVvRi\CqkZHqpq횋>:)mqn%5<cw?uOO`[l 8IӛL\kW@BA*i9q zPzM G ηӫ$Bnnt^rXTrcpH0eP|f =.ZO`;톱?M_}iWw_z5f}qQZPx<9Zx^m0Y>- EG%kp:MA JkJOLj1pcNTYtXQ0ZمCE祽Or9kkVa\mg_̛5#C#;HCM.,Eْ9D*=bYK6m-?VNUbS"oc( t?geWP VIctE/(V?' c} 38l4i/E~~gN=5o_׾1UqQ g9oY47(G͛99mPpQ'%$O/F͚] u|YHJ*QمQIxyG ǘ•܊YK W.G7=iC=gN2#?):娝/i(lώn*MZrP<&c*4>9gGan)|~Iu$(.ŌQ,&_bS!3sW'rr{z?ICÖ"JR*nfk^5ooo={F?a GRylT84 VQsPQ Å$Poe9MM/~=ڧ#VSv ?ߥS09@fgXw Ũ#5ƠShY6ߝ/^˜RG}1 C#ך<UDL*uay졅z QTEJ!ju%%-nñG;/f͉!H5衒5m3@TC^GŠҨߩcCG-ĔHsG }S盯8OP HGdtՅS%DG&hhuNO[AsھQN*Vd ђx.I(CUN. Y靡NY_oy c*< 55çxZ4nH?P76-S:"?~z/SpW2/1q< 7yR*fٓI4*!(U+(rs"e GƐkD;<͂@?d< =cdd1W^2N?TWk&.h NKOг3 +Jm(*> 'Tߔ&-Wlk0ʹe+puuŏsGͫ Xӕ*;d+qg>|׿&pWaL Oo]bz4 qsL&V jr{8#lobNz@Ϲ˯'XăRBL/3fR>O}hsW^ED0㡯Dp!BqtLG\~ƌ70{} (iots4D}S<JcDr9(_faG¼aւ*f•1p$!*`BPYD1L1b xP8S̃T/۠ڏ}l'<}}ټ=7ȥRkĦElid+螔KʯA6%w9=%Z[ 3EÅIJ Wa:דe:Tx?u%qA*dywRddzs6gMl|-]]p~ˏԮJ+R<|(P'nA Dq)f{S-9=sh }n{4VGC)IW iGM!Dǫeҟc)yL8GuрƁ0Ć2;.qe69ZaLμ?5CϙeKjکD()pkOt\Q0WE܂PR5bӓpGB ʃ]>@1h4"9JDzD;@(GGe p cg},3!%_xw )JShAYyK"4(9U% gyŠ:5l~n /7Y0<܏G5`t j\JJgܒGOh'aQ.5h ԣ B] nt3G|蒗i7ۀYpo眉u0BGpxEWqEE=e HLe&MʜֈҲE%jbp3@PjD;Ix"T0Ntx4ZԽ#jZAigTx4w|R5/ٰa&JaLL7b2UZˑd+D랔煃&)1y^W-T&9@"UEfyY4ud:Tx?R & wSwLUg0qvלwΣ#Ly(d+ND'*"SR=AK0U̺GKd|*CG*fIu *kfq Yp5 Ӹ1i=`.Hje \]E(%)fA)kDi/*!'5VR+ 5CcIJO֠@6kՅp7q𒐓,<7imrpq=V)Џ+bAp׊6+Δtrܟj77tq p3-\na$#sW,xuTE;\ŠJCp'$&KDю+c@K')4|y<z]h@Y@J  4p|'B<PB)+-ΐ /򲃸}mcן^8.FVXMLV=F%OӀW@DRÐP5)*\u(%#$Tcaȡ贗%Pʟo̥WdluARFLT7g]2K{1yŦmej0b}2^gT| 30uO ުjB c^bQaΏ(CSHe8*b+/7A!#h5&hOz+ý9h5&֩3Ǭ-8VUUZ5xy>!%IZU4Cz ƢK W>UxZ:=kA&DqBX2@DP R1M"RyQSզ6أY:;.ZD #cCoASJoU5ΐ` A(G,VJؐDHe8*bW1gB A`BJJ*KgdC1X_뽢0dթ2b̈ţndAq7"|8CԵq8*B@x_6tm0L歳5 D .Tͧ2)B+QFR3J1@:9 ɝ'5E->Po+db[^[+[kF%7O1С;x-2g |љ+;gPxxBQ f{$ cq4R GQ,*0hv È4y-TZSC1TW]xPnu,a-;d+q/V❙A_o0ќ{dMVEZp"PCä]=@}|JvQW|M;^a(S撳nKU&4lGJbxMx3J<0;I);d+T{xU $qIxlT[?! zUgEWxZF<'Ś vvY>j#"6ZPH)ĠJ%W!.Jax3Fq~$%7OޜV*^HaCe^(6x 7ۙYI\_arc*3c6WhXXjÕ']ycEAYg/{WWl^nPnl oJbx>?G1ky-<36fcbKY"(`kH,ü8q!kUM+?6x8+jhEf357TL> &ƋAQSǠ[)+<@?d<:3Us?۬bEd 2Hss 78<$8pk"us'%EF~PYA?xC gekx^-w8+Xfv+ 3a_t[ Ns:jtI!x1]b1Nh>H[1ItDLt\ܺSE[񁞇sC@Qvxl#[Β^ l jϔ[EgV!V^7gϏ7L,uXsYsÿo`iF)`-֊DHu ޚ')OZ 5PŬw 6OR"輱"cekZXEڋ(')6wpI%}qiM. Y?8-v7!F ^E);RI(rE/)1LaDm r'K+ LxE/N;;̞ki\?VrPg=qŲt)-V/C%/>!k^^nsvGu/\yF l7O"*Eޘ5 /9w2NkԘ!+JL=}xXXYӵ-{J,*Zu\ұ+< .|* WY]0c˂A}i)BЛ޸ioȴUnUOoz-7όn64m(*\u(ede^^<ϼ[9w~u፣b #5t]2!?l؞ٴ[~^u/[1",< 3F'9s?:5"sn8^0D?ފ]+3sLx d|f9%!Ie)y=_vzM}C׏SRV3d`JTxX&5/[ uu:#`=c%Éc#'Js]"9DBq(-{(ċ* !gy,6ƥPYFdf,~̲5UpίgU#3fZ#0"Õ3Wt,֭k4)5LI󑗯?o̙5;flEo'pzXp@ A ^#5$FI]<-[1FzQb@$PaWKL|>+L̺™lUg)=enap@mV @h$4e.i N9@M$"*vpt,=fekݪ:sLrBoI&e w9Ѥ! ~4Ap`ٟ+2tQQ:e#t0O/UfَBfqgNq2>$C + Q 3A@ѡ?bl/w{%v϶0sycbGB<\U 2ni^mZb^T-3%5" jc)T~ڨsd. =)6˒`Wby{')no6GAf& ћŢ25PY:7.DT7!QBXZNVԈ3Z &,I䀁`xY}J<>VyZ 6dJzsƓ1o/&e&q$Kġ.쐭X] fN}hs#<.J5Q*k 4wqAUg ~[= Hutn2ֲ.•0D*59~j5R[SR^zqf Ln2=\gt˘kY22s'7!Kk[ҋBD s u F3l˖d+(fuMrL` :a QG1<0 J &ff]&Z5d\?4-r8w2_5s"k0xR[hpK!H,]U!#/[ 0QߜU# :cN09|8貨F(u =%'p| P<_{ }?Orآ^>CtՅ`Ԉe4ނ`P>ER(Y,?zR"SBJ/  q4^|4&ko ~_Ngcke> )g0٪P937燾m11Ӳ8n<"{TVxj+A@>i*5n\Ri#b#ɄmüqGMc8n̜{9Bp8֥1sO/V*s7ixr/Zsl00FN.ԉ_,[̆7|V l:(nyl`6=[tyh6\ggnT1/N?JzM*`00 K{*d|t(i2^PՋȲ?~͋Qљc*8s13Cu|fI^播PQo@IDAT3uQݸ]8lws[{1wtci۠f` wҸ c%}ǝhJvP7qv~qkqmAȎ2"uG^Z*EU-v ͮh(䥹NqVOz39橬gvKYn$'=PzdY- QOs1j͛~%ў\jDZ Nm~˧W62-okɒWLXIΘQ7xf%,jz1*zPDT*V9nE+yN@ܿr % wݵ{gT:6sOO"W_ُYdVRG|dad֖v ISg1Mq̭ _Бc2K2 OvdBCi=h~b3693%噲~/_oޚlqkRxGETYKSr~kq,od Q3LH7e$Z B%dIF zqK- 7K=C5oL"O[(fGnfnԅF n (#@dy 'H'isH[wcNC™٬;$yln 9tO%|PsWb~K5 ?[ef`Hq 7N 7^d!/t[N? P %n T#G;pLΛ}FLO\H3{>1Tj FH%d¼őq.뒭S< DkD|yKRZ̡A6dt>oߚS1'<-uq6_C`%Ɛ*:;/T)Q+qe9u[B3|r%n>H կ]{ U!(%ACO>eO ٛREOMtN&<oH9̓WW)͙GN/㛖Z>n*y*K_G[\e6'$k [1V4/v_S8Uڌi#+x-S){ lEyM>='&G+K .Ñ/&}lԣ˂;FC4F 4^S?yś:v\:☽%0O/9( b6,fo3F-㙧̆ǎ>k~JkR)i*:k*!H&r&_mfIsE1} p#'vjj8&H K,޽v ̛1}G^ǔ0>w.nƋiG'"(Dg9+[͟A[V4Z 궚S<۠1{Un7Ҡi({TY1.&VsgΜ>\s5_'G}?%m6f:^`/Ց7شys#WSy x Flq=/??WHEY0xݣ$̭9,\z(HrQ}YaӸG*$l⁃*jsj`n47y ۏ⦏ GXjـoݜ = E$sz( vS ya>YL/^ɍxf4yc,_p ^[XlM- 쇯أ}+)B Nhʯe2C2>$GtЁYT ^,I#}YM^tw7x Cƨ&wA;dlid+Ҥ saމT(|'?P<;\'畭Gƚs͏~tg&uXs;-n[KWjQΓzXn.\H'숗ϑ$1 n(Jfn{x_/c2K2 x8ԈA85w][?kLTJ#S=\|[Kk2$ 4=O@]uRs3=1D;T N)Ϥ1n콾02~,x#~3Cuկˏ (Ʊ5ɆL9[b]LJJ"*=.> `sV{.K:pU} T-~+C?O4ſV<"<$8"kossUH:G[D:70zf [8 0594zyРO ^d c _Bf#{<~,<jy9*3Q >ZpDkNEWҗɃ˟oLKy/Dm*qo:;<Яk<&^ qԈsF o&,a RRiCɽX WĿ3?:O4;p:oIp餳_&i\[.yjs}w8[6TF/6ccph@tD% a^&鹓a2_82[HB͖NGh F̈́|1*:LlϐJ_6ǠڑAa>osb ru e &x,e [nt5W1k5+հ'8|&b*c cH֐v澦Vu{qͭ.Vu"tz:*=w@?]Mo2`UVY'Me|$O^:FEJD1ސ3}6 `:H){j:e8W >[G3+ʳ-U+k! >M6e !<(T8ؼ+oPM `M/9_̛"V%ɦP;l/\$EWoibMl疀;]v n@ ie+pCn` zZQt  cpdT(tL94Ef De};SSkdېZ3NJ<Ǐ{ BDTY>s:"`y< *yǝy[|>5{sPd0b0OGm4Dh'Wŭ.;Apx8&I;%d8]`i cO苏0W0)\Y`y@6A*iNЫvׯCuL1rUJ)Jc|<5C?aX`x*ACޑ4Eݱo0R܃ !T" ={|4@0.|^xn땭*\p1Xk3F /(ٸ' 05cp릊 S ֯Kedz@\Dw綬 dy~t=7nm~->xl%UQs/V(fjbm'iYryW[ s8Z2أSQ+r}rDl<-"y3M-|]x`z>vr }^5=C>?fxx\=y'L]$Qzd4~6:(̒ ݀%6<$/b m4Q壦pw-ލ4W_n_%q|*[==v=5=7^P %3*҄+3QNntyj ܚ3PŹ}tʫ < xD™V1 8x_?7A{1Vd+)ŬiXk QEЂ{d q(-꘽_~; Bؿ^Xa^p7f-]]{MK[x>(Kvtzuig("WD^p! eCDUauX G"Ee +JKU zmk8ϡ{v)֚5=v|[jk^\تtOjqMꞈxu%}lr$x6 >G<@|<2r"<CAl7GBzVd`-2 uTz~<^Жo?/n<])=tHF0o;/P7jE{# !(+@8$AYWkTMi UWW #nX2%H~TBY@MW9{Uwtx>5|UEHU.+2^ Й\͎({eb`TzcuP*uu^$ZOc^נM>4DCuk1i'=[S^a4mʬͥsÇZhC} PCߥq0Ep%:AomÜ(khƐ_Ȕoٰ <+x-b{W܊ٖ$o=\:x^*"\ftjT~c\b 6 j}͛5wc{i\༡K8+W.ٹ.Ǟ0_uxZ%Fn I{)98jPR2_lehWZN6q0׀1T-8ґ`e -g+3Ue%wX':xC-}Œp$ؑcY;$X7+?t)b!02t8@?t<}\T?{3xODdx^^2 8_ϋ) (itDZcm@4)p -A + 6 塩` 1 .Xx졬c "U c\]襠ڱѪ3*.fEYC-߄r'IxOKAw}10PzQ7XT( ttP V6.+J gm87(lѷ[w6Z 7eY8Vƭ.;,zKC;+ U͉#C?tT :aD,z>͏H Qg"Aq|$5;.xG(]Z^_6y,)U5'p>{rQŇxdU0MC#7GDe:sG4@C0aSA{'/)?B&Wuco{%3vFd ź? З'ql@~#Mw'A b0$Ҟ޴SΚ⬡ bTҸpHyztJ@syF/7|(Є Q&9QS5Xv5 QӟϸR|̒L[Bţ}e3za#OKo6}w{"ҠṳNsI=kgf YjrWFҟ߽S?Q ׯ2wB!._.2/g7+>Mп?{l >Г~'!jnY2uO+6QO~^-T}Sm#O?mR ~1^{vhsxc59l6ţ҉7< li; zIGW/G /p& }TLj0w?(dn䅧=l=c _2Pp;L|eNZ*8u>LX?]>rǠ͜) f?pZAZ #Ĕd\ ^l{ 㑵Ggv/мq-Z &#05*AS鞈T+0? yw!lU!Ơq[ GzSÝCqn=hyT&Tz_?; η ?=͵ݷocluʃCWX^\}=.Hjce}M٬7C (uQHPi_z:Soc Ie+7C?p1PO<y皷VZPh}F3f~Y<2ZR9CTфVVmgX-y[kZ~1,o)=ϷZݘ/%jeC$(FT5fE csG=6+rSsa` TڄԂ:-Eb2]:GRdhx*S P2LZ HWRx~us^:נ7Gsa8i*G"^pȇ^n%|v|kԢÏaW4)vQԎ!ZVFD"M'QJnZ?{T*܂yك>r)g-0x 8(CsYlf'_OD<Z9<9/94Tc0p cc?p>{9o 5jS5Y\eJW5acWnltجd\+ e&Pp:Q 4/UǠ{0H7( 5_L:|Z(+iOmӸqi% Nmcc0[@kVBŐk3$QJV^kOh[,1o/ʃ o}\Xrc*/%\眪BAZoq@:lcKҥmБМ&V$J{2<?4ח/vWhOϦUu4=9Jrh@IWK}_]<|kDiJ_E"7nͣeQz~PMr &wAa2Em8?TOrV`@. >YGb* vQҚGe66xThNcZA5 G@=ZuoN: `]Qѣs F50^DJVLj܋ k}nBHmpnZeI&JOB:=LeF[ 00J[}\sVGYQ+p3qX"8QX$ٗhc>jKӁ H,9 =>Nxk/+C>cZNVZ_ttد0N+ rڧ\PS A:f__Uj6MA [jeM7ẊFt:C0^kh~ waqTj4=JJD~4ꂔ܊ظg=C?1 aӟ+Pg).,rB7Ic8OP ÄN-z+=+JS8a~{cʘJ+ sGB2_ m ee=ȚִfyeI&ŽޓэaWPqv6+|8ԲG1W"^"y\UjxdD8hp,|Pm+v4_AK`?f dcT|.\qͭ.Bp ystܹ2 wAHjy])l >|CժaN'-GslUN(Yxt67J/|B=RamɂG50="\"Md0z]-(GҢtx*o<_&Q+NiNԓg2d=>ޏTzQ,q5֚rEs7㎵?7IRNn 0oHΏ-;sHQu?Q {hWX0{ў'qSpW'#Y{0oq+d%^2[1QVnRi]Ԃ:㌺CY1.}|Ȁ(#3 O0cN6PzG#;JiŢsFֹr'vxJ*Hrc>*C(xf?<*_GYC}UuC*0@ &P?Fn&A|s(rht״j/ W]¤^譪P\kېJ471TxF桙gSG;o2u"-ZP]|>< ^YXrWIgv ti1N A<{YnBۗ>cnuY6!cҗF1qD(}ZPzO7f +D|5T|:ppr|RrnšQԊ(+vaFG5cJu)tcv]<[ؿVd`ea-hEzi}x#k?huh@e :g{_~y]5A'{?ꖣ8E# Ebil6 `:v{鶻Y9=^ac nc: mCX ==ғNFf}==7"#"2+[ֽy2w%T5Y~3.sOVl_$ szN9'O8#n =FqG45ו"F0V_߷e´p9 7=, ć]n!g)+4ԙke|xB)VI|S Ȏ'*f\ ۇnݯ0W:A&V(&rdCnv^!Q%2_FCsMSDK&XY&X?@Z7u%^Z2ɥ#((f="3߸B!My3ݻ @.G}OJ]j*6>X"0s_]MwP1=T,\&T͏$ڱ<'f'ιͅF<,7Mjx7 7jQ;5cͶZ/}T 3!mQ20ťX}/6*.=F +߳~[e[ǹjEĺ:^0AU'9cǤxo6f^Gũajw1:s[.q6C3g빧1ۿbKW^E-U4B^VԚ<㔞[Gj }E씕hn #4'ɚ4Ε*Ѵ9_I& =1S.Ti)dY48tAFJB!TW# ._XcҚ!mZC!\Pe;2l*[wk[|;<*zЌ.>oi; ȴ֫eNx;tU\Ōf.+ytMg@*y_orFK5Hz \n$ePƑJGnQbηv*}锖g.ЏӧxlL򪒃[I Ү|ܹGuͰ/ж' ):_IU4>ռK:=|*d ͼ}zKjuh5˦0W=:+LKTΣ6ìb]2_Yn(ɝ%Иde A_xuJ/86;SQ% }K~rEL6#ܛnr6 DwkBoԫK2;z~_lܔf{PH.u'sAVgּύ"o9U(& 22$IOFa\#г2wЭ~Zqٔ=N$ȥqIAܼJDqWy'% ȢXGB_~Hqy䷤j{Phi"Zw[FFat+X^ <=iՒyYufH] GC;^Z81rp~C>4ow'hg5ᘇj/_#%PJVG#Or]Ɠ?X7Uif2f-BʊR.)a C1h'0U|uQ'by,4ӤĉوFҭb=ey{ZnJH;`% Z0EPjEo"weťe螹RrBF;4!'K$0=Y49Xmk8AY<&Rr`4: +Q)9>2ye4qN&cX||"+ z9ecp[-a㴭~fhpo}ͻў+\w@`L;}*hsѣ*g.U`ӈJ A"=5 2ACaj gQ' wDW(E(1x.4N AATB^=<;߽|~>۔x\:SZs7Jh@IPߞ;8csYOpb"=X+`=qL@!jc2F&t81;.=%7: @ֹ_:#F%̛<ڱθɗdڵ_Whk G>#:d-ʸ+KG5!7}>k@ vDW} L:ar‘K 2"9ͦVս@/<ԯ5/A:2zQ^m_ 7&e"+KzI(BzZ`A 0xB,8riK\%AIm5DO\K:Êrog¦H^9bһ C20V-wZ"zZƔ4ԯ6;@ztt/YHxCnI ˙#OlA,* c 2b C z .8sr೥N[u%ގuY.qN1$?Úl}NǁsOQ&n: 1 u|rRE ѡ*@A&,\p}>ּwaa4JS6ìbQЭ`R'is5/& ֪3OWgݏmQ-j0)` oH\(#s?0}# Ihmzy,KziUHkd<1N(&_~weތ2TcĒ,dӭE%C\d[O ^:Hӕ1 &k@2P^e]`Gyə>Rq",k%a;-[VLb,@U 2`rr鱕i}ʿy0B\rdKzuUVVZ"K^:YYZ.EA;St. j)齽{ ˜%}0&vP'W%N]>T6G¤Ƌ.\j#=z2c{^ R F|5CP we(@@t.n8ld/MzuA!k#WQ z=lOnUu 7D]ufr:p٩Gsoc:X VI oE`PgVL?V~͔,I):-k?DI Q.FqEACD)Tc ;p3,n J"Rs D {y/+#@IDAT ɏx5  = dt#d:VxZ=D";!EﲳgFԊ eQEn".\&h;[c$gcY ӡFiop$~I 4Zmc\JEPϝWzZM=mIӧ҂g(H7}:@dH QfI^yK<}q:хM6僤1ukʢk2*%[gmYkTs;m m2' qtb|Y9B蠅0BV〃`#{ a1W/q{u,//IHқ<]?1аcM޻{@ON~a]rm9w)q;%Am&9sHFFvS?Y].+x#dDV70ttv !;À;2rW|NZͷ^pE\)cYٽckhO/awUf"̆S`S"3涳\VV$3ʘIN50vH'Z|V8ā~קy3G:rDꝰ3_o3MÅ;OIv'I'XDseiY$ Y ;1?:bCu8o]ǒ #R;ǩQJGpJkͱ2<,5.ZGY/X^u,nX!h5z/([E;cH[S%jzYjV2>)tCp$V͆90Ⲙ.Mk<5 MKєư\:K&I,eéyyȔ=nt 䂱pCWU_zVVj^59<ёWЭMh%wE=*+ c{SO~wz3 .HMounU n->>xs,:^BQ$tit G =F|w}SuL?ҳi7 q{N<1;Ajm|?ν?>|{4y^{n3 @}S.>v^h KJ;Qs]+wݦUlYe{$Gߺcr,{mVJm'}{X=t}z¸{Oʙ^T}<_rDw)瞝n}`o vܺU%Fk OH`'Ϻ3؎Nɗ* Mԏ1ObPH0 .>MA%x^Ee~.^5}rk q+Hyμӡm\ ~_oೣUj(84]wV>ί~{'1SH^{+8ɢ0s1K0ݡ[\e*1׭2}@_Tu1Rji D&}z(/.{byD|_ /Po)Bfh7~^'=9PI璳]O`oJ.HRޔo)*15%ZP -I3iw%^\M*eѫ;t+I+MGh0&M,*F 6cYF71rV=q* $71<˝<ŽMOY]'-(Y3La:lQ( F~uA gdO dIRiM5JO' x0TD*]=L&q ɱYM0c*\osV(Mt16`_t^)iH&Sn.c0F(UgqQ=矅E=t2{ }xC˖D qr3 jȶ#"p2yt I:C#;*qM<}K{hF2mľUDeaf*SzS%~L)S^D|sC^K~CjtF316SNN`LX=5$bBLSO2@|y[ Ŧ,j?쳪'ztwN2Ԃ 3`6&׭&/"bFn9"'ƍJvVw2:ezG^`Z[= ۫M?q-;ZUsMf ӡ&ѶG%ϭn4ȳ@WOTFҀW7(N[cPP?cO쯵L-ƂB^g KK-fʘYi|K~:,X @SDKoIpݵUњor j1uuTn޹ZSaW+1 K_/8 VxM?I 9tdg^']XsxBFg(e90B-B01$.!]p90B4 R%q,lScK%.@.m:m-cL3E#{׾3t0\K\$7qj5،r)$L[Kfj.~f 1KrYsx4PI1/w?+hNdD#AT7W<ɘ֙-@L$CrHMBʈ= ôl_pcj2YT7pq, j{Wį%0\@(K/N+8tnY\iZ vcc=ek1d^ZoEl~B:Ȋ}4ijb vn^=1'A2X։i[O?}iF;]ЕtXOhyIHZomJDń$[XB8L8۾?gƙ;}=^5vVj8֘iRZ{Zc* 7t/_Xl}nc3ce_99r##O z!kj7}_ӻ1S*9}X?<Pա(ۣxݪ2 "-ZR7][d ݧR|@_+yelz5uo#ct/! WI?':]q>]ଛ0ުQp6FAveǞN=F ^ p2W.?QR;jhۣqۣ֗YH1!Ր'?yk:8B=8G8Ph"F;EbAz&(т* 2w &zU%e:#Keyҵ+=~p*n+w%ݝ,r78k#=ͥ 0?1z@Bjdþ}L>kk!nW>/J ^!jry0XHTUZ}Oh3bMFm)-"oU]oFfK\à^n53X*G>5Yo;o$-ډ^,6 cgk/};v^(Jc0潢`l,'b`v 1EzqD=])V[s(2E?I,z_APÕ _WcK}qL O3oJjĸ[G0/fԅTor ,N4{*5T^qx'%~#]se]\PS!L[v0:Fo+[XͶj`F^<}~MTBo]6h(ֻ*.SRT/8&"/MSSe 9.W#=wJJ`am##ѩRz3 t-T |fr@kNtUshFDI~귛 dfK|VܷYn|BF"/վ'Ӓi,@-C#N}j_af- V4`z-G_nmj2Y!=V6S} ZlueQ6!s`}w roO~]cT6>-sN91ugΉ74 =q;[ u#]yAmGzm/?BzXWO81If==5 /'{zMf٪{YDLcIJD3TC*ũF/U;{T-q;P&<`:PDI<~x%󿷕^a'/[yr( c}zoDu}A ۣv8.Pzp[!q@UiL=\Sͣ8F_*R7mT2*RIeY%?jbttIƚ0֫ |S)qz-uYjdj:5NMFsža ,gp!$tDWmXXrgSX^&nȗտ29Mײ8*A~'iO%<=y\N:Iz@AF~,_ WZP dRhQxRϳą+wv1rBF@@tAHzZo -5>y'VGBØc̨o].tp zƓ̂#9@X{[Ip}c8ܛRpunY"xT#7;ϊwD2 4 I,PSP{ H m!syDc` 8Ѷ'81u|( _+)ԋ[ݨD'_089pY -.*^ i%e#Ϋ s#՞9'T];ԓM*ܺH${*tV-[Y,-w|l;Pm4R9N3MshŘ1U.Sn"w`dqA2ۣWpoI6?6Z7=[uKe:ʨ֭ P_FS!M#w"FGxiAQ-q.V;j,*{%M4tyOvw~PI)4їЪ )qK|Al&,#%z1vyw%GQQ_al[iô> YQ,4PW 27คO1Bp;~h:D/cV!"-yʾq^h[n;ƲZ--=1E>4৲+풳NJcS;'a!h/`w A}4%[G*_al;X@r &h8-;vĕP;]SۖLPGM |F׮%Ho].tpbUۗm[szu/:_|\wNb|v&5cz1A`σQ\,#u_T85(UY4qTL+ϗiD"iN d| xL?@y)Go/Odz$ K{0_49I s b;Eb8)*f*+M$c" G20%Ȁa=9O8iQŘi앆qZ1gȑEɈz 2iu yUn2'^F2zT%ʤ(+J[!L&cGAM!YQjj @@tt%.|u89 r*^[gK\w0ʑ!"|"Q{{s" Kpi̔%7 U_*rD޻R_7&D=aP6WܭVV9m٘7C|J3ZU^8)L85tχd= P\n!5ӕ;@,[Mgӥ|yހN5HLh^P)d@YF+A*ءݟIc+ =hLFKa:"lf;u߃X҅o?atʦ 5(fI2eI"_o89@Z& {IO-2x{\hǁxA(1R8v {',e6 hRi:.vHĄ9t+ZoAyi:I&=K%NnrfB &Qq6c~~^sޛ/ʜ>W[ctpzhHOI+_PcMse=Es#fqIO![5qȀscH JǷfnؤ1yb-ةӰ%H_lR^va twoQ{Gv hPi4@`FMl;उx.W_Xvlh+9dTjټUw9(*Z fS]dT*Mix ׄQV sp7&𷾎)҈ :- X ~MNes_ߓ)ӆTNRzxq:o]5L6>2BQzn^gw|sC+P9"\@؁2u)BzztNW "arrIcpVXRF9wc ikoB L@A&4,\&y{OOp ^5@u_ R\iU'M妸1A ip}B\_KJsDZlPd@"L y;v&Wx^ܾUzk˝00/(<=Z$B/ƃ;2F GAO X G\z7nW2"py{s,hot&yygBUrqm<'oI181@L`!E1Ae~L\qOHYU)D3ď>HEB&^XH6[\Ͷy'j'5߱`0~\2#TFK&]gK8MxQuL!!}1FgC;jBORsbBam,m]nHn\lr̍(Sɽ&QZoG**P 4emdo,3UC*dɕd~]nS2ݼG~q7|$W27!U2HZʙP$WIYR{*@ǐuە՛l0|2bӑбLOCyOVեsʉ=L*(HK%'<ga aF")Is piGPmeRG2խ9\nVf:Z﶐Rbפ (J {9Vפ{֜}:xxЄkTb>ʹ|CV+.~b$[ԐNJXgL$F۞qH$^U]=^:m e6z P ECRs纟E5~e_?%~)?Er{0xG={tyalfPԊ?u_+X7_awݳp2F}b0:Zz(i)Yz =к`rٛLxddнCzA.qsh-[;̣BzWb8Y?6KuXA G&2|1uol>SNMOnY| 3$ ӴJty,d2cOsslZ0|߸m4IѶG?xnzWt$["H PK>.ӡRhCD*yh%2Z{^y±WY[6r4Fua3 )ECĿ= Lhn%}l]d!Z3ܲgn>q=F \@/3-Kύxֿo˔g|T&=7F K,Ej*ՂOq_Oc^ 1Ez;@#tc;}RJu  [`aУf|f3]Ch;*.:'pˇ-~'1M?M<-l(X/"Su/jݡ%Ș1Tmexzd[Evhc'0OŎd@l&/省FO8wԾ{|@&|{7g?mc=8z)[ &w'Z޵#pʮ]#'l < sܹ>0dno[\v=iĝ$f +z{?u"1KGJY׿U7oo[ 0s⢬taݱ$x|pxBYq)hdz6O' S~9ov{ed:u&]LCnAO ?r$ps'}m[[ٳmr'O*8h};ٽI!kͽ{\zbS Y[ <75Ca#ÿ8I/dbj؞&l8:s*e{vWTƍM4 ݦ}6Ք@zu&sJDKJ x.&wcO?ph^/ިxne4᭪K5fZwqUO4s 7lc0%hۣ]+8>Gos]E)jZDhs/<7Fk67?uUEqq0r)'/h}Ľ ΊMMCQiw7={n ?XLw {í֑/=qt-еѕgͬcju_Vز8ziR$@B1@&2 9t=|Bc<ތ|Izj'aI2$KGuvR@E[ 1AR]FKpxE~zN7Ę\HҺq/m d`CFC*/mړ' =Q _o믜nȘC(d>JL4;>!-+ I \z'70| N'g`=>M2n˾?G.NԾ]< Ku|rdOMzgω>E,o@SmI |eFt1DP:!4mO/F'"_w~L.1瘼'#׏=:,E2Q`8֫=[Qv0E' PNj,MU `Ÿj ‘f{k_LH9syʹw?V #>N^י,΍|\QBӳz{|D,ɣj9+11Ȏo㲓rvLzj6:oK}]7*-Dc8M2ѭ6K[Q1:acm'?iހf3jȶ#"p2ytŊh.xnh HQ Kխyd֓/jx/}xw}Gl*HzlrkF鈡\Ѷg\0cM)Q;2b$R4i\?7dLgϹxp Ü'b`KBz"t3p2yTX0hH563e:~\n~]3=N!˾`2/]E2QsȘ̨r1Kx{ RR'[rB[E<[[[? }D× W>&h.R?6lE.>抍+ %T'eKz)M;(0[h#iQzni"b' $ {, NNsڧ?|>H丣RpAZ.B M@հkUcܹ Ƽ^;;9[lvC( ͭu㴸pEFsvЬF'_.; ȶ+ ?m2*w^?RK92i v\iT3)K 53oRZ{? 9[f>]+}ky~mmy' p}پC.himLǂ)%p+ݾMvP%7B*vc)GFr/.'vɤ87$OpּGfۀk,wMMvjOz,7ߎc痹ZVٛX\/ʔY7sx.oNFfp#T.>/*UPQ0e``0IDAT1M}kW$wK .&jh6?Ӈ^|e \Uu$$g坉5{F*331PGj//!|L^Z8Gˑ5 9J5"3V>`zT@Sjf9cXo ?kgE<6qo;4ǒGhl7nG}QKRr#Yq%ikI6^2޲AO_vֱV)B"roP+dV;Wf AG_˚Mz܍)ٚY @Llk2Fۯ+3Ap'C3`h[MG Bz\&$:_숹䡈J;? έ7Z1!eLnSl1EH8 _-[m)Kj<'䝪ɓô|l@AF\a)`2wn8_{2J*5^<ނ@ ]\ RsNgv7֭,L!'t#Ghp%]փpKѺ|zB:6zWo|{9adlǤ HzvJHQW.`7ANRL9c܁$#uUߒ{w HWu1K Xw5laT\Q=aK2Ѓ]wsjŎ #=qȼeai(0?B:4GvP!c`dsGt.1eUAn 25.қMn`@6\z@64U6x2WlmW+:idݜ]씐>UJ\f;Ic+[Io4{w(A$g93/I*cr. a%e_&tSXwZUet-] d n*ByWC5ÁfB:X bѶ'ۅ2z3O{˯|V;a,׻iƎ F(wKpVYAyedQHrZ%hX/i[ñ[񧟜\jmeZfWŚh9fnjխZ%&s3kFWl֜ëJцG[Xv~^{W0\r{!5N \+e41r}n젝,XFPyTuML|nnuL)GCIϏ߿șe^ۭ6t6Yȼԯ0|TUW΂q!P!# \Fgpr ` Vo?㊟󸵵}z;Ʒݰc@6 G2E3iDN"+!0۞aZk,P|L! 2o} tqx/ ooܲvK¸~8Yeb j٤cLJJ; +AP7@&Cх0@zΕ]_zOD&i 5 +,\&oy{~ ݎv d9G䩵%.|?WK|n NT^ʄG2Ѓu4stp4ԗE vDW|d{7:\b dlo֝ǭ4Gou- _vQowe=e2yTy)._tF #rB 7D>Sэp` >=sIh+4EgLXaLA'1-/\$9 ;YcPx{KTNcD4%gaBSrt;\&4>6׭yd7ſ1{r <}[;~7e>V`G)P v*+X^ɄTl;gjfb@ P/rBx[n?;f2уld'6Vf"CC&,\:/BA0@61N*^̯[~ٲGqwe2brqQTiL(n%xN.eL>Z%c@H36υN \cSU>skI򦕒4Ry{\ YedF&9ZzG\YbWGC7s[y?FqY#v <w`XQ6~@ld423U)#&wPG3Rˠj?PAwQ}t˽t[ft;yBEomõ6ߖҎ (ep62уqQ| ŭOkK|R= ssƎ<(? d+.ڋG vWK趇E9h՚oHW(,G: Bf>C|MxuyL ,=-z5}sG[dCh0h,JK F@*`]=P}itf㘗~Ac0hC\3Vy56<92 /RR\ybL3p_aP-Ɨ;ZXYIkg1j@o Z~/ yi6>]g`L_&BO94:.ɶ|Ku뽕/aEJ=6m9 ̭U 4 -ʘЭ1HQd]3rF|j>ɢ9QQ9aP6Y-|DŽNX?S;2JE\z!sK7 ڔ`Hd<))Rpb[:gb~ᚏAu󿰗]A>H#/` 3grCgS:lf;t+b,)FiNsd1N{q,~r8ʚv¸_1|?w@ʖ+v HMREȀ%VtI$O"JzaюNyn;PE0n!'8V<&^aLkZr̥ Y`2: ',]dp$̻н94\kwOv ZO`9'TYzLOפc4(6q<8PCH^ xxJD;VNK0-s^]^+Ԑ=U 9[-d\A8d¥.txJhfc7Y!հ7O;Q+X ;~zJ=l^Z>ZE#b.phR('eTi)a#vNXJ|z[:Q~;~-m7 3M<빇砦Ä\5"fÙO<9Zw#|;c+ ]o|W+v/ZQqC+צ4tDp PK /[Lht>p+"#@ vDW}Ak K>p4c3rX b%Cv]%:{usݫ<~HvOT]ܫէæ\|#Ҧ1(p\|Lzcsb . Yo+W-.2=)몳ᇰ.?]U6'|a.]\. .ZcǓnW2"qi:)uMNQl.u|̓o?gy}\[j/D *2'0&Yٺ+bT}1eI).G+Ohz!ǝjdn7&"޵@7=\+0ȉj;"+&,K6i=: |d !C8@Idxכ_}ŋ_~7,2 *9㈰ R$I 2#V+E7:\V$#S/e[_[$h~bCt#\}OU s' /V@c$2o}WYKNMZ4.9!+Ako|}nw_ꘟ};l`b0'PwDWj2U;Y2IaCP 8$9ndC黟w6Q' w~ _[%M.zNeyu6Z#syCs}v+ z+]Ȇ [=&}l9AL-A_%FK+[,w~_Ϲ㮣<̧q?q^}[tߩjFĎp%7v="9un-\v))&ܽcˍQ,)8˽S';P]vVmiou(>ťtچ2=iM2,ZtRa?㿎7cTh: 3xً=?8vvSw:aKtԪ(F}'ܑhOv' ImګHv#vީ' /ǟ6Xzp;n6Hel#5t" R{JQ.mvmOȡxG~@~d=vꊕ_g{|{('e2`RPL7W sxchp5*^Z^>飿Ht䶳LdAɣ10/1rslw??Y̙#{̟0h?xݷ?o0ѦC2]·r 9qнGr{[jn;t8]îitwx/&Zo.8N ,H4?Iov^Ÿ,><[p;}]}[H:0l!v\zJ=xI^'75÷51\cy PhUZ]A@?w?̙ϝٝOr<͝{?<;ѕx;ɻCEk..Q: G *c<xkj2z q sRD_Jc:]"Mω|SukHor#LEvMt9,c+N1Զk(6~\>;}QnGnI\zk;A`Uf̓`yj nAbA[a `h~2;տBJ37yfhrʣQXX3掸FMh Opp3kG=3j<=4썍+/I* Ri*%3o;tut[t,j rv?q*$zFzߙN#N1fQp[ H!@2V+# -k4t):i}Fp/͍R]jhSũnB RrAVN#F[Tw\t<Oc`H2w|:?b#Dʏ)B:-4MBZYZ #U:zvtk}uR4ǡ&CW8)èH Jt14ҽh4f!#4uwuƁm/N&  ' .1'T(ڈi).@9N.|4 Fhc):,}x口Dhp#-mbIv:ULNB^٠X9 RQ!i|+!IN ;)j$(LYH I]5-tԏeɇT:C[rBn@I(U Vm~IUm# eqK 'cL/mV/ \=XaUqZWQOۍX4M;.?wOuf!FLYm4 %E|&(FIu+(t&&0 M OQȻ~) nwttdkSK)i( -g^'7@$[Z{)pAG`ǺGa]8^<}yf#'hoҾ4TaA0)-RD4M)CɌiA y-cffhx}ҚPJ,쑲yUTUDtk},\Ѩ{͎@S0v :_L~W2.&B|HREvAWyz3h0Hl(iuyK5ySx|U\Uq {ᬩt|+@(l;mMkFL%S0DnR("IDWJZ&_.%WesFZ! iyjcGOw@7TA!-suWjx4_]Xuq&48LaWإqD%WYKUa1L |C7 Q)(l(9"ڕO[0?ٶ#5G` tw:q:kvښf0(Щ`NKhqȣ@d+ʐ`ȍ rQRh60 oZgW_+m 7Fi ̚bzI.zj 4XKͮKчf%_nfy@yvj&GeU5`pUI~z}0C $e:+dx{NvGݍTS.$To&NwzWu^N t#F )ƏX>]:Gp׀x.u!]>~q!ywT(MެRo[r,ll6?AGNsbA{-yTo휼 ~rCkW9k+ɆPO`vA/:YgSg7ÝS_Ə%}[]Q=9YZI ~.80i F \0*3gʲy+7~w܂{pB[ɞT^yښR ތ$f+W n|, Y Pred|.!B|ã)wuwfrApF` cy&Yyx>InE9FjFd=6<8$a}[|[9THXKo]2 X!\ @Rvw->{ڝ҅aA F.t' b8Nljwz n8TbGew}SS#cvAw1@{XT./rϗ>ui yipK: k|+kzui?n#` X8ߑÛ wa;/6F`SxP}`Z{Gq|.#P3` &W;P8:&B =8 n&)M]Zq4>\0 ! (w/{b~}C1&M |E}(dp>~rmSn[|濱fLJ/2cҎPd!!cHLV܀?nl3n```(1zlƹ0 ?3. 2 ~8'eqcvƌ4؆`F`F`F`F`F`F`F&j'‘U]IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/_static/specreduce.css0000644000175100001660000000042214764317313020464 0ustar00runnerdocker@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; } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.712497 specreduce-1.5.1/docs/_templates/0000755000175100001660000000000014764317326016345 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.718497 specreduce-1.5.1/docs/_templates/autosummary/0000755000175100001660000000000014764317326020733 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/_templates/autosummary/base.rst0000644000175100001660000000037214764317313022375 0ustar00runnerdocker{% extends "autosummary_core/base.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/_templates/autosummary/class.rst0000644000175100001660000000037314764317313022571 0ustar00runnerdocker{% extends "autosummary_core/class.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/_templates/autosummary/module.rst0000644000175100001660000000037414764317313022752 0ustar00runnerdocker{% extends "autosummary_core/module.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/api.rst0000644000175100001660000000101714764317313015506 0ustar00runnerdocker.. _api_index: API Index ========= .. automodapi:: specreduce :no-inheritance-diagram: .. automodapi:: specreduce.core :no-inheritance-diagram: .. automodapi:: specreduce.utils.synth_data :no-inheritance-diagram: .. automodapi:: specreduce.tracing :no-inheritance-diagram: .. automodapi:: specreduce.background :no-inheritance-diagram: .. automodapi:: specreduce.extract :no-inheritance-diagram: .. automodapi:: specreduce.calibration_data :no-inheritance-diagram: :include-all-objects: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/atm_transmission_secz1.5_1.6mm.dat0000644000175100001660000063672014764317313022474 0ustar00runnerdocker0.900000 0.948654 0.900500 0.942270 0.901000 0.947468 0.901500 0.946108 0.902000 0.941138 0.902500 0.944661 0.903000 0.956764 0.903500 0.978545 0.904000 0.987198 0.904500 0.985155 0.905000 0.985933 0.905500 0.983675 0.906000 0.980373 0.906500 0.967384 0.907000 0.943643 0.907500 0.936187 0.908000 0.947027 0.908500 0.948955 0.909000 0.954411 0.909500 0.960169 0.910000 0.955615 0.910500 0.950850 0.911000 0.957920 0.911500 0.968951 0.912000 0.960049 0.912500 0.962144 0.913000 0.951225 0.913500 0.932353 0.914000 0.942717 0.914500 0.969754 0.915000 0.965365 0.915500 0.932753 0.916000 0.937585 0.916500 0.969009 0.917000 0.970596 0.917500 0.948742 0.918000 0.949352 0.918500 0.965726 0.919000 0.975516 0.919500 0.976791 0.920000 0.980078 0.920500 0.988850 0.921000 0.987226 0.921500 0.981262 0.922000 0.979548 0.922500 0.985318 0.923000 0.986866 0.923500 0.984321 0.924000 0.980954 0.924500 0.978778 0.925000 0.978388 0.925500 0.982463 0.926000 0.983853 0.926500 0.984372 0.927000 0.987250 0.927500 0.977302 0.928000 0.957896 0.928500 0.950821 0.929000 0.949588 0.929500 0.936239 0.930000 0.912281 0.930500 0.895657 0.931000 0.871601 0.931500 0.836746 0.932000 0.796466 0.932500 0.784504 0.933000 0.791712 0.933500 0.778925 0.934000 0.730238 0.934500 0.689957 0.935000 0.748015 0.935500 0.756215 0.936000 0.724900 0.936500 0.771703 0.937000 0.754404 0.937500 0.751159 0.938000 0.763841 0.938500 0.779592 0.939000 0.846616 0.939500 0.916115 0.940000 0.918993 0.940500 0.902259 0.941000 0.889363 0.941500 0.881720 0.942000 0.883934 0.942500 0.846681 0.943000 0.785517 0.943500 0.814545 0.944000 0.775153 0.944500 0.749129 0.945000 0.842947 0.945500 0.847050 0.946000 0.793236 0.946500 0.813635 0.947000 0.860710 0.947500 0.867957 0.948000 0.836124 0.948500 0.831939 0.949000 0.887025 0.949500 0.837512 0.950000 0.765804 0.950500 0.804802 0.951000 0.899172 0.951500 0.898838 0.952000 0.828835 0.952500 0.814234 0.953000 0.860126 0.953500 0.894153 0.954000 0.882984 0.954500 0.849349 0.955000 0.859827 0.955500 0.865675 0.956000 0.861372 0.956500 0.858717 0.957000 0.855468 0.957500 0.904725 0.958000 0.905174 0.958500 0.880795 0.959000 0.859772 0.959500 0.853298 0.960000 0.893632 0.960500 0.912352 0.961000 0.920746 0.961500 0.934287 0.962000 0.916878 0.962500 0.900972 0.963000 0.926794 0.963500 0.938201 0.964000 0.909834 0.964500 0.906022 0.965000 0.925492 0.965500 0.953079 0.966000 0.945531 0.966500 0.926942 0.967000 0.939247 0.967500 0.963107 0.968000 0.970026 0.968500 0.969073 0.969000 0.982198 0.969500 0.985010 0.970000 0.977449 0.970500 0.973962 0.971000 0.981516 0.971500 0.977721 0.972000 0.974243 0.972500 0.973712 0.973000 0.972839 0.973500 0.963881 0.974000 0.953921 0.974500 0.955559 0.975000 0.956370 0.975500 0.954694 0.976000 0.952368 0.976500 0.960257 0.977000 0.976785 0.977500 0.983021 0.978000 0.975044 0.978500 0.970744 0.979000 0.973373 0.979500 0.970194 0.980000 0.972061 0.980500 0.976089 0.981000 0.987769 0.981500 0.991617 0.982000 0.989496 0.982500 0.985568 0.983000 0.985701 0.983500 0.991358 0.984000 0.994686 0.984500 0.993137 0.985000 0.991633 0.985500 0.994589 0.986000 0.997970 0.986500 0.998532 0.987000 0.997169 0.987500 0.996260 0.988000 0.997617 0.988500 0.999203 0.989000 0.999440 0.989500 0.998697 0.990000 0.997875 0.990500 0.998411 0.991000 0.999130 0.991500 0.999395 0.992000 0.999422 0.992500 0.998854 0.993000 0.998370 0.993500 0.999084 0.994000 0.999641 0.994500 0.999782 0.995000 0.999814 0.995500 0.999674 0.996000 0.999664 0.996500 0.999777 0.997000 0.999731 0.997500 0.999677 0.998000 0.999590 0.998500 0.999655 0.999000 0.999624 0.999500 0.999449 1.00000 0.999452 1.00050 0.999674 1.00100 0.999647 1.00150 0.998836 1.00200 0.997946 1.00250 0.998486 1.00300 0.999449 1.00350 0.999617 1.00400 0.999258 1.00450 0.999361 1.00500 0.999567 1.00550 0.999433 1.00600 0.999526 1.00650 0.999679 1.00700 0.999666 1.00750 0.999518 1.00800 0.999270 1.00850 0.999172 1.00900 0.998637 1.00950 0.998235 1.01000 0.998769 1.01050 0.999405 1.01100 0.999681 1.01150 0.999602 1.01200 0.999323 1.01250 0.999492 1.01300 0.999763 1.01350 0.999849 1.01400 0.999809 1.01450 0.999670 1.01500 0.999663 1.01550 0.999578 1.01600 0.999730 1.01650 0.999822 1.01700 0.999707 1.01750 0.999713 1.01800 0.999864 1.01850 0.999851 1.01900 0.999416 1.01950 0.999160 1.02000 0.999603 1.02050 0.999814 1.02100 0.999849 1.02150 0.999711 1.02200 0.999722 1.02250 0.999716 1.02300 0.999454 1.02350 0.999075 1.02400 0.998936 1.02450 0.999573 1.02500 0.999873 1.02550 0.999826 1.02600 0.999803 1.02650 0.999840 1.02700 0.999711 1.02750 0.999519 1.02800 0.999688 1.02850 0.999762 1.02900 0.999815 1.02950 0.999908 1.03000 0.999946 1.03050 0.999960 1.03100 0.999918 1.03150 0.999883 1.03200 0.999949 1.03250 0.999990 1.03300 0.999977 1.03350 0.999966 1.03400 0.999988 1.03450 0.999999 1.03500 0.999999 1.03550 0.999987 1.03600 0.999960 1.03650 0.999907 1.03700 0.999857 1.03750 0.999883 1.03800 0.999949 1.03850 0.999970 1.03900 0.999952 1.03950 0.999942 1.04000 0.999944 1.04050 0.999959 1.04100 0.999978 1.04150 0.999992 1.04200 0.999999 1.04250 1.000000 1.04300 1.00000 1.04350 1.00000 1.04400 1.00000 1.04450 1.00000 1.04500 1.00000 1.04550 1.00000 1.04600 1.00000 1.04650 1.00000 1.04700 1.00000 1.04750 1.000000 1.04800 0.999996 1.04850 0.999938 1.04900 0.999708 1.04950 0.999489 1.05000 0.999585 1.05050 0.999812 1.05100 0.999888 1.05150 0.999833 1.05200 0.999792 1.05250 0.999806 1.05300 0.999850 1.05350 0.999895 1.05400 0.999930 1.05450 0.999956 1.05500 0.999975 1.05550 0.999986 1.05600 0.999991 1.05650 0.999991 1.05700 0.999987 1.05750 0.999984 1.05800 0.999975 1.05850 0.999968 1.05900 0.999953 1.05950 0.999961 1.06000 0.999953 1.06050 0.999954 1.06100 0.999937 1.06150 0.999924 1.06200 0.999833 1.06250 0.999689 1.06300 0.999630 1.06350 0.999551 1.06400 0.999452 1.06450 0.999223 1.06500 0.999143 1.06550 0.999265 1.06600 0.999358 1.06650 0.999460 1.06700 0.999320 1.06750 0.998738 1.06800 0.998397 1.06850 0.998842 1.06900 0.999116 1.06950 0.999013 1.07000 0.999058 1.07050 0.999346 1.07100 0.999615 1.07150 0.999471 1.07200 0.999014 1.07250 0.998782 1.07300 0.999174 1.07350 0.999633 1.07400 0.999161 1.07450 0.997460 1.07500 0.997703 1.07550 0.999305 1.07600 0.999643 1.07650 0.999299 1.07700 0.997739 1.07750 0.995892 1.07800 0.997439 1.07850 0.998967 1.07900 0.999435 1.07950 0.998522 1.08000 0.996039 1.08050 0.995377 1.08100 0.996175 1.08150 0.996278 1.08200 0.997916 1.08250 0.999084 1.08300 0.997164 1.08350 0.993476 1.08400 0.994690 1.08450 0.996819 1.08500 0.997534 1.08550 0.994862 1.08600 0.986948 1.08650 0.986943 1.08700 0.993688 1.08750 0.996690 1.08800 0.997774 1.08850 0.996961 1.08900 0.994510 1.08950 0.993157 1.09000 0.991080 1.09050 0.989124 1.09100 0.994849 1.09150 0.998258 1.09200 0.993379 1.09250 0.980956 1.09300 0.982794 1.09350 0.992623 1.09400 0.994362 1.09450 0.989816 1.09500 0.987046 1.09550 0.986598 1.09600 0.982533 1.09650 0.989097 1.09700 0.994809 1.09750 0.988697 1.09800 0.983299 1.09850 0.984920 1.09900 0.981452 1.09950 0.972369 1.10000 0.972351 1.10050 0.977640 1.10100 0.977123 1.10150 0.977883 1.10200 0.971043 1.10250 0.968623 1.10300 0.970545 1.10350 0.975563 1.10400 0.970007 1.10450 0.963794 1.10500 0.973415 1.10550 0.971069 1.10600 0.958290 1.10650 0.956328 1.10700 0.973571 1.10750 0.975610 1.10800 0.955917 1.10850 0.945755 1.10900 0.956992 1.10950 0.969107 1.11000 0.966012 1.11050 0.939959 1.11100 0.920135 1.11150 0.925545 1.11200 0.948692 1.11250 0.933251 1.11300 0.876186 1.11350 0.851763 1.11400 0.889769 1.11450 0.910296 1.11500 0.841800 1.11550 0.817737 1.11600 0.789890 1.11650 0.686181 1.11700 0.699684 1.11750 0.783042 1.11800 0.833176 1.11850 0.788880 1.11900 0.720396 1.11950 0.728391 1.12000 0.757344 1.12050 0.779253 1.12100 0.815802 1.12150 0.748593 1.12200 0.625861 1.12250 0.579093 1.12300 0.686282 1.12350 0.703119 1.12400 0.724877 1.12450 0.818729 1.12500 0.739023 1.12550 0.575220 1.12600 0.623246 1.12650 0.750044 1.12700 0.792277 1.12750 0.721815 1.12800 0.708645 1.12850 0.743655 1.12900 0.732202 1.12950 0.691436 1.13000 0.704174 1.13050 0.828487 1.13100 0.910727 1.13150 0.912520 1.13200 0.886162 1.13250 0.842981 1.13300 0.782162 1.13350 0.667562 1.13400 0.569252 1.13450 0.425347 1.13500 0.430525 1.13550 0.638204 1.13600 0.726293 1.13650 0.826607 1.13700 0.907367 1.13750 0.875251 1.13800 0.848953 1.13850 0.836608 1.13900 0.897902 1.13950 0.908410 1.14000 0.857842 1.14050 0.844483 1.14100 0.809568 1.14150 0.820806 1.14200 0.854212 1.14250 0.892016 1.14300 0.918704 1.14350 0.869072 1.14400 0.761278 1.14450 0.729615 1.14500 0.757038 1.14550 0.768671 1.14600 0.772257 1.14650 0.719294 1.14700 0.680704 1.14750 0.730166 1.14800 0.853410 1.14850 0.870374 1.14900 0.825745 1.14950 0.776315 1.15000 0.763452 1.15050 0.841766 1.15100 0.853065 1.15150 0.852384 1.15200 0.817911 1.15250 0.766774 1.15300 0.821260 1.15350 0.864742 1.15400 0.826659 1.15450 0.847765 1.15500 0.885408 1.15550 0.868207 1.15600 0.874482 1.15650 0.913144 1.15700 0.924995 1.15750 0.900897 1.15800 0.897519 1.15850 0.915011 1.15900 0.906591 1.15950 0.918299 1.16000 0.911739 1.16050 0.907733 1.16100 0.936403 1.16150 0.949203 1.16200 0.944764 1.16250 0.948642 1.16300 0.972327 1.16350 0.969878 1.16400 0.969258 1.16450 0.974017 1.16500 0.958576 1.16550 0.945447 1.16600 0.950553 1.16650 0.961556 1.16700 0.966523 1.16750 0.973922 1.16800 0.973221 1.16850 0.972920 1.16900 0.978206 1.16950 0.980222 1.17000 0.981184 1.17050 0.976008 1.17100 0.976547 1.17150 0.982596 1.17200 0.979140 1.17250 0.974704 1.17300 0.974501 1.17350 0.958697 1.17400 0.938513 1.17450 0.958885 1.17500 0.975557 1.17550 0.975021 1.17600 0.988260 1.17650 0.994002 1.17700 0.980372 1.17750 0.949508 1.17800 0.949525 1.17850 0.972612 1.17900 0.981544 1.17950 0.969594 1.18000 0.975340 1.18050 0.987625 1.18100 0.977929 1.18150 0.941932 1.18200 0.916442 1.18250 0.949461 1.18300 0.982671 1.18350 0.986757 1.18400 0.969196 1.18450 0.951621 1.18500 0.956697 1.18550 0.978990 1.18600 0.988531 1.18650 0.985182 1.18700 0.976861 1.18750 0.954704 1.18800 0.939069 1.18850 0.951637 1.18900 0.971985 1.18950 0.982253 1.19000 0.984752 1.19050 0.980359 1.19100 0.981037 1.19150 0.985026 1.19200 0.987986 1.19250 0.991293 1.19300 0.983971 1.19350 0.974746 1.19400 0.984188 1.19450 0.988584 1.19500 0.983569 1.19550 0.980270 1.19600 0.982935 1.19650 0.987270 1.19700 0.986618 1.19750 0.978813 1.19800 0.980855 1.19850 0.971897 1.19900 0.961843 1.19950 0.974741 1.20000 0.983190 1.20050 0.981964 1.20100 0.978773 1.20150 0.973377 1.20200 0.974917 1.20250 0.985326 1.20300 0.977496 1.20350 0.944434 1.20400 0.936448 1.20450 0.961981 1.20500 0.968605 1.20550 0.981381 1.20600 0.989106 1.20650 0.982717 1.20700 0.977013 1.20750 0.976124 1.20800 0.976634 1.20850 0.978362 1.20900 0.976562 1.20950 0.977756 1.21000 0.982973 1.21050 0.989250 1.21100 0.982825 1.21150 0.975368 1.21200 0.980099 1.21250 0.985002 1.21300 0.991563 1.21350 0.991791 1.21400 0.985006 1.21450 0.981860 1.21500 0.983106 1.21550 0.988560 1.21600 0.993286 1.21650 0.990601 1.21700 0.990397 1.21750 0.993539 1.21800 0.990053 1.21850 0.986350 1.21900 0.983398 1.21950 0.984957 1.22000 0.988633 1.22050 0.991860 1.22100 0.992328 1.22150 0.986436 1.22200 0.985590 1.22250 0.987027 1.22300 0.988197 1.22350 0.988667 1.22400 0.990009 1.22450 0.993038 1.22500 0.994105 1.22550 0.994089 1.22600 0.995171 1.22650 0.993444 1.22700 0.990217 1.22750 0.992936 1.22800 0.996877 1.22850 0.997753 1.22900 0.996750 1.22950 0.995914 1.23000 0.996826 1.23050 0.998364 1.23100 0.998967 1.23150 0.998883 1.23200 0.998834 1.23250 0.997977 1.23300 0.996776 1.23350 0.997367 1.23400 0.999008 1.23450 0.999468 1.23500 0.999303 1.23550 0.999318 1.23600 0.999496 1.23650 0.999138 1.23700 0.999013 1.23750 0.999507 1.23800 0.999662 1.23850 0.999657 1.23900 0.999799 1.23950 0.999921 1.24000 0.999819 1.24050 0.999496 1.24100 0.999474 1.24150 0.999785 1.24200 0.999937 1.24250 0.999972 1.24300 0.999982 1.24350 0.999936 1.24400 0.999796 1.24450 0.999773 1.24500 0.999830 1.24550 0.999858 1.24600 0.999864 1.24650 0.999655 1.24700 0.999669 1.24750 0.999586 1.24800 0.998969 1.24850 0.999182 1.24900 0.999223 1.24950 0.998261 1.25000 0.998700 1.25050 0.998587 1.25100 0.996748 1.25150 0.997608 1.25200 0.997834 1.25250 0.994814 1.25300 0.995997 1.25350 0.997178 1.25400 0.992464 1.25450 0.993117 1.25500 0.996493 1.25550 0.991123 1.25600 0.989697 1.25650 0.995638 1.25700 0.991237 1.25750 0.985451 1.25800 0.992316 1.25850 0.990902 1.25900 0.979553 1.25950 0.981838 1.26000 0.983431 1.26050 0.968600 1.26100 0.960381 1.26150 0.963959 1.26200 0.953670 1.26250 0.937863 1.26300 0.940169 1.26350 0.939693 1.26400 0.924447 1.26450 0.920700 1.26500 0.930985 1.26550 0.928900 1.26600 0.919060 1.26650 0.929692 1.26700 0.945111 1.26750 0.945282 1.26800 0.899087 1.26850 0.784339 1.26900 0.758176 1.26950 0.853277 1.27000 0.930262 1.27050 0.957934 1.27100 0.963815 1.27150 0.964836 1.27200 0.961788 1.27250 0.961196 1.27300 0.961872 1.27350 0.967564 1.27400 0.963418 1.27450 0.966112 1.27500 0.969241 1.27550 0.976962 1.27600 0.973136 1.27650 0.973427 1.27700 0.980372 1.27750 0.984600 1.27800 0.985305 1.27850 0.982528 1.27900 0.989911 1.27950 0.992007 1.28000 0.990550 1.28050 0.989450 1.28100 0.993592 1.28150 0.997412 1.28200 0.995688 1.28250 0.993663 1.28300 0.996647 1.28350 0.998384 1.28400 0.997067 1.28450 0.994882 1.28500 0.996985 1.28550 0.999208 1.28600 0.998780 1.28650 0.997108 1.28700 0.997172 1.28750 0.998351 1.28800 0.997678 1.28850 0.995288 1.28900 0.994508 1.28950 0.996863 1.29000 0.998151 1.29050 0.998247 1.29100 0.997249 1.29150 0.994203 1.29200 0.993317 1.29250 0.995928 1.29300 0.997616 1.29350 0.997860 1.29400 0.997272 1.29450 0.997292 1.29500 0.995935 1.29550 0.993070 1.29600 0.993259 1.29650 0.993791 1.29700 0.989695 1.29750 0.988357 1.29800 0.993488 1.29850 0.996987 1.29900 0.995085 1.29950 0.988373 1.30000 0.985354 1.30050 0.982596 1.30100 0.984381 1.30150 0.992523 1.30200 0.994774 1.30250 0.988664 1.30300 0.980634 1.30350 0.971293 1.30400 0.962918 1.30450 0.969263 1.30500 0.987720 1.30550 0.991306 1.30600 0.985716 1.30650 0.968053 1.30700 0.967671 1.30750 0.978376 1.30800 0.971561 1.30850 0.979002 1.30900 0.984302 1.30950 0.962535 1.31000 0.950377 1.31050 0.956719 1.31100 0.976448 1.31150 0.990140 1.31200 0.978798 1.31250 0.966486 1.31300 0.956840 1.31350 0.934889 1.31400 0.939970 1.31450 0.958249 1.31500 0.960607 1.31550 0.962254 1.31600 0.974172 1.31650 0.970949 1.31700 0.954463 1.31750 0.971178 1.31800 0.968766 1.31850 0.946931 1.31900 0.945659 1.31950 0.942867 1.32000 0.918592 1.32050 0.929011 1.32100 0.950730 1.32150 0.945695 1.32200 0.955817 1.32250 0.946827 1.32300 0.926178 1.32350 0.936737 1.32400 0.941362 1.32450 0.956181 1.32500 0.965625 1.32550 0.949400 1.32600 0.957974 1.32650 0.958642 1.32700 0.939564 1.32750 0.937278 1.32800 0.916788 1.32850 0.879224 1.32900 0.878624 1.32950 0.913538 1.33000 0.924358 1.33050 0.907027 1.33100 0.864149 1.33150 0.834164 1.33200 0.830201 1.33250 0.843688 1.33300 0.889382 1.33350 0.871627 1.33400 0.859372 1.33450 0.904001 1.33500 0.918914 1.33550 0.905105 1.33600 0.866219 1.33650 0.897052 1.33700 0.880653 1.33750 0.842492 1.33800 0.866221 1.33850 0.905274 1.33900 0.903538 1.33950 0.903040 1.34000 0.857331 1.34050 0.827196 1.34100 0.874881 1.34150 0.881135 1.34200 0.912678 1.34250 0.918185 1.34300 0.902951 1.34350 0.859701 1.34400 0.738835 1.34450 0.709692 1.34500 0.798344 1.34550 0.816491 1.34600 0.786334 1.34650 0.762971 1.34700 0.768541 1.34750 0.643887 1.34800 0.491810 1.34850 0.508059 1.34900 0.629390 1.34950 0.706715 1.35000 0.576285 1.35050 0.462633 1.35100 0.527266 1.35150 0.509747 1.35200 0.483102 1.35250 0.445504 1.35300 0.356107 1.35350 0.342676 1.35400 0.328445 1.35450 0.197219 1.35500 0.164488 1.35550 0.218198 1.35600 0.276226 1.35650 0.365766 1.35700 0.283954 1.35750 0.194598 1.35800 0.208406 1.35850 0.205095 1.35900 0.199447 1.35950 0.156136 1.36000 0.177611 1.36050 0.153180 1.36100 6.62577E-02 1.36150 2.66995E-02 1.36200 1.51404E-02 1.36250 5.98374E-02 1.36300 0.209144 1.36350 0.297767 1.36400 0.196222 1.36450 6.98286E-02 1.36500 4.96920E-02 1.36550 0.135551 1.36600 0.302574 1.36650 0.337787 1.36700 0.217834 1.36750 1.00855E-01 1.36800 4.70564E-02 1.36850 6.26280E-02 1.36900 0.149675 1.36950 0.198916 1.37000 0.149135 1.37050 0.118841 1.37100 9.56551E-02 1.37150 0.140676 1.37200 0.195606 1.37250 0.283089 1.37300 0.326103 1.37350 0.354299 1.37400 0.386147 1.37450 0.322281 1.37500 0.360891 1.37550 0.453086 1.37600 0.377087 1.37650 0.327164 1.37700 0.320174 1.37750 0.495142 1.37800 0.519425 1.37850 0.294434 1.37900 0.276679 1.37950 0.407687 1.38000 0.299731 1.38050 1.03512E-01 1.38100 0.109494 1.38150 0.214145 1.38200 0.162237 1.38250 6.52233E-02 1.38300 6.09866E-02 1.38350 0.109256 1.38400 0.178010 1.38450 0.220932 1.38500 0.158425 1.38550 0.168395 1.38600 0.233275 1.38650 0.331220 1.38700 0.442871 1.38750 0.345280 1.38800 0.172340 1.38850 0.275885 1.38900 0.499047 1.38950 0.535737 1.39000 0.472701 1.39050 0.478809 1.39100 0.483321 1.39150 0.449178 1.39200 0.237272 1.39250 0.132192 1.39300 0.295937 1.39350 0.433298 1.39400 0.392175 1.39450 0.258722 1.39500 1.01425E-01 1.39550 5.64375E-02 1.39600 7.69295E-02 1.39650 0.126794 1.39700 0.284888 1.39750 0.501226 1.39800 0.592373 1.39850 0.580968 1.39900 0.481826 1.39950 0.249707 1.40000 0.114295 1.40050 6.69293E-02 1.40100 7.63803E-02 1.40150 0.167137 1.40200 0.417217 1.40250 0.602445 1.40300 0.547245 1.40350 0.490765 1.40400 0.338334 1.40450 0.167855 1.40500 0.142995 1.40550 0.336699 1.40600 0.535478 1.40650 0.441941 1.40700 0.209916 1.40750 0.264541 1.40800 0.480982 1.40850 0.469298 1.40900 0.267194 1.40950 0.176476 1.41000 0.328066 1.41050 0.466136 1.41100 0.497142 1.41150 0.517930 1.41200 0.494083 1.41250 0.528470 1.41300 0.670770 1.41350 0.543994 1.41400 0.315460 1.41450 0.215553 1.41500 0.281061 1.41550 0.462164 1.41600 0.679135 1.41650 0.731140 1.41700 0.647579 1.41750 0.573938 1.41800 0.580755 1.41850 0.508240 1.41900 0.292362 1.41950 0.289450 1.42000 0.447425 1.42050 0.533599 1.42100 0.596804 1.42150 0.673366 1.42200 0.744543 1.42250 0.689326 1.42300 0.530980 1.42350 0.431204 1.42400 0.491623 1.42450 0.675004 1.42500 0.674831 1.42550 0.558107 1.42600 0.613803 1.42650 0.753543 1.42700 0.695788 1.42750 0.561568 1.42800 0.504341 1.42850 0.511057 1.42900 0.608029 1.42950 0.705028 1.43000 0.748174 1.43050 0.790472 1.43100 0.613405 1.43150 0.423867 1.43200 0.373470 1.43250 0.385777 1.43300 0.555137 1.43350 0.672079 1.43400 0.582412 1.43450 0.536952 1.43500 0.644952 1.43550 0.689954 1.43600 0.636357 1.43650 0.633261 1.43700 0.588406 1.43750 0.429183 1.43800 0.480450 1.43850 0.686126 1.43900 0.692260 1.43950 0.588509 1.44000 0.660672 1.44050 0.723990 1.44100 0.676157 1.44150 0.617975 1.44200 0.686400 1.44250 0.661564 1.44300 0.628641 1.44350 0.763524 1.44400 0.785264 1.44450 0.759266 1.44500 0.672885 1.44550 0.438539 1.44600 0.472890 1.44650 0.673442 1.44700 0.705155 1.44750 0.771982 1.44800 0.831514 1.44850 0.835559 1.44900 0.856568 1.44950 0.796607 1.45000 0.602390 1.45050 0.417076 1.45100 0.460663 1.45150 0.674837 1.45200 0.740778 1.45250 0.746815 1.45300 0.790204 1.45350 0.875651 1.45400 0.906380 1.45450 0.855457 1.45500 0.740161 1.45550 0.706508 1.45600 0.833406 1.45650 0.890081 1.45700 0.867164 1.45750 0.829506 1.45800 0.862152 1.45850 0.921453 1.45900 0.933240 1.45950 0.903528 1.46000 0.796659 1.46050 0.751729 1.46100 0.795446 1.46150 0.818978 1.46200 0.886126 1.46250 0.854437 1.46300 0.714736 1.46350 0.744172 1.46400 0.885306 1.46450 0.899101 1.46500 0.749455 1.46550 0.696033 1.46600 0.719099 1.46650 0.670840 1.46700 0.677860 1.46750 0.741448 1.46800 0.811650 1.46850 0.882057 1.46900 0.821356 1.46950 0.689987 1.47000 0.681842 1.47050 0.596856 1.47100 0.555473 1.47150 0.693104 1.47200 0.692504 1.47250 0.643489 1.47300 0.730927 1.47350 0.761093 1.47400 0.825224 1.47450 0.926959 1.47500 0.933974 1.47550 0.829277 1.47600 0.768126 1.47650 0.807752 1.47700 0.773674 1.47750 0.737137 1.47800 0.728135 1.47850 0.838802 1.47900 0.856256 1.47950 0.790548 1.48000 0.731276 1.48050 0.698974 1.48100 0.796290 1.48150 0.777120 1.48200 0.738904 1.48250 0.846352 1.48300 0.913145 1.48350 0.922818 1.48400 0.878982 1.48450 0.829555 1.48500 0.866690 1.48550 0.882665 1.48600 0.858853 1.48650 0.794350 1.48700 0.795396 1.48750 0.832984 1.48800 0.842272 1.48850 0.892043 1.48900 0.925376 1.48950 0.895341 1.49000 0.918729 1.49050 0.964423 1.49100 0.923781 1.49150 0.867480 1.49200 0.900942 1.49250 0.930368 1.49300 0.941320 1.49350 0.958792 1.49400 0.955063 1.49450 0.939097 1.49500 0.915747 1.49550 0.911000 1.49600 0.912630 1.49650 0.914039 1.49700 0.951242 1.49750 0.962185 1.49800 0.942913 1.49850 0.953168 1.49900 0.962683 1.49950 0.962687 1.50000 0.976127 1.50050 0.987873 1.50100 0.988915 1.50150 0.978267 1.50200 0.968245 1.50250 0.964149 1.50300 0.952026 1.50350 0.930347 1.50400 0.910647 1.50450 0.921950 1.50500 0.949984 1.50550 0.978659 1.50600 0.990180 1.50650 0.994654 1.50700 0.989170 1.50750 0.984464 1.50800 0.970488 1.50850 0.939241 1.50900 0.940464 1.50950 0.975258 1.51000 0.989160 1.51050 0.988591 1.51100 0.989222 1.51150 0.991763 1.51200 0.989753 1.51250 0.982265 1.51300 0.980492 1.51350 0.978270 1.51400 0.977114 1.51450 0.983816 1.51500 0.989752 1.51550 0.993510 1.51600 0.987815 1.51650 0.981240 1.51700 0.981664 1.51750 0.982419 1.51800 0.986983 1.51850 0.987771 1.51900 0.984376 1.51950 0.988020 1.52000 0.992537 1.52050 0.993898 1.52100 0.996880 1.52150 0.995895 1.52200 0.995165 1.52250 0.998102 1.52300 0.998530 1.52350 0.997494 1.52400 0.996642 1.52450 0.994038 1.52500 0.993946 1.52550 0.993713 1.52600 0.993061 1.52650 0.992550 1.52700 0.992221 1.52750 0.994028 1.52800 0.996662 1.52850 0.996541 1.52900 0.996339 1.52950 0.996204 1.53000 0.995676 1.53050 0.996101 1.53100 0.994965 1.53150 0.994058 1.53200 0.995719 1.53250 0.995789 1.53300 0.994631 1.53350 0.993077 1.53400 0.991478 1.53450 0.990239 1.53500 0.989070 1.53550 0.989680 1.53600 0.991399 1.53650 0.993151 1.53700 0.994556 1.53750 0.996615 1.53800 0.997626 1.53850 0.996225 1.53900 0.994022 1.53950 0.992960 1.54000 0.992898 1.54050 0.992830 1.54100 0.993067 1.54150 0.993408 1.54200 0.993820 1.54250 0.994946 1.54300 0.996148 1.54350 0.997009 1.54400 0.997002 1.54450 0.997351 1.54500 0.998492 1.54550 0.998804 1.54600 0.998827 1.54650 0.999161 1.54700 0.999538 1.54750 0.999680 1.54800 0.999747 1.54850 0.999898 1.54900 0.999957 1.54950 0.999908 1.55000 0.999905 1.55050 0.999869 1.55100 0.999727 1.55150 0.999531 1.55200 0.999232 1.55250 0.999521 1.55300 0.999876 1.55350 0.999927 1.55400 0.999782 1.55450 0.999738 1.55500 0.999903 1.55550 0.999961 1.55600 0.999858 1.55650 0.999794 1.55700 0.999909 1.55750 0.999950 1.55800 0.999853 1.55850 0.999752 1.55900 0.999777 1.55950 0.999810 1.56000 0.999819 1.56050 0.999835 1.56100 0.999836 1.56150 0.999811 1.56200 0.999451 1.56250 0.999325 1.56300 0.999658 1.56350 0.999748 1.56400 0.999556 1.56450 0.999502 1.56500 0.999757 1.56550 0.999748 1.56600 0.999569 1.56650 0.999419 1.56700 0.998895 1.56750 0.997646 1.56800 0.995226 1.56850 0.991109 1.56900 0.984467 1.56950 0.975234 1.57000 0.964163 1.57050 0.952727 1.57100 0.942431 1.57150 0.934682 1.57200 0.930290 1.57250 0.929652 1.57300 0.932669 1.57350 0.939408 1.57400 0.950297 1.57450 0.964999 1.57500 0.979779 1.57550 0.983609 1.57600 0.974103 1.57650 0.962790 1.57700 0.955258 1.57750 0.951500 1.57800 0.950629 1.57850 0.951747 1.57900 0.954432 1.57950 0.958269 1.58000 0.962654 1.58050 0.967323 1.58100 0.972054 1.58150 0.976600 1.58200 0.980834 1.58250 0.984497 1.58300 0.987513 1.58350 0.990428 1.58400 0.992995 1.58450 0.994813 1.58500 0.996168 1.58550 0.997180 1.58600 0.997703 1.58650 0.998076 1.58700 0.998400 1.58750 0.998924 1.58800 0.999493 1.58850 0.999719 1.58900 0.999806 1.58950 0.999795 1.59000 0.999626 1.59050 0.999606 1.59100 0.999684 1.59150 0.999478 1.59200 0.999361 1.59250 0.999411 1.59300 0.999673 1.59350 0.999755 1.59400 0.999421 1.59450 0.999211 1.59500 0.999479 1.59550 0.999537 1.59600 0.999160 1.59650 0.998724 1.59700 0.998103 1.59750 0.997171 1.59800 0.995456 1.59850 0.991753 1.59900 0.985847 1.59950 0.977709 1.60000 0.967902 1.60050 0.957382 1.60100 0.947361 1.60150 0.939081 1.60200 0.933282 1.60250 0.930844 1.60300 0.931917 1.60350 0.936357 1.60400 0.944567 1.60450 0.956796 1.60500 0.972188 1.60550 0.984489 1.60600 0.983245 1.60650 0.972040 1.60700 0.961620 1.60750 0.954897 1.60800 0.951228 1.60850 0.950145 1.60900 0.951139 1.60950 0.953324 1.61000 0.956358 1.61050 0.960237 1.61100 0.964884 1.61150 0.969655 1.61200 0.974316 1.61250 0.978735 1.61300 0.982687 1.61350 0.986299 1.61400 0.989509 1.61450 0.991717 1.61500 0.993204 1.61550 0.994340 1.61600 0.995103 1.61650 0.995492 1.61700 0.996078 1.61750 0.996861 1.61800 0.997288 1.61850 0.997381 1.61900 0.997678 1.61950 0.998421 1.62000 0.998802 1.62050 0.998776 1.62100 0.998729 1.62150 0.998804 1.62200 0.998989 1.62250 0.999105 1.62300 0.999097 1.62350 0.998694 1.62400 0.998469 1.62450 0.999025 1.62500 0.999183 1.62550 0.998587 1.62600 0.998171 1.62650 0.998464 1.62700 0.998196 1.62750 0.997820 1.62800 0.997434 1.62850 0.997274 1.62900 0.997993 1.62950 0.998421 1.63000 0.997172 1.63050 0.994920 1.63100 0.996114 1.63150 0.997760 1.63200 0.997498 1.63250 0.995306 1.63300 0.993580 1.63350 0.995311 1.63400 0.997154 1.63450 0.997027 1.63500 0.993315 1.63550 0.990432 1.63600 0.994459 1.63650 0.996644 1.63700 0.994306 1.63750 0.986407 1.63800 0.986976 1.63850 0.994173 1.63900 0.994776 1.63950 0.991007 1.64000 0.980729 1.64050 0.976942 1.64100 0.986842 1.64150 0.990721 1.64200 0.988497 1.64250 0.976916 1.64300 0.968928 1.64350 0.982052 1.64400 0.991232 1.64450 0.992525 1.64500 0.982766 1.64550 0.967816 1.64600 0.980126 1.64650 0.993557 1.64700 0.994296 1.64750 0.988097 1.64800 0.973328 1.64850 0.972751 1.64900 0.984921 1.64950 0.989340 1.65000 0.989238 1.65050 0.978063 1.65100 0.967990 1.65150 0.981931 1.65200 0.992765 1.65250 0.995613 1.65300 0.991795 1.65350 0.978515 1.65400 0.980623 1.65450 0.993565 1.65500 0.995874 1.65550 0.995054 1.65600 0.991908 1.65650 0.987227 1.65700 0.991630 1.65750 0.997000 1.65800 0.998547 1.65850 0.998189 1.65900 0.995499 1.65950 0.993695 1.66000 0.996784 1.66050 0.999005 1.66100 0.998375 1.66150 0.996868 1.66200 0.994010 1.66250 0.993167 1.66300 0.996674 1.66350 0.997982 1.66400 0.998521 1.66450 0.995489 1.66500 0.971076 1.66550 0.927306 1.66600 0.923475 1.66650 0.952526 1.66700 0.975791 1.66750 0.989480 1.66800 0.994668 1.66850 0.995675 1.66900 0.997153 1.66950 0.998042 1.67000 0.998188 1.67050 0.997299 1.67100 0.995376 1.67150 0.995133 1.67200 0.997900 1.67250 0.999163 1.67300 0.999223 1.67350 0.998118 1.67400 0.992682 1.67450 0.988228 1.67500 0.994055 1.67550 0.997917 1.67600 0.998936 1.67650 0.998614 1.67700 0.993459 1.67750 0.984968 1.67800 0.989630 1.67850 0.997209 1.67900 0.999228 1.67950 0.999313 1.68000 0.997405 1.68050 0.990808 1.68100 0.989273 1.68150 0.995740 1.68200 0.998706 1.68250 0.999319 1.68300 0.998376 1.68350 0.990795 1.68400 0.980805 1.68450 0.988967 1.68500 0.997343 1.68550 0.998664 1.68600 0.998913 1.68650 0.997086 1.68700 0.990052 1.68750 0.988390 1.68800 0.995657 1.68850 0.998457 1.68900 0.998063 1.68950 0.997635 1.69000 0.994119 1.69050 0.985778 1.69100 0.987120 1.69150 0.992654 1.69200 0.994602 1.69250 0.996123 1.69300 0.997699 1.69350 0.995098 1.69400 0.990797 1.69450 0.992972 1.69500 0.996296 1.69550 0.998484 1.69600 0.998157 1.69650 0.992445 1.69700 0.984021 1.69750 0.985571 1.69800 0.994299 1.69850 0.997533 1.69900 0.997159 1.69950 0.994233 1.70000 0.992502 1.70050 0.990767 1.70100 0.988779 1.70150 0.993147 1.70200 0.994740 1.70250 0.991910 1.70300 0.990486 1.70350 0.991295 1.70400 0.993332 1.70450 0.994203 1.70500 0.991526 1.70550 0.988988 1.70600 0.989099 1.70650 0.987328 1.70700 0.986778 1.70750 0.985875 1.70800 0.981381 1.70850 0.972679 1.70900 0.977266 1.70950 0.989928 1.71000 0.992930 1.71050 0.991058 1.71100 0.993445 1.71150 0.996543 1.71200 0.996317 1.71250 0.995869 1.71300 0.995553 1.71350 0.990737 1.71400 0.979007 1.71450 0.976442 1.71500 0.988320 1.71550 0.991714 1.71600 0.986361 1.71650 0.989698 1.71700 0.995886 1.71750 0.997766 1.71800 0.998281 1.71850 0.998016 1.71900 0.998005 1.71950 0.997728 1.72000 0.995841 1.72050 0.988585 1.72100 0.967241 1.72150 0.961070 1.72200 0.981616 1.72250 0.989215 1.72300 0.983622 1.72350 0.971844 1.72400 0.970175 1.72450 0.969362 1.72500 0.978757 1.72550 0.988441 1.72600 0.986625 1.72650 0.987573 1.72700 0.990403 1.72750 0.991697 1.72800 0.989576 1.72850 0.980966 1.72900 0.981130 1.72950 0.991560 1.73000 0.995457 1.73050 0.993014 1.73100 0.990766 1.73150 0.976093 1.73200 0.939757 1.73250 0.941933 1.73300 0.980472 1.73350 0.995811 1.73400 0.996484 1.73450 0.994913 1.73500 0.989011 1.73550 0.973590 1.73600 0.962304 1.73650 0.961789 1.73700 0.973036 1.73750 0.990482 1.73800 0.994329 1.73850 0.994816 1.73900 0.993127 1.73950 0.979801 1.74000 0.973763 1.74050 0.978315 1.74100 0.951173 1.74150 0.940155 1.74200 0.973486 1.74250 0.993695 1.74300 0.997225 1.74350 0.994541 1.74400 0.974627 1.74450 0.957539 1.74500 0.980048 1.74550 0.994150 1.74600 0.978571 1.74650 0.918166 1.74700 0.877017 1.74750 0.922594 1.74800 0.978262 1.74850 0.995971 1.74900 0.996797 1.74950 0.996542 1.75000 0.996631 1.75050 0.992819 1.75100 0.967950 1.75150 0.934713 1.75200 0.961233 1.75250 0.989495 1.75300 0.994754 1.75350 0.997510 1.75400 0.995987 1.75450 0.977098 1.75500 0.936040 1.75550 0.947272 1.75600 0.977622 1.75650 0.954346 1.75700 0.918975 1.75750 0.923623 1.75800 0.968881 1.75850 0.994627 1.75900 0.998502 1.75950 0.998742 1.76000 0.995493 1.76050 0.976922 1.76100 0.952603 1.76150 0.970133 1.76200 0.975319 1.76250 0.948511 1.76300 0.940851 1.76350 0.969066 1.76400 0.991622 1.76450 0.992540 1.76500 0.959453 1.76550 0.834546 1.76600 0.772715 1.76650 0.901742 1.76700 0.980070 1.76750 0.990129 1.76800 0.986865 1.76850 0.985082 1.76900 0.979604 1.76950 0.962794 1.77000 0.950618 1.77050 0.931784 1.77100 0.942754 1.77150 0.981390 1.77200 0.990495 1.77250 0.987907 1.77300 0.984096 1.77350 0.951467 1.77400 0.894844 1.77450 0.907695 1.77500 0.952940 1.77550 0.943479 1.77600 0.884594 1.77650 0.894857 1.77700 0.957899 1.77750 0.974114 1.77800 0.934478 1.77850 0.844515 1.77900 0.832301 1.77950 0.927918 1.78000 0.974862 1.78050 0.948595 1.78100 0.846457 1.78150 0.831929 1.78200 0.931842 1.78250 0.978378 1.78300 0.979546 1.78350 0.958535 1.78400 0.889102 1.78450 0.758125 1.78500 0.738123 1.78550 0.830661 1.78600 0.854463 1.78650 0.886765 1.78700 0.868959 1.78750 0.899529 1.78800 0.959811 1.78850 0.951439 1.78900 0.943822 1.78950 0.969998 1.79000 0.936923 1.79050 0.847680 1.79100 0.819047 1.79150 0.858394 1.79200 0.860120 1.79250 0.835251 1.79300 0.812922 1.79350 0.739816 1.79400 0.755525 1.79450 0.858164 1.79500 0.856749 1.79550 0.841673 1.79600 0.841466 1.79650 0.803639 1.79700 0.784590 1.79750 0.847177 1.79800 0.840571 1.79850 0.885013 1.79900 0.922763 1.79950 0.780291 1.80000 0.584666 1.80050 0.506150 1.80100 0.471652 1.80150 0.512914 1.80200 0.568489 1.80250 0.591665 1.80300 0.677113 1.80350 0.699647 1.80400 0.736619 1.80450 0.830771 1.80500 0.855857 1.80550 0.690749 1.80600 0.438181 1.80650 0.411676 1.80700 0.566936 1.80750 0.610007 1.80800 0.732577 1.80850 0.782688 1.80900 0.726123 1.80950 0.764853 1.81000 0.711491 1.81050 0.489735 1.81100 0.388435 1.81150 0.506124 1.81200 0.510681 1.81250 0.387894 1.81300 0.388558 1.81350 0.579377 1.81400 0.761626 1.81450 0.764149 1.81500 0.537874 1.81550 0.332955 1.81600 0.358867 1.81650 0.495185 1.81700 0.572971 1.81750 0.679194 1.81800 0.647414 1.81850 0.448197 1.81900 0.502743 1.81950 0.529272 1.82000 0.283356 1.82050 0.116465 1.82100 0.180236 1.82150 0.217103 1.82200 0.180477 1.82250 0.254541 1.82300 0.503638 1.82350 0.767083 1.82400 0.841912 1.82450 0.745811 1.82500 0.595845 1.82550 0.484293 1.82600 0.267268 1.82650 0.176834 1.82700 0.368454 1.82750 0.478558 1.82800 0.339694 1.82850 0.171749 1.82900 0.173364 1.82950 0.276971 1.83000 0.240067 1.83050 0.207665 1.83100 0.176644 1.83150 0.227948 1.83200 0.341251 1.83250 0.261043 1.83300 0.258407 1.83350 0.412062 1.83400 0.408319 1.83450 0.219426 1.83500 8.46848E-02 1.83550 6.81353E-02 1.83600 4.97111E-02 1.83650 5.47538E-02 1.83700 8.75217E-02 1.83750 5.25519E-02 1.83800 4.88797E-02 1.83850 0.154729 1.83900 0.245404 1.83950 0.154266 1.84000 5.04592E-02 1.84050 8.40258E-02 1.84100 0.134459 1.84150 7.03485E-02 1.84200 2.09530E-02 1.84250 8.08668E-02 1.84300 0.224701 1.84350 0.273863 1.84400 0.325278 1.84450 0.504203 1.84500 0.597827 1.84550 0.501915 1.84600 0.292331 1.84650 1.03795E-01 1.84700 1.76967E-02 1.84750 5.70865E-03 1.84800 5.19340E-03 1.84850 1.80083E-02 1.84900 8.55736E-02 1.84950 0.206820 1.85000 0.212377 1.85050 0.132717 1.85100 0.228723 1.85150 0.463528 1.85200 0.513124 1.85250 0.312798 1.85300 9.91987E-02 1.85350 2.73874E-02 1.85400 3.10382E-02 1.85450 1.03641E-01 1.85500 0.240103 1.85550 0.348119 1.85600 0.256409 1.85650 9.17837E-02 1.85700 4.44364E-02 1.85750 0.139329 1.85800 0.331460 1.85850 0.386931 1.85900 0.255882 1.85950 8.92806E-02 1.86000 1.56786E-02 1.86050 2.36687E-02 1.86100 0.136774 1.86150 0.388707 1.86200 0.562145 1.86250 0.481994 1.86300 0.365988 1.86350 0.376995 1.86400 0.258770 1.86450 0.144683 1.86500 0.229582 1.86550 0.439161 1.86600 0.595415 1.86650 0.513722 1.86700 0.269665 1.86750 7.28503E-02 1.86800 2.86421E-02 1.86850 8.05517E-02 1.86900 0.106550 1.86950 5.89996E-02 1.87000 2.42958E-02 1.87050 1.71540E-02 1.87100 8.70671E-03 1.87150 2.33764E-02 1.87200 7.93091E-02 1.87250 8.55701E-02 1.87300 2.92135E-02 1.87350 7.85538E-03 1.87400 3.38997E-02 1.87450 8.69694E-02 1.87500 8.87879E-02 1.87550 7.36377E-02 1.87600 1.00062E-01 1.87650 8.17879E-02 1.87700 4.91521E-02 1.87750 0.122091 1.87800 0.243875 1.87850 0.389643 1.87900 0.517754 1.87950 0.519130 1.88000 0.508667 1.88050 0.316157 1.88100 0.173518 1.88150 0.255371 1.88200 0.476100 1.88250 0.588994 1.88300 0.520819 1.88350 0.331499 1.88400 0.131395 1.88450 0.178703 1.88500 0.342564 1.88550 0.387104 1.88600 0.440294 1.88650 0.384287 1.88700 0.327282 1.88750 0.542175 1.88800 0.729578 1.88850 0.729931 1.88900 0.615871 1.88950 0.370293 1.89000 0.125563 1.89050 0.128184 1.89100 0.288727 1.89150 0.297685 1.89200 0.169225 1.89250 0.147100 1.89300 0.200545 1.89350 0.190246 1.89400 0.234055 1.89450 0.413328 1.89500 0.578752 1.89550 0.660335 1.89600 0.579465 1.89650 0.531565 1.89700 0.494070 1.89750 0.293291 1.89800 0.159377 1.89850 0.145070 1.89900 6.59336E-02 1.89950 1.57474E-02 1.90000 1.26247E-02 1.90050 4.09072E-02 1.90100 0.158473 1.90150 0.322464 1.90200 0.362018 1.90250 0.205565 1.90300 8.48610E-02 1.90350 0.183224 1.90400 0.316524 1.90450 0.236446 1.90500 8.30411E-02 1.90550 2.20497E-02 1.90600 2.54519E-02 1.90650 3.64896E-02 1.90700 0.108216 1.90750 0.337190 1.90800 0.525159 1.90850 0.482315 1.90900 0.405334 1.90950 0.483618 1.91000 0.439805 1.91050 0.287100 1.91100 0.198571 1.91150 0.122362 1.91200 0.149485 1.91250 0.219584 1.91300 0.168011 1.91350 6.53308E-02 1.91400 5.08557E-02 1.91450 0.134771 1.91500 0.310537 1.91550 0.422834 1.91600 0.266148 1.91650 0.137026 1.91700 0.274917 1.91750 0.568813 1.91800 0.756328 1.91850 0.740929 1.91900 0.553928 1.91950 0.250883 1.92000 6.11322E-02 1.92050 2.58625E-02 1.92100 8.31992E-02 1.92150 0.277266 1.92200 0.546590 1.92250 0.703957 1.92300 0.704167 1.92350 0.512093 1.92400 0.218637 1.92450 0.234665 1.92500 0.542120 1.92550 0.734648 1.92600 0.669011 1.92650 0.491648 1.92700 0.257205 1.92750 0.264051 1.92800 0.369206 1.92850 0.302842 1.92900 0.418468 1.92950 0.602152 1.93000 0.487219 1.93050 0.306640 1.93100 0.260635 1.93150 0.339334 1.93200 0.500856 1.93250 0.616774 1.93300 0.619693 1.93350 0.580344 1.93400 0.431508 1.93450 0.531992 1.93500 0.757475 1.93550 0.771910 1.93600 0.624667 1.93650 0.478980 1.93700 0.540739 1.93750 0.577286 1.93800 0.488617 1.93850 0.497161 1.93900 0.399613 1.93950 0.306515 1.94000 0.457610 1.94050 0.614259 1.94100 0.521632 1.94150 0.479325 1.94200 0.665395 1.94250 0.836420 1.94300 0.804259 1.94350 0.650314 1.94400 0.558456 1.94450 0.491433 1.94500 0.504787 1.94550 0.649515 1.94600 0.704404 1.94650 0.721354 1.94700 0.671476 1.94750 0.628802 1.94800 0.613201 1.94850 0.603658 1.94900 0.633286 1.94950 0.638534 1.95000 0.663765 1.95050 0.655430 1.95100 0.669548 1.95150 0.720225 1.95200 0.682669 1.95250 0.608920 1.95300 0.526445 1.95350 0.486591 1.95400 0.496520 1.95450 0.484241 1.95500 0.491300 1.95550 0.451325 1.95600 0.437327 1.95650 0.432061 1.95700 0.448548 1.95750 0.454386 1.95800 0.446968 1.95850 0.465756 1.95900 0.446269 1.95950 0.508316 1.96000 0.660921 1.96050 0.700766 1.96100 0.664026 1.96150 0.697099 1.96200 0.687160 1.96250 0.614468 1.96300 0.547593 1.96350 0.536336 1.96400 0.562797 1.96450 0.588990 1.96500 0.627330 1.96550 0.635671 1.96600 0.647841 1.96650 0.683231 1.96700 0.704657 1.96750 0.699117 1.96800 0.691477 1.96850 0.723825 1.96900 0.752994 1.96950 0.771480 1.97000 0.796069 1.97050 0.797955 1.97100 0.757150 1.97150 0.716151 1.97200 0.733529 1.97250 0.802412 1.97300 0.856930 1.97350 0.849135 1.97400 0.795526 1.97450 0.821585 1.97500 0.894606 1.97550 0.895768 1.97600 0.892699 1.97650 0.931530 1.97700 0.948029 1.97750 0.940579 1.97800 0.946013 1.97850 0.952906 1.97900 0.935200 1.97950 0.830717 1.98000 0.741971 1.98050 0.802358 1.98100 0.916834 1.98150 0.974485 1.98200 0.981750 1.98250 0.964425 1.98300 0.944212 1.98350 0.967332 1.98400 0.986558 1.98450 0.977321 1.98500 0.947129 1.98550 0.949520 1.98600 0.968415 1.98650 0.906401 1.98700 0.824194 1.98750 0.891949 1.98800 0.966978 1.98850 0.984228 1.98900 0.981862 1.98950 0.942798 1.99000 0.867579 1.99050 0.864036 1.99100 0.932131 1.99150 0.978125 1.99200 0.985413 1.99250 0.987140 1.99300 0.986963 1.99350 0.969279 1.99400 0.925177 1.99450 0.922377 1.99500 0.940895 1.99550 0.916483 1.99600 0.897388 1.99650 0.867044 1.99700 0.827800 1.99750 0.787334 1.99800 0.706908 1.99850 0.662155 1.99900 0.639285 1.99950 0.596910 2.00000 0.556002 2.00050 0.508995 2.00100 0.454164 2.00150 0.417463 2.00200 0.387244 2.00250 0.345502 2.00300 0.327869 2.00350 0.337062 2.00400 0.337982 2.00450 0.331677 2.00500 0.321837 2.00550 0.315961 2.00600 0.334940 2.00650 0.384449 2.00700 0.431995 2.00750 0.468816 2.00800 0.514937 2.00850 0.593274 2.00900 0.657419 2.00950 0.609583 2.01000 0.521058 2.01050 0.468812 2.01100 0.449696 2.01150 0.444837 2.01200 0.440626 2.01250 0.439943 2.01300 0.445357 2.01350 0.449248 2.01400 0.458862 2.01450 0.476211 2.01500 0.484873 2.01550 0.493869 2.01600 0.504679 2.01650 0.506091 2.01700 0.512109 2.01750 0.535956 2.01800 0.553449 2.01850 0.575841 2.01900 0.600710 2.01950 0.632571 2.02000 0.659412 2.02050 0.675746 2.02100 0.690374 2.02150 0.714045 2.02200 0.744452 2.02250 0.771781 2.02300 0.787411 2.02350 0.803735 2.02400 0.820653 2.02450 0.841601 2.02500 0.856356 2.02550 0.875177 2.02600 0.891883 2.02650 0.904355 2.02700 0.916050 2.02750 0.915351 2.02800 0.908367 2.02850 0.909366 2.02900 0.931483 2.02950 0.949025 2.03000 0.956729 2.03050 0.954635 2.03100 0.958649 2.03150 0.962757 2.03200 0.964469 2.03250 0.960821 2.03300 0.951341 2.03350 0.950733 2.03400 0.960386 2.03450 0.963544 2.03500 0.964312 2.03550 0.964373 2.03600 0.964186 2.03650 0.964009 2.03700 0.963889 2.03750 0.963145 2.03800 0.959963 2.03850 0.957111 2.03900 0.956403 2.03950 0.947628 2.04000 0.930549 2.04050 0.927770 2.04100 0.935863 2.04150 0.938058 2.04200 0.938262 2.04250 0.938662 2.04300 0.938092 2.04350 0.937561 2.04400 0.937251 2.04450 0.942568 2.04500 0.950601 2.04550 0.954354 2.04600 0.951335 2.04650 0.938762 2.04700 0.920234 2.04750 0.898897 2.04800 0.875485 2.04850 0.853501 2.04900 0.832114 2.04950 0.809014 2.05000 0.785837 2.05050 0.768295 2.05100 0.755254 2.05150 0.733945 2.05200 0.714702 2.05250 0.702360 2.05300 0.697071 2.05350 0.683016 2.05400 0.665835 2.05450 0.659634 2.05500 0.662759 2.05550 0.669362 2.05600 0.666639 2.05650 0.662827 2.05700 0.674628 2.05750 0.695444 2.05800 0.719976 2.05850 0.745888 2.05900 0.778991 2.05950 0.827200 2.06000 0.877946 2.06050 0.889037 2.06100 0.842994 2.06150 0.802663 2.06200 0.776576 2.06250 0.753178 2.06300 0.739510 2.06350 0.735301 2.06400 0.732821 2.06450 0.731170 2.06500 0.732417 2.06550 0.734088 2.06600 0.734408 2.06650 0.743096 2.06700 0.754927 2.06750 0.763885 2.06800 0.772236 2.06850 0.781575 2.06900 0.789880 2.06950 0.797068 2.07000 0.806938 2.07050 0.815498 2.07100 0.824557 2.07150 0.836803 2.07200 0.847271 2.07250 0.860418 2.07300 0.865582 2.07350 0.867764 2.07400 0.876762 2.07450 0.883734 2.07500 0.889187 2.07550 0.899364 2.07600 0.908583 2.07650 0.917289 2.07700 0.922241 2.07750 0.920770 2.07800 0.929780 2.07850 0.948897 2.07900 0.963711 2.07950 0.970740 2.08000 0.971610 2.08050 0.972781 2.08100 0.970019 2.08150 0.964121 2.08200 0.958987 2.08250 0.955405 2.08300 0.952902 2.08350 0.952600 2.08400 0.950460 2.08450 0.942934 2.08500 0.941579 2.08550 0.951750 2.08600 0.957638 2.08650 0.961765 2.08700 0.965898 2.08750 0.968015 2.08800 0.968744 2.08850 0.969073 2.08900 0.970050 2.08950 0.969301 2.09000 0.967523 2.09050 0.971590 2.09100 0.975703 2.09150 0.977484 2.09200 0.978377 2.09250 0.978491 2.09300 0.978757 2.09350 0.972654 2.09400 0.969828 2.09450 0.980094 2.09500 0.986052 2.09550 0.987759 2.09600 0.988737 2.09650 0.989196 2.09700 0.988857 2.09750 0.988783 2.09800 0.984599 2.09850 0.971005 2.09900 0.970741 2.09950 0.983968 2.10000 0.985586 2.10050 0.984140 2.10100 0.982791 2.10150 0.969959 2.10200 0.971298 2.10250 0.986679 2.10300 0.991906 2.10350 0.992403 2.10400 0.992585 2.10450 0.991891 2.10500 0.990666 2.10550 0.991320 2.10600 0.991978 2.10650 0.990860 2.10700 0.990089 2.10750 0.989484 2.10800 0.988603 2.10850 0.987929 2.10900 0.986920 2.10950 0.983884 2.11000 0.982689 2.11050 0.986071 2.11100 0.986735 2.11150 0.981447 2.11200 0.979408 2.11250 0.985626 2.11300 0.991021 2.11350 0.993767 2.11400 0.994487 2.11450 0.994103 2.11500 0.993010 2.11550 0.991964 2.11600 0.990991 2.11650 0.991483 2.11700 0.992698 2.11750 0.991889 2.11800 0.983970 2.11850 0.971855 2.11900 0.979086 2.11950 0.986677 2.12000 0.986021 2.12050 0.988951 2.12100 0.992599 2.12150 0.993930 2.12200 0.995124 2.12250 0.995836 2.12300 0.996118 2.12350 0.995328 2.12400 0.989318 2.12450 0.982217 2.12500 0.988202 2.12550 0.995557 2.12600 0.997300 2.12650 0.997113 2.12700 0.992464 2.12750 0.977895 2.12800 0.977083 2.12850 0.992157 2.12900 0.997727 2.12950 0.998401 2.13000 0.998534 2.13050 0.998556 2.13100 0.998703 2.13150 0.998836 2.13200 0.998842 2.13250 0.998855 2.13300 0.998938 2.13350 0.999044 2.13400 0.999137 2.13450 0.999175 2.13500 0.998478 2.13550 0.995184 2.13600 0.993312 2.13650 0.996881 2.13700 0.999013 2.13750 0.999248 2.13800 0.999150 2.13850 0.998895 2.13900 0.998691 2.13950 0.998646 2.14000 0.998875 2.14050 0.999125 2.14100 0.999183 2.14150 0.999165 2.14200 0.998968 2.14250 0.998034 2.14300 0.997102 2.14350 0.997802 2.14400 0.998546 2.14450 0.998475 2.14500 0.998611 2.14550 0.998569 2.14600 0.998272 2.14650 0.998110 2.14700 0.997262 2.14750 0.996111 2.14800 0.995171 2.14850 0.994371 2.14900 0.993065 2.14950 0.991842 2.15000 0.991835 2.15050 0.991803 2.15100 0.983901 2.15150 0.975700 2.15200 0.980254 2.15250 0.984396 2.15300 0.985028 2.15350 0.987872 2.15400 0.989390 2.15450 0.989185 2.15500 0.989151 2.15550 0.990972 2.15600 0.994613 2.15650 0.995879 2.15700 0.996363 2.15750 0.996285 2.15800 0.995721 2.15850 0.993083 2.15900 0.987623 2.15950 0.988481 2.16000 0.990452 2.16050 0.987673 2.16100 0.980903 2.16150 0.985045 2.16200 0.992109 2.16250 0.989632 2.16300 0.973865 2.16350 0.958663 2.16400 0.977795 2.16450 0.993738 2.16500 0.995623 2.16550 0.994897 2.16600 0.992859 2.16650 0.986264 2.16700 0.982185 2.16750 0.988255 2.16800 0.987906 2.16850 0.976416 2.16900 0.980153 2.16950 0.993003 2.17000 0.995129 2.17050 0.995134 2.17100 0.997341 2.17150 0.997671 2.17200 0.991474 2.17250 0.977741 2.17300 0.980152 2.17350 0.990936 2.17400 0.989441 2.17450 0.984273 2.17500 0.988683 2.17550 0.992322 2.17600 0.987212 2.17650 0.987250 2.17700 0.995416 2.17750 0.996183 2.17800 0.984596 2.17850 0.973010 2.17900 0.977554 2.17950 0.983870 2.18000 0.993351 2.18050 0.998100 2.18100 0.998929 2.18150 0.999065 2.18200 0.998138 2.18250 0.991366 2.18300 0.971275 2.18350 0.960062 2.18400 0.969308 2.18450 0.964133 2.18500 0.959404 2.18550 0.981925 2.18600 0.996161 2.18650 0.996612 2.18700 0.991580 2.18750 0.986871 2.18800 0.984696 2.18850 0.989740 2.18900 0.994335 2.18950 0.991143 2.19000 0.988768 2.19050 0.987144 2.19100 0.993016 2.19150 0.998238 2.19200 0.999085 2.19250 0.999107 2.19300 0.999104 2.19350 0.998684 2.19400 0.993953 2.19450 0.979371 2.19500 0.972930 2.19550 0.986936 2.19600 0.995153 2.19650 0.993627 2.19700 0.993788 2.19750 0.989778 2.19800 0.969747 2.19850 0.958481 2.19900 0.959772 2.19950 0.936635 2.20000 0.904162 2.20050 0.908416 2.20100 0.952181 2.20150 0.968994 2.20200 0.961742 2.20250 0.961983 2.20300 0.972796 2.20350 0.982878 2.20400 0.981270 2.20450 0.966098 2.20500 0.958580 2.20550 0.976865 2.20600 0.989844 2.20650 0.991440 2.20700 0.990608 2.20750 0.992038 2.20800 0.995536 2.20850 0.998074 2.20900 0.998375 2.20950 0.998150 2.21000 0.996682 2.21050 0.993781 2.21100 0.989655 2.21150 0.991445 2.21200 0.997280 2.21250 0.998798 2.21300 0.998861 2.21350 0.998463 2.21400 0.996766 2.21450 0.993372 2.21500 0.991418 2.21550 0.987521 2.21600 0.971367 2.21650 0.963606 2.21700 0.980334 2.21750 0.994027 2.21800 0.994988 2.21850 0.994404 2.21900 0.996066 2.21950 0.996900 2.22000 0.995557 2.22050 0.991455 2.22100 0.979036 2.22150 0.963817 2.22200 0.973502 2.22250 0.989688 2.22300 0.994425 2.22350 0.993828 2.22400 0.992148 2.22450 0.990939 2.22500 0.988860 2.22550 0.988802 2.22600 0.985367 2.22650 0.973255 2.22700 0.967869 2.22750 0.977822 2.22800 0.988426 2.22850 0.992117 2.22900 0.993506 2.22950 0.994752 2.23000 0.994718 2.23050 0.991141 2.23100 0.982207 2.23150 0.966953 2.23200 0.960961 2.23250 0.964296 2.23300 0.976815 2.23350 0.978526 2.23400 0.981932 2.23450 0.987778 2.23500 0.987859 2.23550 0.985300 2.23600 0.977612 2.23650 0.972477 2.23700 0.975502 2.23750 0.971001 2.23800 0.971979 2.23850 0.984863 2.23900 0.991374 2.23950 0.987579 2.24000 0.979151 2.24050 0.974810 2.24100 0.974257 2.24150 0.972455 2.24200 0.981710 2.24250 0.984980 2.24300 0.983033 2.24350 0.973910 2.24400 0.961679 2.24450 0.973755 2.24500 0.989035 2.24550 0.984345 2.24600 0.967868 2.24650 0.956469 2.24700 0.944037 2.24750 0.955394 2.24800 0.983178 2.24850 0.990925 2.24900 0.986336 2.24950 0.979128 2.25000 0.972989 2.25050 0.969456 2.25100 0.974776 2.25150 0.982455 2.25200 0.979486 2.25250 0.974909 2.25300 0.971611 2.25350 0.958053 2.25400 0.946224 2.25450 0.959704 2.25500 0.971204 2.25550 0.960936 2.25600 0.952730 2.25650 0.943552 2.25700 0.938235 2.25750 0.957662 2.25800 0.967147 2.25850 0.969626 2.25900 0.979161 2.25950 0.978685 2.26000 0.967217 2.26050 0.953147 2.26100 0.930291 2.26150 0.915995 2.26200 0.914730 2.26250 0.940945 2.26300 0.968367 2.26350 0.958359 2.26400 0.950043 2.26450 0.940373 2.26500 0.943509 2.26550 0.974245 2.26600 0.988689 2.26650 0.988223 2.26700 0.979713 2.26750 0.963261 2.26800 0.932784 2.26850 0.899877 2.26900 0.921958 2.26950 0.963219 2.27000 0.958127 2.27050 0.947918 2.27100 0.969175 2.27150 0.969777 2.27200 0.948723 2.27250 0.935947 2.27300 0.959925 2.27350 0.974447 2.27400 0.979633 2.27450 0.968265 2.27500 0.927595 2.27550 0.872848 2.27600 0.881464 2.27650 0.921859 2.27700 0.935959 2.27750 0.954508 2.27800 0.971933 2.27850 0.978674 2.27900 0.987011 2.27950 0.990485 2.28000 0.975465 2.28050 0.934239 2.28100 0.930259 2.28150 0.963877 2.28200 0.967130 2.28250 0.918723 2.28300 0.880540 2.28350 0.910531 2.28400 0.918914 2.28450 0.946450 2.28500 0.946472 2.28550 0.940926 2.28600 0.970154 2.28650 0.977190 2.28700 0.975369 2.28750 0.974304 2.28800 0.974016 2.28850 0.976595 2.28900 0.953052 2.28950 0.896540 2.29000 0.882249 2.29050 0.932530 2.29100 0.963816 2.29150 0.967471 2.29200 0.951108 2.29250 0.937592 2.29300 0.959022 2.29350 0.956944 2.29400 0.947137 2.29450 0.941898 2.29500 0.919981 2.29550 0.910921 2.29600 0.915713 2.29650 0.946151 2.29700 0.973374 2.29750 0.974277 2.29800 0.931860 2.29850 0.891625 2.29900 0.903607 2.29950 0.918032 2.30000 0.904420 2.30050 0.893896 2.30100 0.923603 2.30150 0.956751 2.30200 0.954860 2.30250 0.945079 2.30300 0.939584 2.30350 0.913138 2.30400 0.895780 2.30450 0.901001 2.30500 0.924654 2.30550 0.938239 2.30600 0.943688 2.30650 0.944019 2.30700 0.934959 2.30750 0.913497 2.30800 0.938670 2.30850 0.965929 2.30900 0.964231 2.30950 0.963499 2.31000 0.973815 2.31050 0.987864 2.31100 0.994375 2.31150 0.988675 2.31200 0.975269 2.31250 0.980292 2.31300 0.993809 2.31350 0.997085 2.31400 0.997118 2.31450 0.996271 2.31500 0.992056 2.31550 0.968407 2.31600 0.905904 2.31650 0.813697 2.31700 0.741154 2.31750 0.762081 2.31800 0.807843 2.31850 0.853012 2.31900 0.898999 2.31950 0.917139 2.32000 0.927846 2.32050 0.936867 2.32100 0.942031 2.32150 0.938965 2.32200 0.925964 2.32250 0.929202 2.32300 0.938148 2.32350 0.950685 2.32400 0.963227 2.32450 0.975641 2.32500 0.960537 2.32550 0.940908 2.32600 0.953554 2.32650 0.962983 2.32700 0.978302 2.32750 0.984367 2.32800 0.957858 2.32850 0.914691 2.32900 0.927635 2.32950 0.958990 2.33000 0.977835 2.33050 0.972343 2.33100 0.953465 2.33150 0.932278 2.33200 0.926684 2.33250 0.946931 2.33300 0.965216 2.33350 0.963410 2.33400 0.946427 2.33450 0.939448 2.33500 0.937441 2.33550 0.946345 2.33600 0.941163 2.33650 0.953312 2.33700 0.969662 2.33750 0.964027 2.33800 0.933469 2.33850 0.908774 2.33900 0.917623 2.33950 0.899535 2.34000 0.849853 2.34050 0.859787 2.34100 0.886399 2.34150 0.902342 2.34200 0.923683 2.34250 0.939823 2.34300 0.939325 2.34350 0.927649 2.34400 0.937298 2.34450 0.929117 2.34500 0.905541 2.34550 0.911958 2.34600 0.922879 2.34650 0.908866 2.34700 0.901061 2.34750 0.886318 2.34800 0.861221 2.34850 0.883943 2.34900 0.913045 2.34950 0.905750 2.35000 0.884418 2.35050 0.846103 2.35100 0.848492 2.35150 0.872819 2.35200 0.858068 2.35250 0.855305 2.35300 0.877964 2.35350 0.882434 2.35400 0.926982 2.35450 0.943698 2.35500 0.931073 2.35550 0.882179 2.35600 0.833487 2.35650 0.895704 2.35700 0.941379 2.35750 0.942649 2.35800 0.898892 2.35850 0.811805 2.35900 0.839390 2.35950 0.948189 2.36000 0.985150 2.36050 0.984192 2.36100 0.962659 2.36150 0.916173 2.36200 0.931674 2.36250 0.974088 2.36300 0.977077 2.36350 0.946484 2.36400 0.908475 2.36450 0.921383 2.36500 0.959631 2.36550 0.973592 2.36600 0.954171 2.36650 0.942535 2.36700 0.907596 2.36750 0.858218 2.36800 0.896493 2.36850 0.921080 2.36900 0.912123 2.36950 0.903999 2.37000 0.782742 2.37050 0.628936 2.37100 0.618359 2.37150 0.672108 2.37200 0.764363 2.37250 0.828775 2.37300 0.876091 2.37350 0.914529 2.37400 0.937871 2.37450 0.944311 2.37500 0.937856 2.37550 0.945167 2.37600 0.916955 2.37650 0.844378 2.37700 0.847027 2.37750 0.888169 2.37800 0.848238 2.37850 0.868077 2.37900 0.941370 2.37950 0.935210 2.38000 0.897067 2.38050 0.899442 2.38100 0.945207 2.38150 0.975747 2.38200 0.975352 2.38250 0.936696 2.38300 0.843852 2.38350 0.777218 2.38400 0.777523 2.38450 0.861230 2.38500 0.938573 2.38550 0.912114 2.38600 0.837304 2.38650 0.854894 2.38700 0.908215 2.38750 0.895066 2.38800 0.848374 2.38850 0.893604 2.38900 0.958882 2.38950 0.944031 2.39000 0.918148 2.39050 0.919918 2.39100 0.847690 2.39150 0.780647 2.39200 0.880173 2.39250 0.953289 2.39300 0.948335 2.39350 0.944433 2.39400 0.938653 2.39450 0.923676 2.39500 0.932864 2.39550 0.939643 2.39600 0.918608 2.39650 0.830222 2.39700 0.808714 2.39750 0.887448 2.39800 0.938626 2.39850 0.952752 2.39900 0.957573 2.39950 0.948458 2.40000 0.911583 2.40050 0.892039 2.40100 0.943568 2.40150 0.969052 2.40200 0.962411 2.40250 0.965630 2.40300 0.961690 2.40350 0.919130 2.40400 0.805536 2.40450 0.800888 2.40500 0.913866 2.40550 0.938582 2.40600 0.923120 2.40650 0.881429 2.40700 0.815630 2.40750 0.824301 2.40800 0.888851 2.40850 0.952288 2.40900 0.946111 2.40950 0.862613 2.41000 0.821815 2.41050 0.912370 2.41100 0.955188 2.41150 0.911764 2.41200 0.898795 2.41250 0.945079 2.41300 0.963990 2.41350 0.921457 2.41400 0.833355 2.41450 0.827269 2.41500 0.890472 2.41550 0.782933 2.41600 0.623885 2.41650 0.744560 2.41700 0.913734 2.41750 0.942810 2.41800 0.857536 2.41850 0.669586 2.41900 0.568041 2.41950 0.669188 2.42000 0.867286 2.42050 0.956546 2.42100 0.970454 2.42150 0.958259 2.42200 0.932975 2.42250 0.944180 2.42300 0.958509 2.42350 0.913936 2.42400 0.828977 2.42450 0.862245 2.42500 0.918503 2.42550 0.923287 2.42600 0.861337 2.42650 0.827370 2.42700 0.914487 2.42750 0.958499 2.42800 0.969334 2.42850 0.977016 2.42900 0.974032 2.42950 0.968545 2.43000 0.967515 2.43050 0.974601 2.43100 0.977684 2.43150 0.977794 2.43200 0.974221 2.43250 0.939977 2.43300 0.847345 2.43350 0.817170 2.43400 0.885587 2.43450 0.819872 2.43500 0.587468 2.43550 0.423735 2.43600 0.450873 2.43650 0.573688 2.43700 0.803187 2.43750 0.933509 2.43800 0.963263 2.43850 0.972448 2.43900 0.977902 2.43950 0.980418 2.44000 0.978676 2.44050 0.977528 2.44100 0.979516 2.44150 0.979711 2.44200 0.970300 2.44250 0.926485 2.44300 0.891714 2.44350 0.932442 2.44400 0.963169 2.44450 0.961123 2.44500 0.925630 2.44550 0.767493 2.44600 0.557422 2.44650 0.622374 2.44700 0.690174 2.44750 0.691813 2.44800 0.763652 2.44850 0.872282 2.44900 0.938356 2.44950 0.877507 2.45000 0.694134 2.45050 0.607990 2.45100 0.548934 2.45150 0.564526 2.45200 0.787022 2.45250 0.923653 2.45300 0.933530 2.45350 0.855869 2.45400 0.768594 2.45450 0.687864 2.45500 0.758187 2.45550 0.915924 2.45600 0.960811 2.45650 0.957282 2.45700 0.962986 2.45750 0.972367 2.45800 0.975347 2.45850 0.973874 2.45900 0.965308 2.45950 0.960711 2.46000 0.970417 2.46050 0.974221 2.46100 0.964829 2.46150 0.940391 2.46200 0.833780 2.46250 0.592366 2.46300 0.578570 2.46350 0.808207 2.46400 0.878948 2.46450 0.870326 2.46500 0.930417 2.46550 0.963790 2.46600 0.960592 2.46650 0.959658 2.46700 0.934625 2.46750 0.832232 2.46800 0.803242 2.46850 0.893985 2.46900 0.940032 2.46950 0.953463 2.47000 0.947762 2.47050 0.931176 2.47100 0.882532 2.47150 0.707245 2.47200 0.386032 2.47250 0.230481 2.47300 0.387630 2.47350 0.702714 2.47400 0.892005 2.47450 0.931527 2.47500 0.911206 2.47550 0.900902 2.47600 0.931180 2.47650 0.951266 2.47700 0.923727 2.47750 0.888870 2.47800 0.891582 2.47850 0.868052 2.47900 0.864266 2.47950 0.822565 2.48000 0.708235 2.48050 0.596334 2.48100 0.577294 2.48150 0.572595 2.48200 0.727780 2.48250 0.858553 2.48300 0.841080 2.48350 0.683533 2.48400 0.415841 2.48450 0.417153 2.48500 0.700256 2.48550 0.861003 2.48600 0.862802 2.48650 0.760302 2.48700 0.625782 2.48750 0.462789 2.48800 0.320126 2.48850 0.454396 2.48900 0.669309 2.48950 0.755920 2.49000 0.798100 2.49050 0.788546 2.49100 0.787778 2.49150 0.697324 2.49200 0.528836 2.49250 0.589910 2.49300 0.698714 2.49350 0.706435 2.49400 0.566537 2.49450 0.289364 2.49500 0.250413 2.49550 0.512559 2.49600 0.739590 2.49650 0.762701 2.49700 0.806569 2.49750 0.839128 2.49800 0.816082 2.49850 0.836141 2.49900 0.825863 2.49950 0.812290 2.50000 0.859637 2.50050 0.880342 2.50100 0.881045 2.50150 0.861771 2.50200 0.800617 2.50250 0.629906 2.50300 0.392314 2.50350 0.484261 2.50400 0.674368 2.50450 0.648110 2.50500 0.632014 2.50550 0.494431 2.50600 0.358292 2.50650 0.528584 2.50700 0.782765 2.50750 0.902050 2.50800 0.890074 2.50850 0.768570 2.50900 0.651085 2.50950 0.666175 2.51000 0.697582 2.51050 0.470949 2.51100 0.277624 2.51150 0.433166 2.51200 0.563810 2.51250 0.544119 2.51300 0.578282 2.51350 0.710885 2.51400 0.776085 2.51450 0.579143 2.51500 0.337549 2.51550 0.248457 2.51600 0.173012 2.51650 0.146515 2.51700 0.140888 2.51750 0.206415 2.51800 0.462253 2.51850 0.621524 2.51900 0.526686 2.51950 0.416277 2.52000 0.530547 2.52050 0.642531 2.52100 0.770596 2.52150 0.785093 2.52200 0.633073 2.52250 0.541423 2.52300 0.597740 2.52350 0.521539 2.52400 0.292984 2.52450 0.330189 2.52500 0.435927 2.52550 0.405049 2.52600 0.382376 2.52650 0.316188 2.52700 0.275948 2.52750 0.336176 2.52800 0.414042 2.52850 0.426600 2.52900 0.371080 2.52950 0.173469 2.53000 0.129927 2.53050 0.217549 2.53100 0.159081 2.53150 5.42837E-02 2.53200 7.36881E-02 2.53250 1.04730E-01 2.53300 8.87523E-02 2.53350 0.214393 2.53400 0.448291 2.53450 0.481634 2.53500 0.281284 2.53550 0.106706 2.53600 4.10849E-02 2.53650 5.17446E-02 2.53700 0.168290 2.53750 0.289895 2.53800 0.361603 2.53850 0.307732 2.53900 0.230749 2.53950 0.377892 2.54000 0.566428 2.54050 0.509028 2.54100 0.277028 2.54150 0.132388 2.54200 1.00939E-01 2.54250 7.86398E-02 2.54300 3.17442E-02 2.54350 2.29503E-02 2.54400 1.71456E-02 2.54450 6.21574E-03 2.54500 1.48122E-02 2.54550 6.53314E-02 2.54600 0.167322 2.54650 0.211016 2.54700 0.119869 2.54750 2.79078E-02 2.54800 3.07616E-03 2.54850 1.27496E-02 2.54900 7.94431E-02 2.54950 0.180338 2.55000 0.164326 2.55050 6.64017E-02 2.55100 2.36729E-02 2.55150 5.29800E-02 2.55200 7.37960E-02 2.55250 3.58386E-02 2.55300 1.27467E-02 2.55350 6.23339E-02 2.55400 0.200682 2.55450 0.286113 2.55500 0.287589 2.55550 0.344045 2.55600 0.433375 2.55650 0.468968 2.55700 0.392275 2.55750 0.310678 2.55800 0.281874 2.55850 0.236204 2.55900 0.155075 2.55950 6.06339E-02 2.56000 1.12537E-02 2.56050 8.22040E-04 2.56100 1.99218E-05 2.56150 5.31712E-08 2.56200 7.04277E-08 2.56250 2.83073E-06 2.56300 3.04938E-05 2.56350 8.86269E-05 2.56400 6.98677E-05 2.56450 4.31127E-05 2.56500 8.69180E-04 2.56550 8.53199E-03 2.56600 3.50122E-02 2.56650 7.84159E-02 2.56700 9.29130E-02 2.56750 5.37144E-02 2.56800 1.51688E-02 2.56850 2.63614E-03 2.56900 4.80369E-04 2.56950 4.89174E-05 2.57000 1.90489E-04 2.57050 2.33284E-03 2.57100 1.08536E-02 2.57150 1.96635E-02 2.57200 1.53896E-02 2.57250 5.76536E-03 2.57300 9.72730E-04 2.57350 7.02864E-05 2.57400 6.76498E-05 2.57450 1.50234E-04 2.57500 4.64435E-04 2.57550 1.48849E-03 2.57600 1.51976E-03 2.57650 4.39861E-04 2.57700 1.25036E-04 2.57750 1.49480E-03 2.57800 9.01993E-03 2.57850 2.27736E-02 2.57900 2.94524E-02 2.57950 2.04180E-02 2.58000 7.44139E-03 2.58050 1.78394E-03 2.58100 2.24446E-04 2.58150 7.36249E-05 2.58200 2.50571E-04 2.58250 2.92329E-04 2.58300 1.00811E-04 2.58350 9.96234E-06 2.58400 5.73564E-07 2.58450 1.01644E-05 2.58500 1.04099E-04 2.58550 3.47114E-04 2.58600 3.83984E-04 2.58650 1.34896E-04 2.58700 1.41135E-05 2.58750 4.15259E-07 2.58800 7.73058E-10 2.58850 6.14685E-08 2.58900 5.82730E-06 2.58950 1.37129E-04 2.59000 1.03550E-03 2.59050 2.55482E-03 2.59100 2.28052E-03 2.59150 1.26387E-03 2.59200 5.39003E-04 2.59250 8.89576E-05 2.59300 4.33847E-06 2.59350 5.14225E-08 2.59400 0. 2.59450 0. 2.59500 0. 2.59550 0. 2.59600 0. 2.59650 1.29516E-08 2.59700 2.36268E-06 2.59750 8.43206E-05 2.59800 1.03404E-03 2.59850 4.75963E-03 2.59900 9.11354E-03 2.59950 7.86873E-03 2.60000 4.64774E-03 2.60050 2.75123E-03 2.60100 7.50119E-04 2.60150 6.49983E-05 2.60200 5.84982E-06 2.60250 9.52441E-06 2.60300 5.50399E-06 2.60350 8.13932E-07 2.60400 3.07099E-08 2.60450 1.22151E-10 2.60500 0. 2.60550 0. 2.60600 0. 2.60650 0. 2.60700 0. 2.60750 0. 2.60800 0. 2.60850 0. 2.60900 2.69301E-09 2.60950 5.82116E-07 2.61000 2.21126E-05 2.61050 2.66238E-04 2.61100 1.08433E-03 2.61150 1.57924E-03 2.61200 7.90820E-04 2.61250 1.22123E-04 2.61300 5.76671E-06 2.61350 9.44120E-06 2.61400 5.49874E-05 2.61450 8.50224E-05 2.61500 3.46907E-05 2.61550 3.73304E-06 2.61600 1.05655E-07 2.61650 1.85436E-11 2.61700 0. 2.61750 0. 2.61800 0. 2.61850 0. 2.61900 0. 2.61950 0. 2.62000 0. 2.62050 4.94590E-08 2.62100 3.39033E-06 2.62150 6.77962E-05 2.62200 5.25843E-04 2.62250 2.23750E-03 2.62300 6.41913E-03 2.62350 1.08452E-02 2.62400 1.01490E-02 2.62450 5.37306E-03 2.62500 1.82351E-03 2.62550 4.08479E-04 2.62600 4.60204E-05 2.62650 1.97978E-06 2.62700 2.37133E-08 2.62750 0. 2.62800 0. 2.62850 0. 2.62900 0. 2.62950 0. 2.63000 0. 2.63050 0. 2.63100 0. 2.63150 0. 2.63200 0. 2.63250 0. 2.63300 8.50751E-10 2.63350 1.08282E-06 2.63400 6.21755E-05 2.63450 1.10571E-03 2.63500 7.21401E-03 2.63550 2.18695E-02 2.63600 4.42830E-02 2.63650 7.04138E-02 2.63700 0.123779 2.63750 0.185466 2.63800 0.198941 2.63850 0.139015 2.63900 9.40803E-02 2.63950 0.135771 2.64000 0.128158 2.64050 5.31014E-02 2.64100 1.37403E-02 2.64150 3.48079E-03 2.64200 1.29076E-03 2.64250 9.07881E-03 2.64300 3.61677E-02 2.64350 5.35005E-02 2.64400 3.25188E-02 2.64450 1.32081E-02 2.64500 5.22988E-03 2.64550 9.54610E-04 2.64600 9.78894E-05 2.64650 1.05753E-03 2.64700 1.02039E-02 2.64750 4.47219E-02 2.64800 0.105665 2.64850 0.158865 2.64900 0.167898 2.64950 0.124930 2.65000 9.27436E-02 2.65050 7.01388E-02 2.65100 2.98892E-02 2.65150 5.70136E-03 2.65200 4.31643E-04 2.65250 1.46558E-05 2.65300 5.84877E-05 2.65350 5.05101E-04 2.65400 1.48995E-03 2.65450 1.45131E-03 2.65500 4.61932E-04 2.65550 7.52919E-05 2.65600 3.15886E-04 2.65650 1.25352E-03 2.65700 2.92523E-03 2.65750 3.58311E-03 2.65800 2.34480E-03 2.65850 1.58906E-03 2.65900 5.66940E-04 2.65950 6.46782E-05 2.66000 2.23259E-06 2.66050 1.33573E-08 2.66100 0. 2.66150 0. 2.66200 0. 2.66250 0. 2.66300 0. 2.66350 0. 2.66400 0. 2.66450 0. 2.66500 0. 2.66550 0. 2.66600 0. 2.66650 0. 2.66700 0. 2.66750 0. 2.66800 0. 2.66850 0. 2.66900 0. 2.66950 0. 2.67000 0. 2.67050 0. 2.67100 0. 2.67150 0. 2.67200 0. 2.67250 0. 2.67300 0. 2.67350 0. 2.67400 0. 2.67450 0. 2.67500 0. 2.67550 0. 2.67600 0. 2.67650 0. 2.67700 0. 2.67750 0. 2.67800 0. 2.67850 0. 2.67900 0. 2.67950 0. 2.68000 0. 2.68050 0. 2.68100 0. 2.68150 0. 2.68200 0. 2.68250 0. 2.68300 0. 2.68350 0. 2.68400 0. 2.68450 0. 2.68500 0. 2.68550 0. 2.68600 0. 2.68650 0. 2.68700 0. 2.68750 0. 2.68800 0. 2.68850 0. 2.68900 0. 2.68950 0. 2.69000 0. 2.69050 0. 2.69100 0. 2.69150 0. 2.69200 0. 2.69250 0. 2.69300 0. 2.69350 0. 2.69400 0. 2.69450 0. 2.69500 0. 2.69550 0. 2.69600 0. 2.69650 0. 2.69700 0. 2.69750 0. 2.69800 0. 2.69850 0. 2.69900 0. 2.69950 0. 2.70000 0. 2.70050 0. 2.70100 0. 2.70150 0. 2.70200 0. 2.70250 0. 2.70300 0. 2.70350 0. 2.70400 0. 2.70450 0. 2.70500 0. 2.70550 0. 2.70600 0. 2.70650 0. 2.70700 0. 2.70750 0. 2.70800 0. 2.70850 0. 2.70900 0. 2.70950 0. 2.71000 0. 2.71050 0. 2.71100 0. 2.71150 7.46271E-09 2.71200 4.08152E-07 2.71250 5.77449E-06 2.71300 2.24869E-05 2.71350 3.24894E-05 2.71400 1.18316E-04 2.71450 4.45578E-04 2.71500 6.04868E-04 2.71550 7.04037E-04 2.71600 9.45985E-04 2.71650 4.97751E-04 2.71700 8.49472E-05 2.71750 4.81223E-06 2.71800 7.95806E-08 2.71850 0. 2.71900 0. 2.71950 0. 2.72000 0. 2.72050 0. 2.72100 0. 2.72150 0. 2.72200 0. 2.72250 0. 2.72300 0. 2.72350 0. 2.72400 0. 2.72450 0. 2.72500 3.43786E-09 2.72550 8.05882E-07 2.72600 2.99542E-05 2.72650 3.76366E-04 2.72700 1.75447E-03 2.72750 3.64936E-03 2.72800 7.77668E-03 2.72850 2.18814E-02 2.72900 3.50740E-02 2.72950 3.32743E-02 2.73000 3.91123E-02 2.73050 4.55297E-02 2.73100 3.12457E-02 2.73150 1.84191E-02 2.73200 7.97910E-03 2.73250 7.04690E-03 2.73300 1.02623E-02 2.73350 6.63580E-03 2.73400 1.88625E-03 2.73450 2.15843E-04 2.73500 9.37679E-06 2.73550 2.99425E-05 2.73600 2.76024E-04 2.73650 8.22255E-04 2.73700 7.91828E-04 2.73750 2.57058E-04 2.73800 3.09913E-05 2.73850 1.38114E-06 2.73900 1.82239E-08 2.73950 0. 2.74000 0. 2.74050 0. 2.74100 0. 2.74150 0. 2.74200 2.85219E-07 2.74250 1.67434E-05 2.74300 2.59645E-04 2.74350 1.12757E-03 2.74400 1.50164E-03 2.74450 7.08162E-04 2.74500 1.34187E-04 2.74550 7.26413E-05 2.74600 8.19766E-04 2.74650 3.45077E-03 2.74700 4.11042E-03 2.74750 1.62636E-03 2.74800 4.64222E-04 2.74850 9.91428E-05 2.74900 3.97107E-05 2.74950 1.12449E-04 2.75000 2.72961E-04 2.75050 2.25304E-04 2.75100 5.04539E-05 2.75150 2.98142E-06 2.75200 4.50252E-08 2.75250 0. 2.75300 0. 2.75350 0. 2.75400 0. 2.75450 0. 2.75500 0. 2.75550 0. 2.75600 0. 2.75650 0. 2.75700 0. 2.75750 0. 2.75800 0. 2.75850 0. 2.75900 0. 2.75950 0. 2.76000 0. 2.76050 0. 2.76100 0. 2.76150 0. 2.76200 0. 2.76250 0. 2.76300 0. 2.76350 0. 2.76400 0. 2.76450 0. 2.76500 0. 2.76550 0. 2.76600 0. 2.76650 0. 2.76700 0. 2.76750 0. 2.76800 0. 2.76850 0. 2.76900 0. 2.76950 0. 2.77000 0. 2.77050 0. 2.77100 0. 2.77150 0. 2.77200 0. 2.77250 0. 2.77300 2.66520E-10 2.77350 2.00496E-08 2.77400 3.77809E-07 2.77450 1.78330E-06 2.77500 2.10791E-06 2.77550 6.24441E-07 2.77600 4.62515E-08 2.77650 8.58907E-10 2.77700 0. 2.77750 0. 2.77800 0. 2.77850 0. 2.77900 0. 2.77950 0. 2.78000 0. 2.78050 0. 2.78100 0. 2.78150 0. 2.78200 0. 2.78250 8.38924E-08 2.78300 3.80616E-06 2.78350 4.40936E-05 2.78400 1.31333E-04 2.78450 1.02332E-04 2.78500 2.46771E-05 2.78550 1.28573E-05 2.78600 1.06914E-05 2.78650 2.52232E-06 2.78700 1.52624E-07 2.78750 2.36056E-09 2.78800 1.07191E-07 2.78850 8.13460E-06 2.78900 1.52285E-04 2.78950 7.39647E-04 2.79000 9.33600E-04 2.79050 4.01153E-04 2.79100 1.25820E-03 2.79150 4.57812E-03 2.79200 6.13691E-03 2.79250 7.91617E-03 2.79300 8.07466E-03 2.79350 4.18372E-03 2.79400 1.12827E-02 2.79450 2.18905E-02 2.79500 1.30209E-02 2.79550 4.06567E-03 2.79600 9.17343E-03 2.79650 1.58233E-02 2.79700 7.69597E-03 2.79750 1.12372E-03 2.79800 8.26521E-04 2.79850 7.00086E-03 2.79900 2.90093E-02 2.79950 5.35208E-02 2.80000 3.99908E-02 2.80050 1.41603E-02 2.80100 8.16306E-03 2.80150 5.25740E-03 2.80200 1.37133E-03 2.80250 1.26905E-04 2.80300 3.67799E-06 2.80350 2.16667E-08 2.80400 4.15539E-07 2.80450 3.28079E-06 2.80500 1.15962E-05 2.80550 3.18687E-05 2.80600 1.51171E-04 2.80650 6.66623E-04 2.80700 3.75033E-03 2.80750 1.17382E-02 2.80800 1.12231E-02 2.80850 5.55076E-03 2.80900 1.34713E-02 2.80950 3.77404E-02 2.81000 5.26864E-02 2.81050 4.92516E-02 2.81100 3.47226E-02 2.81150 4.87988E-02 2.81200 0.125513 2.81250 0.158933 2.81300 1.01133E-01 2.81350 4.69394E-02 2.81400 2.35216E-02 2.81450 1.63181E-02 2.81500 7.15731E-03 2.81550 1.02543E-02 2.81600 2.30568E-02 2.81650 3.81845E-02 2.81700 6.91011E-02 2.81750 7.17183E-02 2.81800 3.48098E-02 2.81850 1.57872E-02 2.81900 1.17363E-02 2.81950 2.27773E-02 2.82000 3.28932E-02 2.82050 1.39404E-02 2.82100 2.00513E-03 2.82150 9.62986E-04 2.82200 6.04452E-03 2.82250 4.37365E-02 2.82300 0.138730 2.82350 0.224456 2.82400 0.249512 2.82450 0.192406 2.82500 0.172525 2.82550 0.190434 2.82600 0.289760 2.82650 0.308545 2.82700 0.165275 2.82750 6.59645E-02 2.82800 0.117027 2.82850 0.277408 2.82900 0.348433 2.82950 0.405702 2.83000 0.453038 2.83050 0.433093 2.83100 0.406940 2.83150 0.220348 2.83200 6.20016E-02 2.83250 4.59858E-02 2.83300 5.01135E-02 2.83350 2.19248E-02 2.83400 4.36578E-03 2.83450 1.49209E-03 2.83500 3.76535E-03 2.83550 2.38627E-02 2.83600 6.65644E-02 2.83650 9.23693E-02 2.83700 7.02928E-02 2.83750 3.86315E-02 2.83800 2.22160E-02 2.83850 1.25131E-02 2.83900 4.10920E-02 2.83950 0.113036 2.84000 0.209938 2.84050 0.289646 2.84100 0.200746 2.84150 1.02646E-01 2.84200 0.128186 2.84250 0.228789 2.84300 0.346488 2.84350 0.448027 2.84400 0.513021 2.84450 0.581912 2.84500 0.472795 2.84550 0.301061 2.84600 0.224535 2.84650 0.178578 2.84700 0.168988 2.84750 0.112529 2.84800 9.13010E-02 2.84850 1.00960E-01 2.84900 6.80385E-02 2.84950 3.76179E-02 2.85000 0.118412 2.85050 0.286894 2.85100 0.387376 2.85150 0.332130 2.85200 0.204127 2.85250 0.122805 2.85300 6.86505E-02 2.85350 2.26740E-02 2.85400 1.05554E-02 2.85450 5.94908E-03 2.85500 1.02322E-02 2.85550 2.43368E-02 2.85600 7.77783E-02 2.85650 0.204824 2.85700 0.272392 2.85750 0.319424 2.85800 0.392779 2.85850 0.412704 2.85900 0.336409 2.85950 0.168226 2.86000 0.105973 2.86050 0.160466 2.86100 0.184883 2.86150 0.323649 2.86200 0.474922 2.86250 0.496971 2.86300 0.544977 2.86350 0.548370 2.86400 0.442921 2.86450 0.366866 2.86500 0.469400 2.86550 0.558558 2.86600 0.450554 2.86650 0.233590 2.86700 0.175106 2.86750 0.305468 2.86800 0.320947 2.86850 0.282038 2.86900 0.299453 2.86950 0.344015 2.87000 0.385138 2.87050 0.387361 2.87100 0.269768 2.87150 9.75122E-02 2.87200 6.28070E-02 2.87250 9.56696E-02 2.87300 0.126041 2.87350 0.286362 2.87400 0.478362 2.87450 0.649139 2.87500 0.729783 2.87550 0.721234 2.87600 0.692020 2.87650 0.634344 2.87700 0.469409 2.87750 0.235782 2.87800 0.262597 2.87850 0.400127 2.87900 0.403897 2.87950 0.540406 2.88000 0.642782 2.88050 0.616171 2.88100 0.542903 2.88150 0.441903 2.88200 0.546196 2.88250 0.599684 2.88300 0.524222 2.88350 0.477117 2.88400 0.346084 2.88450 0.289478 2.88500 0.434816 2.88550 0.616555 2.88600 0.654299 2.88650 0.620983 2.88700 0.575662 2.88750 0.346760 2.88800 0.146877 2.88850 0.135701 2.88900 0.154779 2.88950 0.283967 2.89000 0.492082 2.89050 0.673564 2.89100 0.638540 2.89150 0.547495 2.89200 0.524884 2.89250 0.575575 2.89300 0.549092 2.89350 0.451469 2.89400 0.585078 2.89450 0.782930 2.89500 0.832595 2.89550 0.824502 2.89600 0.823092 2.89650 0.842020 2.89700 0.851538 2.89750 0.848143 2.89800 0.819554 2.89850 0.767634 2.89900 0.681430 2.89950 0.436780 2.90000 0.190966 2.90050 8.04939E-02 2.90100 5.22625E-02 2.90150 0.177364 2.90200 0.366965 2.90250 0.439223 2.90300 0.562444 2.90350 0.635965 2.90400 0.454733 2.90450 0.179353 2.90500 0.133475 2.90550 0.350234 2.90600 0.504669 2.90650 0.449374 2.90700 0.481131 2.90750 0.620807 2.90800 0.614649 2.90850 0.554749 2.90900 0.648051 2.90950 0.759177 2.91000 0.772661 2.91050 0.811187 2.91100 0.891973 2.91150 0.906852 2.91200 0.843808 2.91250 0.708414 2.91300 0.549391 2.91350 0.593320 2.91400 0.532625 2.91450 0.345238 2.91500 0.495452 2.91550 0.769385 2.91600 0.864587 2.91650 0.777137 2.91700 0.555704 2.91750 0.560811 2.91800 0.767043 2.91850 0.875260 2.91900 0.912828 2.91950 0.913540 2.92000 0.850900 2.92050 0.738098 2.92100 0.602366 2.92150 0.410188 2.92200 0.198998 2.92250 0.133223 2.92300 0.149814 2.92350 8.80086E-02 2.92400 0.106390 2.92450 0.278048 2.92500 0.421402 2.92550 0.553195 2.92600 0.759390 2.92650 0.855360 2.92700 0.895248 2.92750 0.894276 2.92800 0.896422 2.92850 0.919440 2.92900 0.878229 2.92950 0.728081 2.93000 0.634454 2.93050 0.775061 2.93100 0.905109 2.93150 0.948339 2.93200 0.952087 2.93250 0.921270 2.93300 0.798097 2.93350 0.660756 2.93400 0.776352 2.93450 0.902821 2.93500 0.868471 2.93550 0.830116 2.93600 0.901471 2.93650 0.929039 2.93700 0.852116 2.93750 0.612270 2.93800 0.412329 2.93850 0.535266 2.93900 0.582152 2.93950 0.526606 2.94000 0.625587 2.94050 0.752818 2.94100 0.842912 2.94150 0.852848 2.94200 0.776636 2.94250 0.701950 2.94300 0.524318 2.94350 0.293954 2.94400 0.386065 2.94450 0.627858 2.94500 0.813171 2.94550 0.886154 2.94600 0.857624 2.94650 0.694996 2.94700 0.365423 2.94750 0.169441 2.94800 0.328133 2.94850 0.600225 2.94900 0.814623 2.94950 0.901854 2.95000 0.906888 2.95050 0.921669 2.95100 0.934276 2.95150 0.912625 2.95200 0.884223 2.95250 0.827627 2.95300 0.608257 2.95350 0.336159 2.95400 0.329959 2.95450 0.354751 2.95500 0.422713 2.95550 0.546026 2.95600 0.614572 2.95650 0.806454 2.95700 0.912714 2.95750 0.872883 2.95800 0.748171 2.95850 0.755293 2.95900 0.782054 2.95950 0.756888 2.96000 0.789198 2.96050 0.781476 2.96100 0.807608 2.96150 0.856365 2.96200 0.852847 2.96250 0.796962 2.96300 0.704986 2.96350 0.728750 2.96400 0.872661 2.96450 0.942164 2.96500 0.950822 2.96550 0.931343 2.96600 0.836771 2.96650 0.755112 2.96700 0.788955 2.96750 0.716179 2.96800 0.546853 2.96850 0.501973 2.96900 0.501303 2.96950 0.410558 2.97000 0.441363 2.97050 0.335326 2.97100 0.189865 2.97150 0.193803 2.97200 0.204897 2.97250 0.289943 2.97300 0.514177 2.97350 0.547055 2.97400 0.401034 2.97450 0.308716 2.97500 0.468761 2.97550 0.610235 2.97600 0.488289 2.97650 0.448675 2.97700 0.646504 2.97750 0.830525 2.97800 0.809276 2.97850 0.587379 2.97900 0.459506 2.97950 0.475649 2.98000 0.419790 2.98050 0.542393 2.98100 0.642221 2.98150 0.565400 2.98200 0.547219 2.98250 0.740329 2.98300 0.874824 2.98350 0.816548 2.98400 0.731269 2.98450 0.814366 2.98500 0.859664 2.98550 0.878881 2.98600 0.882006 2.98650 0.834952 2.98700 0.867769 2.98750 0.923849 2.98800 0.899121 2.98850 0.823156 2.98900 0.869983 2.98950 0.948497 2.99000 0.967438 2.99050 0.960395 2.99100 0.910352 2.99150 0.816810 2.99200 0.760632 2.99250 0.792270 2.99300 0.822826 2.99350 0.788582 2.99400 0.762209 2.99450 0.786309 2.99500 0.807097 2.99550 0.876326 2.99600 0.806686 2.99650 0.528090 2.99700 0.388324 2.99750 0.623353 2.99800 0.771542 2.99850 0.629423 2.99900 0.585653 2.99950 0.804276 3.00000 0.929646 3.00050 0.942570 3.00100 0.947215 3.00150 0.959289 3.00200 0.958495 3.00250 0.923474 3.00300 0.809707 3.00350 0.766460 3.00400 0.856123 3.00450 0.828962 3.00500 0.587872 3.00550 0.398067 3.00600 0.366364 3.00650 0.473768 3.00700 0.693656 3.00750 0.672690 3.00800 0.597106 3.00850 0.650113 3.00900 0.589731 3.00950 0.602968 3.01000 0.810882 3.01050 0.926795 3.01100 0.943991 3.01150 0.919944 3.01200 0.930757 3.01250 0.920965 3.01300 0.801026 3.01350 0.726411 3.01400 0.741368 3.01450 0.727163 3.01500 0.843827 3.01550 0.932749 3.01600 0.928417 3.01650 0.891623 3.01700 0.823873 3.01750 0.614397 3.01800 0.335038 3.01850 0.345331 3.01900 0.478746 3.01950 0.597397 3.02000 0.703785 3.02050 0.630770 3.02100 0.639982 3.02150 0.583434 3.02200 0.286108 3.02250 0.162272 3.02300 0.378514 3.02350 0.697134 3.02400 0.885020 3.02450 0.936965 3.02500 0.951402 3.02550 0.955922 3.02600 0.944999 3.02650 0.886838 3.02700 0.709125 3.02750 0.648019 3.02800 0.834275 3.02850 0.944127 3.02900 0.944823 3.02950 0.853488 3.03000 0.745710 3.03050 0.815749 3.03100 0.893278 3.03150 0.798308 3.03200 0.526720 3.03250 0.325800 3.03300 0.396732 3.03350 0.641157 3.03400 0.828016 3.03450 0.891895 3.03500 0.805095 3.03550 0.605020 3.03600 0.544893 3.03650 0.405090 3.03700 0.219819 3.03750 0.304842 3.03800 0.375178 3.03850 0.490531 3.03900 0.720450 3.03950 0.809840 3.04000 0.681576 3.04050 0.388538 3.04100 0.377474 3.04150 0.686453 3.04200 0.886411 3.04250 0.914064 3.04300 0.884316 3.04350 0.894000 3.04400 0.876974 3.04450 0.763228 3.04500 0.616480 3.04550 0.549638 3.04600 0.528105 3.04650 0.716960 3.04700 0.882602 3.04750 0.863702 3.04800 0.672534 3.04850 0.429857 3.04900 0.442974 3.04950 0.574243 3.05000 0.662228 3.05050 0.775589 3.05100 0.745945 3.05150 0.488669 3.05200 0.228848 3.05250 0.312351 3.05300 0.514118 3.05350 0.550209 3.05400 0.392843 3.05450 0.173134 3.05500 0.242303 3.05550 0.490824 3.05600 0.561583 3.05650 0.558221 3.05700 0.577959 3.05750 0.580034 3.05800 0.722199 3.05850 0.886487 3.05900 0.946118 3.05950 0.957915 3.06000 0.949489 3.06050 0.910814 3.06100 0.806656 3.06150 0.670848 3.06200 0.628784 3.06250 0.510712 3.06300 0.555131 3.06350 0.734077 3.06400 0.717293 3.06450 0.672201 3.06500 0.783790 3.06550 0.879181 3.06600 0.865132 3.06650 0.671782 3.06700 0.448464 3.06750 0.573635 3.06800 0.810794 3.06850 0.878719 3.06900 0.789325 3.06950 0.583334 3.07000 0.393161 3.07050 0.501137 3.07100 0.683033 3.07150 0.764032 3.07200 0.711630 3.07250 0.435874 3.07300 0.255076 3.07350 0.463547 3.07400 0.752790 3.07450 0.884441 3.07500 0.909237 3.07550 0.904012 3.07600 0.932161 3.07650 0.949669 3.07700 0.926702 3.07750 0.905748 3.07800 0.929640 3.07850 0.933432 3.07900 0.873124 3.07950 0.808809 3.08000 0.795530 3.08050 0.672448 3.08100 0.371499 3.08150 0.120289 3.08200 0.170776 3.08250 0.469912 3.08300 0.702139 3.08350 0.750228 3.08400 0.768389 3.08450 0.695536 3.08500 0.694485 3.08550 0.730422 3.08600 0.543921 3.08650 0.499174 3.08700 0.741970 3.08750 0.878165 3.08800 0.852257 3.08850 0.767106 3.08900 0.591507 3.08950 0.359219 3.09000 0.473547 3.09050 0.756501 3.09100 0.863031 3.09150 0.849549 3.09200 0.806784 3.09250 0.624109 3.09300 0.357632 3.09350 0.345891 3.09400 0.550098 3.09450 0.721833 3.09500 0.691413 3.09550 0.495141 3.09600 0.317136 3.09650 0.461954 3.09700 0.698846 3.09750 0.737930 3.09800 0.521011 3.09850 0.398638 3.09900 0.615255 3.09950 0.744053 3.10000 0.761172 3.10050 0.863257 3.10100 0.934657 3.10150 0.941478 3.10200 0.915058 3.10250 0.862052 3.10300 0.712737 3.10350 0.493344 3.10400 0.543438 3.10450 0.607589 3.10500 0.396912 3.10550 0.232055 3.10600 0.177360 3.10650 0.309727 3.10700 0.614369 3.10750 0.807379 3.10800 0.813168 3.10850 0.632031 3.10900 0.494410 3.10950 0.638528 3.11000 0.690316 3.11050 0.487546 3.11100 0.217134 3.11150 0.223265 3.11200 0.510849 3.11250 0.754984 3.11300 0.850735 3.11350 0.862128 3.11400 0.809751 3.11450 0.653167 3.11500 0.391053 3.11550 0.222784 3.11600 0.406256 3.11650 0.700836 3.11700 0.851481 3.11750 0.883193 3.11800 0.893441 3.11850 0.930656 3.11900 0.951940 3.11950 0.952509 3.12000 0.955137 3.12050 0.960361 3.12100 0.947260 3.12150 0.926064 3.12200 0.935413 3.12250 0.945236 3.12300 0.935014 3.12350 0.920014 3.12400 0.849780 3.12450 0.664957 3.12500 0.471656 3.12550 0.506267 3.12600 0.610925 3.12650 0.441280 3.12700 0.217590 3.12750 0.301135 3.12800 0.427994 3.12850 0.346078 3.12900 0.408404 3.12950 0.698124 3.13000 0.878433 3.13050 0.932687 3.13100 0.948588 3.13150 0.950539 3.13200 0.943832 3.13250 0.915022 3.13300 0.909595 3.13350 0.938049 3.13400 0.953228 3.13450 0.960945 3.13500 0.963910 3.13550 0.967775 3.13600 0.966440 3.13650 0.960633 3.13700 0.954688 3.13750 0.943391 3.13800 0.909201 3.13850 0.790486 3.13900 0.495492 3.13950 0.212668 3.14000 0.308207 3.14050 0.641106 3.14100 0.837688 3.14150 0.851917 3.14200 0.786948 3.14250 0.814790 3.14300 0.903018 3.14350 0.925465 3.14400 0.910338 3.14450 0.836187 3.14500 0.719173 3.14550 0.595188 3.14600 0.340259 3.14650 0.165420 3.14700 0.331235 3.14750 0.636826 3.14800 0.820410 3.14850 0.882916 3.14900 0.862171 3.14950 0.775146 3.15000 0.800311 3.15050 0.905639 3.15100 0.915005 3.15150 0.894647 3.15200 0.929168 3.15250 0.955631 3.15300 0.955992 3.15350 0.936237 3.15400 0.877351 3.15450 0.692855 3.15500 0.573704 3.15550 0.730442 3.15600 0.806578 3.15650 0.679334 3.15700 0.702454 3.15750 0.812218 3.15800 0.828099 3.15850 0.846559 3.15900 0.905479 3.15950 0.921914 3.16000 0.883593 3.16050 0.829500 3.16100 0.872977 3.16150 0.951313 3.16200 0.973224 3.16250 0.973852 3.16300 0.979039 3.16350 0.983354 3.16400 0.983704 3.16450 0.982576 3.16500 0.978883 3.16550 0.969419 3.16600 0.931483 3.16650 0.847291 3.16700 0.810435 3.16750 0.896619 3.16800 0.961779 3.16850 0.963643 3.16900 0.948609 3.16950 0.955114 3.17000 0.973468 3.17050 0.976124 3.17100 0.961186 3.17150 0.926863 3.17200 0.861718 3.17250 0.815011 3.17300 0.741281 3.17350 0.766663 3.17400 0.903275 3.17450 0.934732 3.17500 0.830639 3.17550 0.686659 3.17600 0.748619 3.17650 0.910697 3.17700 0.960585 3.17750 0.958318 3.17800 0.952650 3.17850 0.957153 3.17900 0.966223 3.17950 0.970188 3.18000 0.969132 3.18050 0.955376 3.18100 0.891001 3.18150 0.693657 3.18200 0.592397 3.18250 0.785266 3.18300 0.918368 3.18350 0.876219 3.18400 0.667318 3.18450 0.524348 3.18500 0.698083 3.18550 0.895583 3.18600 0.948551 3.18650 0.952048 3.18700 0.945510 3.18750 0.934421 3.18800 0.930528 3.18850 0.926805 3.18900 0.922542 3.18950 0.895012 3.19000 0.832804 3.19050 0.726206 3.19100 0.489850 3.19150 0.208117 3.19200 0.201690 3.19250 0.417885 3.19300 0.460319 3.19350 0.430033 3.19400 0.653189 3.19450 0.858192 3.19500 0.916293 3.19550 0.910515 3.19600 0.844142 3.19650 0.761989 3.19700 0.695324 3.19750 0.526249 3.19800 0.271525 3.19850 0.195851 3.19900 0.286279 3.19950 0.425516 3.20000 0.559350 3.20050 0.736134 3.20100 0.757112 3.20150 0.542421 3.20200 0.216575 3.20250 8.74721E-02 3.20300 0.223656 3.20350 0.470549 3.20400 0.694308 3.20450 0.788522 3.20500 0.738277 3.20550 0.496497 3.20600 0.209099 3.20650 0.161143 3.20700 0.266429 3.20750 0.495759 3.20800 0.621190 3.20850 0.467155 3.20900 0.234696 3.20950 0.175865 3.21000 0.312157 3.21050 0.313021 3.21100 0.263176 3.21150 0.234222 3.21200 0.240528 3.21250 0.343903 3.21300 0.310191 3.21350 0.467391 3.21400 0.712678 3.21450 0.648611 3.21500 0.435642 3.21550 0.374932 3.21600 0.460313 3.21650 0.638390 3.21700 0.646816 3.21750 0.541501 3.21800 0.429542 3.21850 0.515505 3.21900 0.742962 3.21950 0.804359 3.22000 0.778070 3.22050 0.607875 3.22100 0.306640 3.22150 0.283885 3.22200 0.393062 3.22250 0.264640 3.22300 0.212674 3.22350 0.325291 3.22400 0.301725 3.22450 0.176513 3.22500 0.256840 3.22550 0.340643 3.22600 0.242395 3.22650 0.363061 3.22700 0.653523 3.22750 0.803705 3.22800 0.811319 3.22850 0.670424 3.22900 0.430267 3.22950 0.239733 3.23000 8.46591E-02 3.23050 4.17210E-02 3.23100 0.127076 3.23150 0.298467 3.23200 0.397493 3.23250 0.476625 3.23300 0.718094 3.23350 0.874254 3.23400 0.918314 3.23450 0.931914 3.23500 0.935854 3.23550 0.932063 3.23600 0.880212 3.23650 0.837640 3.23700 0.887418 3.23750 0.908567 3.23800 0.881510 3.23850 0.790457 3.23900 0.592514 3.23950 0.485439 3.24000 0.338618 3.24050 0.204305 3.24100 0.411464 3.24150 0.708056 3.24200 0.839487 3.24250 0.846709 3.24300 0.793635 3.24350 0.606999 3.24400 0.404947 3.24450 0.444872 3.24500 0.394503 3.24550 0.370789 3.24600 0.527478 3.24650 0.443854 3.24700 0.224968 3.24750 0.301574 3.24800 0.504563 3.24850 0.435309 3.24900 0.286354 3.24950 0.309534 3.25000 0.248860 3.25050 0.294550 3.25100 0.597309 3.25150 0.822699 3.25200 0.901763 3.25250 0.926175 3.25300 0.921652 3.25350 0.890969 3.25400 0.889685 3.25450 0.921096 3.25500 0.936674 3.25550 0.932779 3.25600 0.923908 3.25650 0.916214 3.25700 0.890954 3.25750 0.874063 3.25800 0.855686 3.25850 0.826873 3.25900 0.735485 3.25950 0.500186 3.26000 0.198810 3.26050 8.39780E-02 3.26100 0.187924 3.26150 0.381855 3.26200 0.511540 3.26250 0.448754 3.26300 0.236173 3.26350 0.232855 3.26400 0.512985 3.26450 0.748374 3.26500 0.847221 3.26550 0.888537 3.26600 0.889874 3.26650 0.848406 3.26700 0.796327 3.26750 0.632249 3.26800 0.438582 3.26850 0.568484 3.26900 0.737648 3.26950 0.656631 3.27000 0.369037 3.27050 0.170099 3.27100 0.194345 3.27150 0.183306 3.27200 0.164073 3.27250 0.305534 3.27300 0.553599 3.27350 0.761732 3.27400 0.841002 3.27450 0.860872 3.27500 0.867676 3.27550 0.877051 3.27600 0.868304 3.27650 0.870210 3.27700 0.880665 3.27750 0.861178 3.27800 0.835147 3.27850 0.803387 3.27900 0.650494 3.27950 0.331259 3.28000 1.03092E-01 3.28050 0.144306 3.28100 0.431149 3.28150 0.746096 3.28200 0.885414 3.28250 0.924374 3.28300 0.935643 3.28350 0.942296 3.28400 0.944544 3.28450 0.938377 3.28500 0.926847 3.28550 0.927994 3.28600 0.938667 3.28650 0.930623 3.28700 0.925407 3.28750 0.940549 3.28800 0.947302 3.28850 0.941044 3.28900 0.934280 3.28950 0.929518 3.29000 0.890570 3.29050 0.731982 3.29100 0.527197 3.29150 0.633871 3.29200 0.827627 3.29250 0.872217 3.29300 0.839976 3.29350 0.682757 3.29400 0.498551 3.29450 0.560467 3.29500 0.529294 3.29550 0.310872 3.29600 0.370828 3.29650 0.632644 3.29700 0.723318 3.29750 0.574746 3.29800 0.275939 3.29850 0.148915 3.29900 0.187524 3.29950 0.200564 3.30000 0.372385 3.30050 0.593196 3.30100 0.649042 3.30150 0.513762 3.30200 0.576965 3.30250 0.794993 3.30300 0.876865 3.30350 0.867296 3.30400 0.773104 3.30450 0.519265 3.30500 0.355621 3.30550 0.554754 3.30600 0.768791 3.30650 0.860551 3.30700 0.889241 3.30750 0.860436 3.30800 0.724231 3.30850 0.531470 3.30900 0.559063 3.30950 0.589115 3.31000 0.536321 3.31050 0.633001 3.31100 0.749855 3.31150 0.726666 3.31200 0.520128 3.31250 0.208805 3.31300 3.50122E-02 3.31350 3.01087E-03 3.31400 1.26671E-03 3.31450 3.80167E-03 3.31500 1.35262E-02 3.31550 2.71408E-02 3.31600 3.34067E-02 3.31650 4.18014E-02 3.31700 7.36682E-02 3.31750 0.122044 3.31800 0.126703 3.31850 0.134040 3.31900 0.111121 3.31950 9.55825E-02 3.32000 0.210154 3.32050 0.346944 3.32100 0.405499 3.32150 0.333834 3.32200 0.251975 3.32250 0.379761 3.32300 0.487623 3.32350 0.498639 3.32400 0.601451 3.32450 0.674567 3.32500 0.690361 3.32550 0.704372 3.32600 0.749284 3.32650 0.770722 3.32700 0.681506 3.32750 0.563265 3.32800 0.468280 3.32850 0.452546 3.32900 0.404902 3.32950 0.503752 3.33000 0.763725 3.33050 0.898567 3.33100 0.926654 3.33150 0.927781 3.33200 0.929823 3.33250 0.937668 3.33300 0.932619 3.33350 0.872614 3.33400 0.662674 3.33450 0.534527 3.33500 0.722461 3.33550 0.878062 3.33600 0.931973 3.33650 0.950493 3.33700 0.946962 3.33750 0.922459 3.33800 0.817444 3.33850 0.728850 3.33900 0.693643 3.33950 0.571146 3.34000 0.578143 3.34050 0.687782 3.34100 0.790606 3.34150 0.732030 3.34200 0.630775 3.34250 0.672466 3.34300 0.851666 3.34350 0.929390 3.34400 0.915487 3.34450 0.820378 3.34500 0.598257 3.34550 0.299419 3.34600 0.270836 3.34650 0.482349 3.34700 0.497619 3.34750 0.571485 3.34800 0.812295 3.34850 0.927370 3.34900 0.952117 3.34950 0.955612 3.35000 0.929426 3.35050 0.810463 3.35100 0.711726 3.35150 0.828405 3.35200 0.921983 3.35250 0.898060 3.35300 0.872892 3.35350 0.888445 3.35400 0.818606 3.35450 0.732328 3.35500 0.638457 3.35550 0.609000 3.35600 0.655601 3.35650 0.431378 3.35700 0.212085 3.35750 0.298015 3.35800 0.457460 3.35850 0.700208 3.35900 0.881195 3.35950 0.921450 3.36000 0.923010 3.36050 0.880014 3.36100 0.731258 3.36150 0.669058 3.36200 0.772374 3.36250 0.798552 3.36300 0.603631 3.36350 0.551893 3.36400 0.779823 3.36450 0.913802 3.36500 0.921848 3.36550 0.883246 3.36600 0.894855 3.36650 0.918896 3.36700 0.877561 3.36750 0.743041 3.36800 0.464940 3.36850 0.222155 3.36900 0.351468 3.36950 0.671682 3.37000 0.860901 3.37050 0.880463 3.37100 0.757050 3.37150 0.602160 3.37200 0.707731 3.37250 0.849178 3.37300 0.900511 3.37350 0.935921 3.37400 0.949898 3.37450 0.956225 3.37500 0.956622 3.37550 0.934129 3.37600 0.832512 3.37650 0.700960 3.37700 0.750747 3.37750 0.877735 3.37800 0.931510 3.37850 0.920751 3.37900 0.827866 3.37950 0.546992 3.38000 0.207306 3.38050 0.187789 3.38100 0.501241 3.38150 0.753812 3.38200 0.818282 3.38250 0.832678 3.38300 0.806243 3.38350 0.809017 3.38400 0.867590 3.38450 0.814805 3.38500 0.736118 3.38550 0.751630 3.38600 0.788096 3.38650 0.883794 3.38700 0.946163 3.38750 0.954523 3.38800 0.963240 3.38850 0.965491 3.38900 0.955193 3.38950 0.945517 3.39000 0.935408 3.39050 0.865575 3.39100 0.684179 3.39150 0.363389 3.39200 0.147977 3.39250 0.295101 3.39300 0.665525 3.39350 0.872481 3.39400 0.852606 3.39450 0.806390 3.39500 0.886410 3.39550 0.946926 3.39600 0.953120 3.39650 0.942419 3.39700 0.910869 3.39750 0.883465 3.39800 0.920427 3.39850 0.959429 3.39900 0.966122 3.39950 0.965888 3.40000 0.964562 3.40050 0.959133 3.40100 0.955429 3.40150 0.952306 3.40200 0.941270 3.40250 0.875214 3.40300 0.641708 3.40350 0.358788 3.40400 0.239321 3.40450 0.298555 3.40500 0.574377 3.40550 0.775403 3.40600 0.792986 3.40650 0.730946 3.40700 0.699110 3.40750 0.826285 3.40800 0.857636 3.40850 0.740297 3.40900 0.699973 3.40950 0.803932 3.41000 0.870337 3.41050 0.845349 3.41100 0.869324 3.41150 0.931802 3.41200 0.931212 3.41250 0.882666 3.41300 0.879496 3.41350 0.932583 3.41400 0.945585 3.41450 0.900443 3.41500 0.717920 3.41550 0.493264 3.41600 0.334241 3.41650 0.274816 3.41700 0.524915 3.41750 0.839073 3.41800 0.947135 3.41850 0.961991 3.41900 0.961002 3.41950 0.959999 3.42000 0.957211 3.42050 0.957721 3.42100 0.956905 3.42150 0.956752 3.42200 0.958729 3.42250 0.953321 3.42300 0.944235 3.42350 0.938257 3.42400 0.931174 3.42450 0.926684 3.42500 0.908780 3.42550 0.835615 3.42600 0.789815 3.42650 0.818993 3.42700 0.753777 3.42750 0.593311 3.42800 0.475669 3.42850 0.387997 3.42900 0.448967 3.42950 0.706849 3.43000 0.909537 3.43050 0.950941 3.43100 0.957356 3.43150 0.956556 3.43200 0.951796 3.43250 0.963593 3.43300 0.968264 3.43350 0.954482 3.43400 0.934225 3.43450 0.937634 3.43500 0.940937 3.43550 0.912258 3.43600 0.907996 3.43650 0.920403 3.43700 0.920670 3.43750 0.931723 3.43800 0.935852 3.43850 0.921656 3.43900 0.885740 3.43950 0.770118 3.44000 0.653705 3.44050 0.593934 3.44100 0.636492 3.44150 0.682416 3.44200 0.837907 3.44250 0.927737 3.44300 0.926145 3.44350 0.940609 3.44400 0.959339 3.44450 0.968757 3.44500 0.961495 3.44550 0.917923 3.44600 0.844306 3.44650 0.857763 3.44700 0.931068 3.44750 0.930468 3.44800 0.862567 3.44850 0.866287 3.44900 0.902996 3.44950 0.923655 3.45000 0.943901 3.45050 0.953359 3.45100 0.917704 3.45150 0.838238 3.45200 0.800668 3.45250 0.795415 3.45300 0.763456 3.45350 0.740103 3.45400 0.685250 3.45450 0.779611 3.45500 0.874728 3.45550 0.829543 3.45600 0.865265 3.45650 0.942162 3.45700 0.965817 3.45750 0.971008 3.45800 0.974527 3.45850 0.973906 3.45900 0.969949 3.45950 0.965975 3.46000 0.957669 3.46050 0.958242 3.46100 0.958821 3.46150 0.936755 3.46200 0.919475 3.46250 0.943985 3.46300 0.943677 3.46350 0.926200 3.46400 0.900749 3.46450 0.855791 3.46500 0.804392 3.46550 0.773715 3.46600 0.742394 3.46650 0.768408 3.46700 0.841000 3.46750 0.901808 3.46800 0.905265 3.46850 0.934322 3.46900 0.962645 3.46950 0.970549 3.47000 0.972980 3.47050 0.969704 3.47100 0.966060 3.47150 0.958420 3.47200 0.940794 3.47250 0.920784 3.47300 0.925739 3.47350 0.915190 3.47400 0.873087 3.47450 0.888806 3.47500 0.923229 3.47550 0.906547 3.47600 0.916203 3.47650 0.913899 3.47700 0.883410 3.47750 0.866386 3.47800 0.862903 3.47850 0.859224 3.47900 0.890929 3.47950 0.907703 3.48000 0.909419 3.48050 0.922517 3.48100 0.932526 3.48150 0.947587 3.48200 0.948336 3.48250 0.936223 3.48300 0.932291 3.48350 0.942180 3.48400 0.959903 3.48450 0.972356 3.48500 0.960773 3.48550 0.915780 3.48600 0.896888 3.48650 0.922925 3.48700 0.929716 3.48750 0.902589 3.48800 0.884601 3.48850 0.888542 3.48900 0.853630 3.48950 0.868533 3.49000 0.893525 3.49050 0.892129 3.49100 0.905550 3.49150 0.932652 3.49200 0.948645 3.49250 0.931404 3.49300 0.916768 3.49350 0.913462 3.49400 0.908071 3.49450 0.918627 3.49500 0.947276 3.49550 0.966971 3.49600 0.971297 3.49650 0.963422 3.49700 0.955938 3.49750 0.960099 3.49800 0.949178 3.49850 0.913843 3.49900 0.903277 3.49950 0.928701 3.50000 0.916092 3.50050 0.926237 3.50100 0.957892 3.50150 0.956503 3.50200 0.948687 3.50250 0.945300 3.50300 0.917185 3.50350 0.869709 3.50400 0.867302 3.50450 0.928201 3.50500 0.963235 3.50550 0.966528 3.50600 0.964750 3.50650 0.969629 3.50700 0.968409 3.50750 0.957432 3.50800 0.953337 3.50850 0.964700 3.50900 0.963699 3.50950 0.945694 3.51000 0.946416 3.51050 0.953799 3.51100 0.929929 3.51150 0.883554 3.51200 0.913951 3.51250 0.958877 3.51300 0.968084 3.51350 0.960897 3.51400 0.945299 3.51450 0.934072 3.51500 0.945875 3.51550 0.961818 3.51600 0.959915 3.51650 0.959839 3.51700 0.963237 3.51750 0.963498 3.51800 0.963229 3.51850 0.963537 3.51900 0.952780 3.51950 0.947303 3.52000 0.952410 3.52050 0.948184 3.52100 0.944332 3.52150 0.956223 3.52200 0.967925 3.52250 0.960877 3.52300 0.945014 3.52350 0.945427 3.52400 0.958456 3.52450 0.969761 3.52500 0.969107 3.52550 0.957964 3.52600 0.947315 3.52650 0.938212 3.52700 0.903172 3.52750 0.888101 3.52800 0.919719 3.52850 0.932343 3.52900 0.930589 3.52950 0.955547 3.53000 0.966193 3.53050 0.949302 3.53100 0.906320 3.53150 0.907267 3.53200 0.936225 3.53250 0.940923 3.53300 0.906834 3.53350 0.879392 3.53400 0.923352 3.53450 0.944981 3.53500 0.878656 3.53550 0.759260 3.53600 0.754393 3.53650 0.858428 3.53700 0.918962 3.53750 0.951193 3.53800 0.951346 3.53850 0.885979 3.53900 0.812809 3.53950 0.817626 3.54000 0.854790 3.54050 0.854844 3.54100 0.829168 3.54150 0.854172 3.54200 0.884243 3.54250 0.866413 3.54300 0.854518 3.54350 0.873003 3.54400 0.895926 3.54450 0.913682 3.54500 0.890408 3.54550 0.881525 3.54600 0.895960 3.54650 0.907632 3.54700 0.908812 3.54750 0.871626 3.54800 0.859899 3.54850 0.924517 3.54900 0.952282 3.54950 0.928271 3.55000 0.899211 3.55050 0.923922 3.55100 0.945666 3.55150 0.942808 3.55200 0.928646 3.55250 0.911709 3.55300 0.923847 3.55350 0.901387 3.55400 0.855161 3.55450 0.864056 3.55500 0.873092 3.55550 0.898631 3.55600 0.936643 3.55650 0.943086 3.55700 0.934157 3.55750 0.932456 3.55800 0.931784 3.55850 0.938929 3.55900 0.935141 3.55950 0.899877 3.56000 0.871921 3.56050 0.901541 3.56100 0.948855 3.56150 0.958914 3.56200 0.958503 3.56250 0.963258 3.56300 0.965085 3.56350 0.953583 3.56400 0.926084 3.56450 0.884535 3.56500 0.881881 3.56550 0.906704 3.56600 0.898395 3.56650 0.886223 3.56700 0.857887 3.56750 0.836020 3.56800 0.893727 3.56850 0.927875 3.56900 0.879367 3.56950 0.826559 3.57000 0.882100 3.57050 0.932522 3.57100 0.923562 3.57150 0.881867 3.57200 0.866240 3.57250 0.883643 3.57300 0.891969 3.57350 0.847236 3.57400 0.804469 3.57450 0.821359 3.57500 0.853838 3.57550 0.864893 3.57600 0.865111 3.57650 0.891033 3.57700 0.920547 3.57750 0.922517 3.57800 0.917367 3.57850 0.931453 3.57900 0.934496 3.57950 0.912785 3.58000 0.919756 3.58050 0.927386 3.58100 0.866778 3.58150 0.815407 3.58200 0.840710 3.58250 0.898035 3.58300 0.937369 3.58350 0.934427 3.58400 0.909646 3.58450 0.879553 3.58500 0.874711 3.58550 0.867903 3.58600 0.855161 3.58650 0.896903 3.58700 0.886616 3.58750 0.838346 3.58800 0.881971 3.58850 0.933394 3.58900 0.938334 3.58950 0.914374 3.59000 0.903644 3.59050 0.915227 3.59100 0.915507 3.59150 0.910163 3.59200 0.898077 3.59250 0.887020 3.59300 0.883055 3.59350 0.887080 3.59400 0.916847 3.59450 0.947209 3.59500 0.946351 3.59550 0.940150 3.59600 0.944876 3.59650 0.926088 3.59700 0.882549 3.59750 0.895561 3.59800 0.938200 3.59850 0.947834 3.59900 0.942448 3.59950 0.938339 3.60000 0.917257 3.60050 0.877647 3.60100 0.898906 3.60150 0.941817 3.60200 0.952048 3.60250 0.956553 3.60300 0.957527 3.60350 0.955091 3.60400 0.952915 3.60450 0.933248 3.60500 0.897581 3.60550 0.901115 3.60600 0.912280 3.60650 0.870167 3.60700 0.807533 3.60750 0.791990 3.60800 0.826427 3.60850 0.896437 3.60900 0.938177 3.60950 0.944205 3.61000 0.924852 3.61050 0.922689 3.61100 0.935907 3.61150 0.916428 3.61200 0.891749 3.61250 0.905671 3.61300 0.912726 3.61350 0.876031 3.61400 0.893147 3.61450 0.938227 3.61500 0.953030 3.61550 0.959277 3.61600 0.956464 3.61650 0.936288 3.61700 0.895973 3.61750 0.898596 3.61800 0.939962 3.61850 0.956623 3.61900 0.956900 3.61950 0.949708 3.62000 0.945213 3.62050 0.952477 3.62100 0.948600 3.62150 0.944735 3.62200 0.957778 3.62250 0.962461 3.62300 0.957246 3.62350 0.951719 3.62400 0.952041 3.62450 0.950964 3.62500 0.935901 3.62550 0.898845 3.62600 0.884380 3.62650 0.895868 3.62700 0.871990 3.62750 0.820088 3.62800 0.855970 3.62850 0.933959 3.62900 0.960988 3.62950 0.963313 3.63000 0.963128 3.63050 0.960023 3.63100 0.935763 3.63150 0.888257 3.63200 0.894698 3.63250 0.937017 3.63300 0.952515 3.63350 0.950886 3.63400 0.931678 3.63450 0.899731 3.63500 0.915601 3.63550 0.938327 3.63600 0.941885 3.63650 0.941924 3.63700 0.940434 3.63750 0.952949 3.63800 0.962514 3.63850 0.962234 3.63900 0.955985 3.63950 0.945941 3.64000 0.945788 3.64050 0.950981 3.64100 0.955630 3.64150 0.956803 3.64200 0.956321 3.64250 0.958949 3.64300 0.959973 3.64350 0.960664 3.64400 0.955318 3.64450 0.949449 3.64500 0.950450 3.64550 0.925271 3.64600 0.876637 3.64650 0.859151 3.64700 0.836995 3.64750 0.802784 3.64800 0.859275 3.64850 0.937563 3.64900 0.958321 3.64950 0.960407 3.65000 0.955881 3.65050 0.935646 3.65100 0.906374 3.65150 0.916214 3.65200 0.934818 3.65250 0.943031 3.65300 0.944690 3.65350 0.939893 3.65400 0.949044 3.65450 0.950604 3.65500 0.947874 3.65550 0.954284 3.65600 0.959541 3.65650 0.960866 3.65700 0.960550 3.65750 0.959723 3.65800 0.958480 3.65850 0.958542 3.65900 0.955612 3.65950 0.953420 3.66000 0.957716 3.66050 0.960768 3.66100 0.954450 3.66150 0.938030 3.66200 0.937093 3.66250 0.949150 3.66300 0.942064 3.66350 0.932389 3.66400 0.944345 3.66450 0.956497 3.66500 0.957733 3.66550 0.956908 3.66600 0.956025 3.66650 0.937581 3.66700 0.858404 3.66750 0.773729 3.66800 0.809182 3.66850 0.876456 3.66900 0.920003 3.66950 0.945280 3.67000 0.949404 3.67050 0.940199 3.67100 0.910055 3.67150 0.864136 3.67200 0.849856 3.67250 0.859767 3.67300 0.859187 3.67350 0.846003 3.67400 0.831769 3.67450 0.878836 3.67500 0.856210 3.67550 0.799027 3.67600 0.799760 3.67650 0.849226 3.67700 0.915802 3.67750 0.936782 3.67800 0.912042 3.67850 0.903118 3.67900 0.922868 3.67950 0.921008 3.68000 0.923520 3.68050 0.926579 3.68100 0.926862 3.68150 0.919216 3.68200 0.920999 3.68250 0.928991 3.68300 0.914855 3.68350 0.909011 3.68400 0.932141 3.68450 0.946163 3.68500 0.950466 3.68550 0.954178 3.68600 0.951547 3.68650 0.929166 3.68700 0.875682 3.68750 0.859601 3.68800 0.873028 3.68850 0.894840 3.68900 0.931189 3.68950 0.946528 3.69000 0.951074 3.69050 0.947556 3.69100 0.937292 3.69150 0.935822 3.69200 0.929292 3.69250 0.914140 3.69300 0.930947 3.69350 0.947394 3.69400 0.947416 3.69450 0.947163 3.69500 0.942285 3.69550 0.945636 3.69600 0.954704 3.69650 0.954193 3.69700 0.941326 3.69750 0.935661 3.69800 0.948744 3.69850 0.956100 3.69900 0.955470 3.69950 0.952220 3.70000 0.949464 3.70050 0.947561 3.70100 0.948760 3.70150 0.954742 3.70200 0.957702 3.70250 0.957937 3.70300 0.957704 3.70350 0.957270 3.70400 0.954643 3.70450 0.951360 3.70500 0.952056 3.70550 0.951006 3.70600 0.945770 3.70650 0.930439 3.70700 0.922548 3.70750 0.926628 3.70800 0.934038 3.70850 0.936375 3.70900 0.903445 3.70950 0.882846 3.71000 0.891409 3.71050 0.901868 3.71100 0.928437 3.71150 0.938689 3.71200 0.937999 3.71250 0.936016 3.71300 0.921042 3.71350 0.881337 3.71400 0.889461 3.71450 0.930326 3.71500 0.932213 3.71550 0.924302 3.71600 0.933242 3.71650 0.947356 3.71700 0.941503 3.71750 0.911876 3.71800 0.907920 3.71850 0.936590 3.71900 0.948796 3.71950 0.949710 3.72000 0.951365 3.72050 0.952735 3.72100 0.952050 3.72150 0.951961 3.72200 0.951560 3.72250 0.950264 3.72300 0.950007 3.72350 0.950332 3.72400 0.946849 3.72450 0.942955 3.72500 0.944462 3.72550 0.946432 3.72600 0.937751 3.72650 0.931045 3.72700 0.934623 3.72750 0.934603 3.72800 0.928638 3.72850 0.936472 3.72900 0.945149 3.72950 0.930388 3.73000 0.878069 3.73050 0.860205 3.73100 0.909951 3.73150 0.942886 3.73200 0.950124 3.73250 0.948110 3.73300 0.932670 3.73350 0.908456 3.73400 0.886842 3.73450 0.863841 3.73500 0.899069 3.73550 0.924609 3.73600 0.906507 3.73650 0.907417 3.73700 0.925700 3.73750 0.920371 3.73800 0.906907 3.73850 0.924384 3.73900 0.938865 3.73950 0.938658 3.74000 0.942298 3.74050 0.940206 3.74100 0.922391 3.74150 0.885018 3.74200 0.891771 3.74250 0.929324 3.74300 0.940905 3.74350 0.940096 3.74400 0.943700 3.74450 0.944691 3.74500 0.942212 3.74550 0.941783 3.74600 0.942349 3.74650 0.941717 3.74700 0.941991 3.74750 0.945678 3.74800 0.944581 3.74850 0.940878 3.74900 0.938062 3.74950 0.932117 3.75000 0.899876 3.75050 0.865114 3.75100 0.900388 3.75150 0.932785 3.75200 0.926605 3.75250 0.923447 3.75300 0.932378 3.75350 0.938416 3.75400 0.921922 3.75450 0.871800 3.75500 0.872188 3.75550 0.922640 3.75600 0.941501 3.75650 0.942789 3.75700 0.944763 3.75750 0.944055 3.75800 0.930029 3.75850 0.901654 3.75900 0.905667 3.75950 0.923418 3.76000 0.913207 3.76050 0.911874 3.76100 0.932129 3.76150 0.939606 3.76200 0.935262 3.76250 0.920768 3.76300 0.898019 3.76350 0.906853 3.76400 0.926015 3.76450 0.921383 3.76500 0.910073 3.76550 0.878260 3.76600 0.872561 3.76650 0.914432 3.76700 0.931654 3.76750 0.937308 3.76800 0.941692 3.76850 0.943201 3.76900 0.942452 3.76950 0.938827 3.77000 0.928828 3.77050 0.896673 3.77100 0.867158 3.77150 0.899643 3.77200 0.927268 3.77250 0.934884 3.77300 0.938675 3.77350 0.930306 3.77400 0.894836 3.77450 0.862631 3.77500 0.896464 3.77550 0.931780 3.77600 0.938854 3.77650 0.937980 3.77700 0.932634 3.77750 0.933122 3.77800 0.938793 3.77850 0.939429 3.77900 0.935184 3.77950 0.927727 3.78000 0.930182 3.78050 0.931709 3.78100 0.911485 3.78150 0.893550 3.78200 0.915248 3.78250 0.932790 3.78300 0.934150 3.78350 0.928157 3.78400 0.909863 3.78450 0.893317 3.78500 0.892837 3.78550 0.911687 3.78600 0.929699 3.78650 0.936321 3.78700 0.937182 3.78750 0.935612 3.78800 0.931519 3.78850 0.924863 3.78900 0.902808 3.78950 0.853690 3.79000 0.841900 3.79050 0.888697 3.79100 0.899090 3.79150 0.874720 3.79200 0.886100 3.79250 0.920494 3.79300 0.930601 3.79350 0.915685 3.79400 0.876802 3.79450 0.878718 3.79500 0.916834 3.79550 0.930172 3.79600 0.931193 3.79650 0.931476 3.79700 0.926743 3.79750 0.912376 3.79800 0.899385 3.79850 0.915912 3.79900 0.930605 3.79950 0.930251 3.80000 0.922275 3.80050 0.922143 3.80100 0.927319 3.80150 0.927451 3.80200 0.927806 3.80250 0.929537 3.80300 0.928757 3.80350 0.921441 3.80400 0.899801 3.80450 0.888154 3.80500 0.906717 3.80550 0.924416 3.80600 0.929197 3.80650 0.930794 3.80700 0.929987 3.80750 0.925672 3.80800 0.914990 3.80850 0.905939 3.80900 0.899362 3.80950 0.901918 3.81000 0.911155 3.81050 0.912217 3.81100 0.907999 3.81150 0.914501 3.81200 0.907648 3.81250 0.878559 3.81300 0.871396 3.81350 0.866071 3.81400 0.857239 3.81450 0.881508 3.81500 0.915050 3.81550 0.925625 3.81600 0.925674 3.81650 0.914607 3.81700 0.892660 3.81750 0.897223 3.81800 0.918311 3.81850 0.920119 3.81900 0.902556 3.81950 0.898328 3.82000 0.917762 3.82050 0.926920 3.82100 0.928334 3.82150 0.929017 3.82200 0.928856 3.82250 0.928963 3.82300 0.928726 3.82350 0.927230 3.82400 0.920239 3.82450 0.915502 3.82500 0.919529 3.82550 0.919656 3.82600 0.923261 3.82650 0.925718 3.82700 0.919091 3.82750 0.904191 3.82800 0.906639 3.82850 0.920056 3.82900 0.925007 3.82950 0.925211 3.83000 0.924883 3.83050 0.923470 3.83100 0.921535 3.83150 0.920292 3.83200 0.916980 3.83250 0.911526 3.83300 0.912049 3.83350 0.898557 3.83400 0.877582 3.83450 0.876125 3.83500 0.873951 3.83550 0.887489 3.83600 0.901969 3.83650 0.896289 3.83700 0.891827 3.83750 0.904988 3.83800 0.905702 3.83850 0.902151 3.83900 0.908147 3.83950 0.907419 3.84000 0.903458 3.84050 0.901534 3.84100 0.907768 3.84150 0.909086 3.84200 0.907170 3.84250 0.907203 3.84300 0.901438 3.84350 0.895419 3.84400 0.898175 3.84450 0.894736 3.84500 0.888385 3.84550 0.890968 3.84600 0.887148 3.84650 0.882521 3.84700 0.877438 3.84750 0.881467 3.84800 0.888676 3.84850 0.893875 3.84900 0.901605 3.84950 0.905661 3.85000 0.901693 3.85050 0.893832 3.85100 0.888552 3.85150 0.896461 3.85200 0.902764 3.85250 0.902770 3.85300 0.903896 3.85350 0.903785 3.85400 0.901893 3.85450 0.900865 3.85500 0.892898 3.85550 0.878873 3.85600 0.870518 3.85650 0.880831 3.85700 0.891815 3.85750 0.893516 3.85800 0.891539 3.85850 0.884247 3.85900 0.871688 3.85950 0.866859 3.86000 0.874359 3.86050 0.878809 3.86100 0.877739 3.86150 0.870645 3.86200 0.867207 3.86250 0.860285 3.86300 0.857616 3.86350 0.857616 3.86400 0.861373 3.86450 0.861431 3.86500 0.858028 3.86550 0.855213 3.86600 0.847457 3.86650 0.849141 3.86700 0.842916 3.86750 0.837250 3.86800 0.827011 3.86850 0.825227 3.86900 0.817496 3.86950 0.824437 3.87000 0.823015 3.87050 0.821470 3.87100 0.814600 3.87150 0.816815 3.87200 0.803373 3.87250 0.794406 3.87300 0.790290 3.87350 0.803279 3.87400 0.794914 3.87450 0.795960 3.87500 0.793137 3.87550 0.800080 3.87600 0.790848 3.87650 0.795720 3.87700 0.777758 3.87750 0.778835 3.87800 0.763146 3.87850 0.783537 3.87900 0.773600 3.87950 0.787414 3.88000 0.771176 3.88050 0.778252 3.88100 0.764849 3.88150 0.775734 3.88200 0.771553 3.88250 0.770202 3.88300 0.777867 3.88350 0.769159 3.88400 0.783888 3.88450 0.765064 3.88500 0.783867 3.88550 0.758678 3.88600 0.775110 3.88650 0.768182 3.88700 0.776628 3.88750 0.784267 3.88800 0.770434 3.88850 0.790383 3.88900 0.766714 3.88950 0.787321 3.89000 0.774275 3.89050 0.776642 3.89100 0.778058 3.89150 0.778179 3.89200 0.801082 3.89250 0.780286 3.89300 0.800587 3.89350 0.792846 3.89400 0.800228 3.89450 0.813858 3.89500 0.802728 3.89550 0.829085 3.89600 0.818642 3.89650 0.830523 3.89700 0.842055 3.89750 0.830284 3.89800 0.846659 3.89850 0.844890 3.89900 0.852682 3.89950 0.862854 3.90000 0.858421 3.90050 0.865934 3.90100 0.875806 3.90150 0.875606 3.90200 0.867403 3.90250 0.861125 3.90300 0.856016 3.90350 0.837602 3.90400 0.842767 3.90450 0.844057 3.90500 0.828002 3.90550 0.839817 3.90600 0.818910 3.90650 0.809439 3.90700 0.833037 3.90750 0.808596 3.90800 0.819508 3.90850 0.823713 3.90900 0.793778 3.90950 0.811833 3.91000 0.774555 3.91050 0.751832 3.91100 0.797041 3.91150 0.779991 3.91200 0.791990 3.91250 0.810021 3.91300 0.774358 3.91350 0.801923 3.91400 0.798882 3.91450 0.770916 3.91500 0.806702 3.91550 0.783009 3.91600 0.769207 3.91650 0.804367 3.91700 0.770844 3.91750 0.779912 3.91800 0.807344 3.91850 0.763763 3.91900 0.787400 3.91950 0.803665 3.92000 0.761555 3.92050 0.795571 3.92100 0.796438 3.92150 0.758724 3.92200 0.800827 3.92250 0.793502 3.92300 0.762855 3.92350 0.806084 3.92400 0.790088 3.92450 0.763916 3.92500 0.801453 3.92550 0.777236 3.92600 0.763287 3.92650 0.807545 3.92700 0.779617 3.92750 0.768704 3.92800 0.811629 3.92850 0.780229 3.92900 0.771407 3.92950 0.818360 3.93000 0.793956 3.93050 0.782159 3.93100 0.821785 3.93150 0.798395 3.93200 0.783563 3.93250 0.822445 3.93300 0.804857 3.93350 0.789000 3.93400 0.826638 3.93450 0.811891 3.93500 0.791612 3.93550 0.826833 3.93600 0.818617 3.93650 0.794508 3.93700 0.825884 3.93750 0.824173 3.93800 0.796177 3.93850 0.822917 3.93900 0.830454 3.93950 0.802728 3.94000 0.821858 3.94050 0.835511 3.94100 0.804791 3.94150 0.807606 3.94200 0.824007 3.94250 0.802379 3.94300 0.809184 3.94350 0.838984 3.94400 0.826825 3.94450 0.817147 3.94500 0.838744 3.94550 0.832704 3.94600 0.817153 3.94650 0.835370 3.94700 0.838579 3.94750 0.819660 3.94800 0.829818 3.94850 0.842990 3.94900 0.828789 3.94950 0.828609 3.95000 0.843315 3.95050 0.834852 3.95100 0.826725 3.95150 0.839850 3.95200 0.837967 3.95250 0.821698 3.95300 0.829299 3.95350 0.839619 3.95400 0.825000 3.95450 0.820725 3.95500 0.837216 3.95550 0.837163 3.95600 0.831801 3.95650 0.839691 3.95700 0.840750 3.95750 0.832875 3.95800 0.832496 3.95850 0.826060 3.95900 0.819936 3.95950 0.828257 3.96000 0.838252 3.96050 0.838305 3.96100 0.832826 3.96150 0.836390 3.96200 0.839439 3.96250 0.834936 3.96300 0.834106 3.96350 0.838123 3.96400 0.836587 3.96450 0.832532 3.96500 0.835211 3.96550 0.836506 3.96600 0.833419 3.96650 0.832621 3.96700 0.834792 3.96750 0.834187 3.96800 0.830477 3.96850 0.831338 3.96900 0.832950 3.96950 0.830737 3.97000 0.829514 3.97050 0.831007 3.97100 0.830440 3.97150 0.827375 3.97200 0.828173 3.97250 0.829135 3.97300 0.827664 3.97350 0.826265 3.97400 0.825511 3.97450 0.822606 3.97500 0.810896 3.97550 0.805542 3.97600 0.812903 3.97650 0.815329 3.97700 0.816207 3.97750 0.820252 3.97800 0.822824 3.97850 0.821232 3.97900 0.817784 3.97950 0.813052 3.98000 0.808779 3.98050 0.808101 3.98100 0.807428 3.98150 0.799935 3.98200 0.805205 3.98250 0.814153 3.98300 0.816043 3.98350 0.815537 3.98400 0.815483 3.98450 0.814255 3.98500 0.813752 3.98550 0.812937 3.98600 0.812273 3.98650 0.812170 3.98700 0.811505 3.98750 0.811284 3.98800 0.810109 3.98850 0.809644 3.98900 0.809318 3.98950 0.808340 3.99000 0.808011 3.99050 0.807054 3.99100 0.806770 3.99150 0.805809 3.99200 0.804086 3.99250 0.801399 3.99300 0.800418 3.99350 0.801883 3.99400 0.801775 3.99450 0.800565 3.99500 0.799542 3.99550 0.798779 3.99600 0.798086 3.99650 0.798631 3.99700 0.798746 3.99750 0.798100 3.99800 0.797432 3.99850 0.796848 3.99900 0.795769 3.99950 0.794321 4.00000 0.793263 4.00050 0.792460 4.00100 0.791906 4.00150 0.790770 4.00200 0.789996 4.00250 0.789282 4.00300 0.788595 4.00350 0.787513 4.00400 0.786554 4.00450 0.785536 4.00500 0.784638 4.00550 0.784021 4.00600 0.782618 4.00650 0.780995 4.00700 0.775564 4.00750 0.770433 4.00800 0.765232 4.00850 0.757148 4.00900 0.757792 4.00950 0.756861 4.01000 0.759298 4.01050 0.768317 4.01100 0.770548 4.01150 0.770972 4.01200 0.769822 4.01250 0.769100 4.01300 0.767485 4.01350 0.765223 4.01400 0.765563 4.01450 0.763732 4.01500 0.762289 4.01550 0.760590 4.01600 0.759253 4.01650 0.759907 4.01700 0.757148 4.01750 0.756945 4.01800 0.755749 4.01850 0.753165 4.01900 0.753944 4.01950 0.750041 4.02000 0.750373 4.02050 0.748762 4.02100 0.745739 4.02150 0.747494 4.02200 0.742465 4.02250 0.743530 4.02300 0.741628 4.02350 0.738010 4.02400 0.740852 4.02450 0.734761 4.02500 0.736114 4.02550 0.733957 4.02600 0.728735 4.02650 0.732631 4.02700 0.725807 4.02750 0.726901 4.02800 0.727063 4.02850 0.721243 4.02900 0.725600 4.02950 0.719416 4.03000 0.719085 4.03050 0.721076 4.03100 0.713298 4.03150 0.717759 4.03200 0.712527 4.03250 0.708457 4.03300 0.713619 4.03350 0.704902 4.03400 0.708598 4.03450 0.706877 4.03500 0.697022 4.03550 0.697391 4.03600 0.685643 4.03650 0.688292 4.03700 0.697357 4.03750 0.690877 4.03800 0.699090 4.03850 0.693976 4.03900 0.689388 4.03950 0.697016 4.04000 0.686202 4.04050 0.689952 4.04100 0.690084 4.04150 0.676406 4.04200 0.680439 4.04250 0.673635 4.04300 0.672087 4.04350 0.682467 4.04400 0.672222 4.04450 0.674923 4.04500 0.678092 4.04550 0.671753 4.04600 0.680591 4.04650 0.673689 4.04700 0.671418 4.04750 0.678680 4.04800 0.669166 4.04850 0.673266 4.04900 0.674266 4.04950 0.666165 4.05000 0.673390 4.05050 0.668879 4.05100 0.665713 4.05150 0.671790 4.05200 0.664507 4.05250 0.666431 4.05300 0.668763 4.05350 0.661806 4.05400 0.666112 4.05450 0.664741 4.05500 0.660522 4.05550 0.664381 4.05600 0.661264 4.05650 0.660885 4.05700 0.663735 4.05750 0.660073 4.05800 0.661080 4.05850 0.662057 4.05900 0.659388 4.05950 0.660228 4.06000 0.660142 4.06050 0.658821 4.06100 0.658988 4.06150 0.659079 4.06200 0.657548 4.06250 0.654597 4.06300 0.651550 4.06350 0.650216 4.06400 0.647559 4.06450 0.642944 4.06500 0.640364 4.06550 0.633352 4.06600 0.631183 4.06650 0.636216 4.06700 0.631714 4.06750 0.628857 4.06800 0.630853 4.06850 0.623109 4.06900 0.621957 4.06950 0.622982 4.07000 0.614772 4.07050 0.617389 4.07100 0.616443 4.07150 0.607690 4.07200 0.612120 4.07250 0.608249 4.07300 0.601014 4.07350 0.606759 4.07400 0.599692 4.07450 0.594709 4.07500 0.600943 4.07550 0.591631 4.07600 0.589207 4.07650 0.592944 4.07700 0.580809 4.07750 0.581454 4.07800 0.585150 4.07850 0.575752 4.07900 0.579831 4.07950 0.578633 4.08000 0.565349 4.08050 0.572882 4.08100 0.573585 4.08150 0.564527 4.08200 0.571876 4.08250 0.567617 4.08300 0.559065 4.08350 0.566456 4.08400 0.560799 4.08450 0.555039 4.08500 0.561607 4.08550 0.553861 4.08600 0.550834 4.08650 0.556923 4.08700 0.547084 4.08750 0.545627 4.08800 0.551482 4.08850 0.541618 4.08900 0.540525 4.08950 0.544633 4.09000 0.535922 4.09050 0.536957 4.09100 0.538536 4.09150 0.528791 4.09200 0.531837 4.09250 0.533723 4.09300 0.523976 4.09350 0.525478 4.09400 0.526841 4.09450 0.519121 4.09500 0.520014 4.09550 0.514918 4.09600 0.507542 4.09650 0.514729 4.09700 0.514821 4.09750 0.506707 4.09800 0.508551 4.09850 0.507943 4.09900 0.502017 4.09950 0.503890 4.10000 0.500565 4.10050 0.494693 4.10100 0.497707 4.10150 0.495578 4.10200 0.489234 4.10250 0.489486 4.10300 0.487684 4.10350 0.483577 4.10400 0.484567 4.10450 0.481145 4.10500 0.475654 4.10550 0.476932 4.10600 0.474882 4.10650 0.470113 4.10700 0.469343 4.10750 0.466443 4.10800 0.462777 4.10850 0.462775 4.10900 0.460147 4.10950 0.455150 4.11000 0.454054 4.11050 0.452066 4.11100 0.447959 4.11150 0.446527 4.11200 0.442661 4.11250 0.437438 4.11300 0.436298 4.11350 0.434320 4.11400 0.431779 4.11450 0.430495 4.11500 0.427799 4.11550 0.423068 4.11600 0.419921 4.11650 0.416893 4.11700 0.412462 4.11750 0.411159 4.11800 0.410682 4.11850 0.407446 4.11900 0.404547 4.11950 0.402311 4.12000 0.398946 4.12050 0.395920 4.12100 0.393190 4.12150 0.389319 4.12200 0.386284 4.12250 0.384075 4.12300 0.380870 4.12350 0.377507 4.12400 0.374187 4.12450 0.370612 4.12500 0.367789 4.12550 0.365124 4.12600 0.361552 4.12650 0.356997 4.12700 0.353454 4.12750 0.351058 4.12800 0.348513 4.12850 0.345645 4.12900 0.342480 4.12950 0.338538 4.13000 0.334527 4.13050 0.331755 4.13100 0.328949 4.13150 0.325707 4.13200 0.322348 4.13250 0.318336 4.13300 0.314242 4.13350 0.311537 4.13400 0.308689 4.13450 0.305240 4.13500 0.301753 4.13550 0.297682 4.13600 0.293539 4.13650 0.290778 4.13700 0.287876 4.13750 0.284380 4.13800 0.280770 4.13850 0.276724 4.13900 0.272669 4.13950 0.269695 4.14000 0.266630 4.14050 0.263127 4.14100 0.259452 4.14150 0.255420 4.14200 0.251279 4.14250 0.248076 4.14300 0.244996 4.14350 0.241532 4.14400 0.237879 4.14450 0.233991 4.14500 0.229863 4.14550 0.226430 4.14600 0.223260 4.14650 0.219746 4.14700 0.216084 4.14750 0.212160 4.14800 0.207682 4.14850 0.203708 4.14900 0.200850 4.14950 0.197747 4.15000 0.194214 4.15050 0.190534 4.15100 0.186569 4.15150 0.182330 4.15200 0.178744 4.15250 0.175597 4.15300 0.171951 4.15350 0.168292 4.15400 0.164681 4.15450 0.160857 4.15500 0.157487 4.15550 0.154384 4.15600 0.150954 4.15650 0.147419 4.15700 0.143876 4.15750 0.140206 4.15800 0.136330 4.15850 0.132830 4.15900 0.129759 4.15950 0.126502 4.16000 0.123112 4.16050 0.119692 4.16100 0.116244 4.16150 0.112833 4.16200 0.109603 4.16250 0.106390 4.16300 1.03140E-01 4.16350 9.99059E-02 4.16400 9.66974E-02 4.16450 9.34549E-02 4.16500 9.02848E-02 4.16550 8.72630E-02 4.16600 8.42312E-02 4.16650 8.12137E-02 4.16700 7.82405E-02 4.16750 7.52792E-02 4.16800 7.23399E-02 4.16850 6.94750E-02 4.16900 6.66983E-02 4.16950 6.39533E-02 4.17000 6.12338E-02 4.17050 5.85592E-02 4.17100 5.59368E-02 4.17150 5.33413E-02 4.17200 5.08086E-02 4.17250 4.83752E-02 4.17300 4.59705E-02 4.17350 4.36091E-02 4.17400 4.13232E-02 4.17450 3.90819E-02 4.17500 3.68913E-02 4.17550 3.47646E-02 4.17600 3.27242E-02 4.17650 3.07441E-02 4.17700 2.88065E-02 4.17750 2.69104E-02 4.17800 2.50773E-02 4.17850 2.33034E-02 4.17900 2.15705E-02 4.17950 1.98873E-02 4.18000 1.82441E-02 4.18050 1.66531E-02 4.18100 1.50602E-02 4.18150 1.35810E-02 4.18200 1.20403E-02 4.18250 1.07259E-02 4.18300 9.29839E-03 4.18350 8.17188E-03 4.18400 6.93094E-03 4.18450 5.92354E-03 4.18500 5.01647E-03 4.18550 4.05469E-03 4.18600 3.52783E-03 4.18650 2.60022E-03 4.18700 2.28597E-03 4.18750 1.62705E-03 4.18800 1.26226E-03 4.18850 1.01039E-03 4.18900 5.98714E-04 4.18950 5.19988E-04 4.19000 3.05751E-04 4.19050 1.81818E-04 4.19100 1.46604E-04 4.19150 5.89732E-05 4.19200 3.41927E-05 4.19250 2.08923E-05 4.19300 4.06534E-06 4.19350 2.17920E-07 4.19400 2.87676E-09 4.19450 0. 4.19500 0. 4.19550 0. 4.19600 0. 4.19650 0. 4.19700 0. 4.19750 0. 4.19800 0. 4.19850 0. 4.19900 0. 4.19950 0. 4.20000 0. 4.20050 0. 4.20100 0. 4.20150 0. 4.20200 0. 4.20250 0. 4.20300 0. 4.20350 0. 4.20400 0. 4.20450 0. 4.20500 0. 4.20550 0. 4.20600 0. 4.20650 0. 4.20700 0. 4.20750 0. 4.20800 0. 4.20850 0. 4.20900 0. 4.20950 0. 4.21000 0. 4.21050 0. 4.21100 0. 4.21150 0. 4.21200 0. 4.21250 0. 4.21300 0. 4.21350 0. 4.21400 0. 4.21450 0. 4.21500 0. 4.21550 0. 4.21600 0. 4.21650 0. 4.21700 0. 4.21750 0. 4.21800 0. 4.21850 0. 4.21900 0. 4.21950 0. 4.22000 0. 4.22050 0. 4.22100 0. 4.22150 0. 4.22200 0. 4.22250 0. 4.22300 0. 4.22350 0. 4.22400 0. 4.22450 0. 4.22500 0. 4.22550 0. 4.22600 0. 4.22650 0. 4.22700 0. 4.22750 0. 4.22800 0. 4.22850 0. 4.22900 0. 4.22950 0. 4.23000 0. 4.23050 0. 4.23100 0. 4.23150 0. 4.23200 0. 4.23250 0. 4.23300 0. 4.23350 0. 4.23400 0. 4.23450 0. 4.23500 0. 4.23550 0. 4.23600 0. 4.23650 0. 4.23700 0. 4.23750 0. 4.23800 0. 4.23850 0. 4.23900 0. 4.23950 0. 4.24000 0. 4.24050 0. 4.24100 0. 4.24150 0. 4.24200 0. 4.24250 0. 4.24300 0. 4.24350 0. 4.24400 0. 4.24450 0. 4.24500 0. 4.24550 0. 4.24600 0. 4.24650 0. 4.24700 0. 4.24750 0. 4.24800 0. 4.24850 0. 4.24900 0. 4.24950 0. 4.25000 0. 4.25050 0. 4.25100 0. 4.25150 0. 4.25200 0. 4.25250 0. 4.25300 0. 4.25350 0. 4.25400 0. 4.25450 0. 4.25500 0. 4.25550 0. 4.25600 0. 4.25650 0. 4.25700 0. 4.25750 0. 4.25800 0. 4.25850 0. 4.25900 0. 4.25950 0. 4.26000 0. 4.26050 0. 4.26100 0. 4.26150 0. 4.26200 0. 4.26250 0. 4.26300 0. 4.26350 0. 4.26400 0. 4.26450 0. 4.26500 0. 4.26550 0. 4.26600 0. 4.26650 0. 4.26700 0. 4.26750 0. 4.26800 0. 4.26850 0. 4.26900 0. 4.26950 0. 4.27000 0. 4.27050 0. 4.27100 0. 4.27150 0. 4.27200 0. 4.27250 0. 4.27300 0. 4.27350 0. 4.27400 0. 4.27450 0. 4.27500 0. 4.27550 0. 4.27600 0. 4.27650 0. 4.27700 0. 4.27750 0. 4.27800 0. 4.27850 0. 4.27900 0. 4.27950 0. 4.28000 0. 4.28050 0. 4.28100 0. 4.28150 0. 4.28200 0. 4.28250 0. 4.28300 0. 4.28350 0. 4.28400 0. 4.28450 0. 4.28500 0. 4.28550 0. 4.28600 0. 4.28650 0. 4.28700 0. 4.28750 0. 4.28800 0. 4.28850 0. 4.28900 0. 4.28950 0. 4.29000 0. 4.29050 0. 4.29100 0. 4.29150 0. 4.29200 0. 4.29250 0. 4.29300 0. 4.29350 0. 4.29400 0. 4.29450 0. 4.29500 0. 4.29550 0. 4.29600 0. 4.29650 0. 4.29700 0. 4.29750 0. 4.29800 0. 4.29850 0. 4.29900 0. 4.29950 0. 4.30000 0. 4.30050 0. 4.30100 0. 4.30150 0. 4.30200 0. 4.30250 0. 4.30300 0. 4.30350 0. 4.30400 0. 4.30450 0. 4.30500 0. 4.30550 0. 4.30600 0. 4.30650 0. 4.30700 0. 4.30750 0. 4.30800 0. 4.30850 0. 4.30900 0. 4.30950 0. 4.31000 0. 4.31050 0. 4.31100 0. 4.31150 0. 4.31200 0. 4.31250 0. 4.31300 0. 4.31350 0. 4.31400 0. 4.31450 0. 4.31500 0. 4.31550 0. 4.31600 0. 4.31650 0. 4.31700 0. 4.31750 0. 4.31800 0. 4.31850 0. 4.31900 0. 4.31950 0. 4.32000 0. 4.32050 0. 4.32100 0. 4.32150 0. 4.32200 0. 4.32250 0. 4.32300 0. 4.32350 0. 4.32400 0. 4.32450 0. 4.32500 0. 4.32550 0. 4.32600 0. 4.32650 0. 4.32700 0. 4.32750 0. 4.32800 0. 4.32850 0. 4.32900 0. 4.32950 0. 4.33000 0. 4.33050 0. 4.33100 0. 4.33150 0. 4.33200 0. 4.33250 0. 4.33300 0. 4.33350 0. 4.33400 0. 4.33450 0. 4.33500 0. 4.33550 0. 4.33600 0. 4.33650 0. 4.33700 0. 4.33750 0. 4.33800 0. 4.33850 0. 4.33900 0. 4.33950 0. 4.34000 0. 4.34050 0. 4.34100 0. 4.34150 0. 4.34200 0. 4.34250 0. 4.34300 0. 4.34350 0. 4.34400 0. 4.34450 0. 4.34500 0. 4.34550 0. 4.34600 0. 4.34650 0. 4.34700 0. 4.34750 0. 4.34800 0. 4.34850 0. 4.34900 0. 4.34950 0. 4.35000 0. 4.35050 0. 4.35100 0. 4.35150 0. 4.35200 0. 4.35250 0. 4.35300 0. 4.35350 0. 4.35400 0. 4.35450 0. 4.35500 0. 4.35550 0. 4.35600 0. 4.35650 0. 4.35700 0. 4.35750 0. 4.35800 0. 4.35850 0. 4.35900 0. 4.35950 0. 4.36000 0. 4.36050 0. 4.36100 0. 4.36150 0. 4.36200 0. 4.36250 0. 4.36300 0. 4.36350 0. 4.36400 0. 4.36450 0. 4.36500 0. 4.36550 0. 4.36600 0. 4.36650 0. 4.36700 0. 4.36750 0. 4.36800 0. 4.36850 0. 4.36900 0. 4.36950 0. 4.37000 0. 4.37050 0. 4.37100 0. 4.37150 1.45745E-09 4.37200 1.89976E-07 4.37250 4.72338E-06 4.37300 3.39044E-05 4.37350 6.98734E-05 4.37400 4.10755E-05 4.37450 7.34538E-06 4.37500 1.28891E-05 4.37550 9.75554E-05 4.37600 2.52611E-04 4.37650 2.15945E-04 4.37700 5.83381E-05 4.37750 2.88221E-05 4.37800 2.57909E-04 4.37850 8.91630E-04 4.37900 1.24768E-03 4.37950 1.53929E-03 4.38000 1.96160E-03 4.38050 1.23701E-03 4.38100 4.66956E-04 4.38150 1.91997E-04 4.38200 4.84889E-05 4.38250 1.52762E-05 4.38300 7.38050E-05 4.38350 3.29145E-04 4.38400 4.96661E-04 4.38450 2.15111E-04 4.38500 2.70901E-05 4.38550 4.54316E-06 4.38600 3.78773E-05 4.38650 1.16438E-04 4.38700 1.14703E-04 4.38750 3.99505E-05 4.38800 4.75128E-06 4.38850 1.50953E-06 4.38900 1.55983E-05 4.38950 4.84433E-05 4.39000 4.02808E-05 4.39050 8.91040E-06 4.39100 5.22384E-07 4.39150 8.81681E-09 4.39200 1.41008E-07 4.39250 3.35473E-06 4.39300 2.13106E-05 4.39350 3.65936E-05 4.39400 1.70251E-05 4.39450 2.15445E-06 4.39500 4.58233E-07 4.39550 7.33621E-06 4.39600 3.87604E-05 4.39650 5.75940E-05 4.39700 2.43400E-05 4.39750 2.92557E-06 4.39800 1.24957E-07 4.39850 1.40379E-06 4.39900 2.08596E-05 4.39950 8.92652E-05 4.40000 1.12751E-04 4.40050 4.28264E-05 4.40100 4.88213E-06 4.40150 7.90382E-07 4.40200 1.14748E-05 4.40250 6.14110E-05 4.40300 9.88700E-05 4.40350 4.93643E-05 4.40400 8.08794E-06 4.40450 5.09460E-07 4.40500 9.36044E-07 4.40550 9.44320E-06 4.40600 3.92555E-05 4.40650 8.75527E-05 4.40700 8.32043E-05 4.40750 2.51813E-05 4.40800 2.29733E-06 4.40850 2.45936E-06 4.40900 2.59770E-05 4.40950 1.37453E-04 4.41000 2.69817E-04 4.41050 1.77976E-04 4.41100 3.93874E-05 4.41150 3.10249E-06 4.41200 1.26939E-05 4.41250 1.81001E-04 4.41300 8.06423E-04 4.41350 1.19935E-03 4.41400 6.33457E-04 4.41450 1.19440E-04 4.41500 9.35754E-06 4.41550 5.50369E-05 4.41600 5.35522E-04 4.41650 1.82873E-03 4.41700 2.22286E-03 4.41750 9.24570E-04 4.41800 1.23985E-04 4.41850 1.03996E-05 4.41900 1.23113E-04 4.41950 8.98436E-04 4.42000 2.14764E-03 4.42050 1.75847E-03 4.42100 6.27424E-04 4.42150 2.04135E-04 4.42200 4.96044E-05 4.42250 9.21165E-05 4.42300 8.01634E-04 4.42350 2.46058E-03 4.42400 2.63355E-03 4.42450 1.71595E-03 4.42500 9.48818E-04 4.42550 2.35021E-04 4.42600 1.45257E-04 4.42650 9.98260E-04 4.42700 2.47239E-03 4.42750 3.05703E-03 4.42800 5.99212E-03 4.42850 7.43127E-03 4.42900 3.17459E-03 4.42950 4.97004E-04 4.43000 5.45414E-04 4.43050 2.09134E-03 4.43100 7.48731E-03 4.43150 2.07705E-02 4.43200 2.41198E-02 4.43250 1.09202E-02 4.43300 1.85035E-03 4.43350 1.53272E-04 4.43400 7.68504E-04 4.43450 7.40557E-03 4.43500 2.81424E-02 4.43550 3.74237E-02 4.43600 1.68647E-02 4.43650 2.83228E-03 4.43700 3.98465E-04 4.43750 9.84583E-04 4.43800 8.99221E-03 4.43850 2.94991E-02 4.43900 3.68256E-02 4.43950 2.60548E-02 4.44000 1.14614E-02 4.44050 3.78331E-03 4.44100 1.24692E-03 4.44150 3.34536E-03 4.44200 1.65728E-02 4.44250 4.74705E-02 4.44300 6.21879E-02 4.44350 3.59381E-02 4.44400 2.09443E-02 4.44450 1.27589E-02 4.44500 4.14917E-03 4.44550 1.26294E-02 4.44600 5.40787E-02 4.44650 7.95956E-02 4.44700 4.98576E-02 4.44750 4.66747E-02 4.44800 4.48139E-02 4.44850 1.79792E-02 4.44900 9.38092E-03 4.44950 3.54121E-02 4.45000 6.63210E-02 4.45050 5.00071E-02 4.45100 5.48736E-02 4.45150 6.82396E-02 4.45200 4.91982E-02 4.45250 2.71416E-02 4.45300 1.39817E-02 4.45350 2.82739E-02 4.45400 3.27536E-02 4.45450 3.67099E-02 4.45500 6.26726E-02 4.45550 5.59118E-02 4.45600 5.08018E-02 4.45650 3.73346E-02 4.45700 1.23777E-02 4.45750 8.11773E-03 4.45800 1.38328E-02 4.45850 3.98425E-02 4.45900 6.04592E-02 4.45950 6.74567E-02 4.46000 9.12996E-02 4.46050 5.46578E-02 4.46100 1.28652E-02 4.46150 8.48518E-03 4.46200 2.03308E-02 4.46250 5.61681E-02 4.46300 6.61649E-02 4.46350 6.20434E-02 4.46400 8.55826E-02 4.46450 5.19454E-02 4.46500 1.48358E-02 4.46550 8.78687E-03 4.46600 1.58777E-02 4.46650 4.46134E-02 4.46700 6.23438E-02 4.46750 5.19429E-02 4.46800 6.29921E-02 4.46850 4.42479E-02 4.46900 1.86556E-02 4.46950 1.63210E-02 4.47000 1.66989E-02 4.47050 4.96097E-02 4.47100 1.03858E-01 4.47150 7.99682E-02 4.47200 3.88077E-02 4.47250 4.50153E-02 4.47300 3.94336E-02 4.47350 2.52310E-02 4.47400 1.78227E-02 4.47450 1.76451E-02 4.47500 6.17339E-02 4.47550 0.105105 4.47600 7.09658E-02 4.47650 7.10820E-02 4.47700 0.109933 4.47750 6.84886E-02 4.47800 2.87308E-02 4.47850 3.33235E-02 4.47900 3.71116E-02 4.47950 6.19893E-02 4.48000 9.37548E-02 4.48050 7.08317E-02 4.48100 7.26721E-02 4.48150 0.116146 4.48200 8.06277E-02 4.48250 4.89525E-02 4.48300 9.69953E-02 4.48350 1.00434E-01 4.48400 9.13643E-02 4.48450 0.138209 4.48500 0.106413 4.48550 7.97803E-02 4.48600 0.136360 4.48650 0.122351 4.48700 7.01300E-02 4.48750 0.106394 4.48800 0.146659 4.48850 0.106203 4.48900 0.125071 4.48950 0.198020 4.49000 0.156973 4.49050 0.157898 4.49100 0.246504 4.49150 0.211197 4.49200 0.188060 4.49250 0.295959 4.49300 0.266987 4.49350 0.190025 4.49400 0.278656 4.49450 0.302146 4.49500 0.244458 4.49550 0.269450 4.49600 0.316912 4.49650 0.409150 4.49700 0.463783 4.49750 0.397145 4.49800 0.363814 4.49850 0.271170 4.49900 0.283891 4.49950 0.364703 4.50000 0.271629 4.50050 0.225996 4.50100 0.312102 4.50150 0.277067 4.50200 0.191142 4.50250 0.274353 4.50300 0.290309 4.50350 0.159419 4.50400 0.168762 4.50450 0.241677 4.50500 0.170121 4.50550 0.112904 4.50600 0.167374 4.50650 0.188593 4.50700 0.128868 4.50750 1.04182E-01 4.50800 0.180653 4.50850 0.195243 4.50900 0.110563 4.50950 0.150260 4.51000 0.240227 4.51050 0.168189 4.51100 0.119610 4.51150 0.221681 4.51200 0.239078 4.51250 0.131175 4.51300 0.141440 4.51350 0.242094 4.51400 0.193683 4.51450 0.106401 4.51500 0.178212 4.51550 0.222937 4.51600 0.124254 4.51650 1.01912E-01 4.51700 0.192672 4.51750 0.188176 4.51800 0.105007 4.51850 0.112480 4.51900 0.188709 4.51950 0.173521 4.52000 9.91521E-02 4.52050 0.139927 4.52100 0.237857 4.52150 0.193776 4.52200 0.107695 4.52250 0.187350 4.52300 0.273411 4.52350 0.186735 4.52400 0.120641 4.52450 0.222486 4.52500 0.281026 4.52550 0.173592 4.52600 0.122600 4.52650 0.238656 4.52700 0.277770 4.52750 0.162242 4.52800 0.149742 4.52850 0.284882 4.52900 0.288227 4.52950 0.153361 4.53000 0.160247 4.53050 0.283991 4.53100 0.260731 4.53150 0.137482 4.53200 0.133571 4.53250 0.232300 4.53300 0.244068 4.53350 0.152218 4.53400 0.151617 4.53450 0.237545 4.53500 0.252812 4.53550 0.161757 4.53600 0.151443 4.53650 0.277269 4.53700 0.318662 4.53750 0.187061 4.53800 0.147202 4.53850 0.269558 4.53900 0.302643 4.53950 0.195393 4.54000 0.183365 4.54050 0.354189 4.54100 0.412130 4.54150 0.261908 4.54200 0.207409 4.54250 0.375345 4.54300 0.442430 4.54350 0.285561 4.54400 0.199164 4.54450 0.329507 4.54500 0.385887 4.54550 0.246365 4.54600 0.175285 4.54650 0.342953 4.54700 0.427319 4.54750 0.284989 4.54800 0.187204 4.54850 0.304000 4.54900 0.401631 4.54950 0.326879 4.55000 0.230387 4.55050 0.279106 4.55100 0.381733 4.55150 0.370345 4.55200 0.286873 4.55250 0.246785 4.55300 0.340693 4.55350 0.418745 4.55400 0.372055 4.55450 0.251893 4.55500 0.300811 4.55550 0.427885 4.55600 0.447137 4.55650 0.289835 4.55700 0.259513 4.55750 0.439397 4.55800 0.528404 4.55850 0.364406 4.55900 0.226761 4.55950 0.378819 4.56000 0.546608 4.56050 0.483872 4.56100 0.324367 4.56150 0.427118 4.56200 0.608897 4.56250 0.574518 4.56300 0.388083 4.56350 0.401004 4.56400 0.579279 4.56450 0.597097 4.56500 0.413596 4.56550 0.269749 4.56600 0.346288 4.56650 0.478049 4.56700 0.447182 4.56750 0.330920 4.56800 0.440403 4.56850 0.589162 4.56900 0.537835 4.56950 0.426682 4.57000 0.404479 4.57050 0.522213 4.57100 0.569687 4.57150 0.507571 4.57200 0.414391 4.57250 0.354471 4.57300 0.356833 4.57350 0.396518 4.57400 0.494691 4.57450 0.479048 4.57500 0.522011 4.57550 0.555980 4.57600 0.574138 4.57650 0.529350 4.57700 0.497079 4.57750 0.560605 4.57800 0.616636 4.57850 0.626750 4.57900 0.536330 4.57950 0.479669 4.58000 0.434754 4.58050 0.463036 4.58100 0.560919 4.58150 0.513867 4.58200 0.534364 4.58250 0.664911 4.58300 0.721010 4.58350 0.618050 4.58400 0.480163 4.58450 0.551011 4.58500 0.695948 4.58550 0.722765 4.58600 0.619436 4.58650 0.563518 4.58700 0.591772 4.58750 0.510627 4.58800 0.520366 4.58850 0.556750 4.58900 0.641179 4.58950 0.747972 4.59000 0.762814 4.59050 0.683239 4.59100 0.618977 4.59150 0.702012 4.59200 0.775512 4.59250 0.752038 4.59300 0.677955 4.59350 0.656053 4.59400 0.696238 4.59450 0.607677 4.59500 0.467573 4.59550 0.546125 4.59600 0.670062 4.59650 0.748281 4.59700 0.757018 4.59750 0.727802 4.59800 0.720025 4.59850 0.727623 4.59900 0.755261 4.59950 0.763617 4.60000 0.758367 4.60050 0.747376 4.60100 0.725691 4.60150 0.717794 4.60200 0.587047 4.60250 0.478013 4.60300 0.594776 4.60350 0.714554 4.60400 0.766488 4.60450 0.756868 4.60500 0.730058 4.60550 0.682126 4.60600 0.701010 4.60650 0.750245 4.60700 0.774148 4.60750 0.796512 4.60800 0.787342 4.60850 0.757996 4.60900 0.734921 4.60950 0.615622 4.61000 0.488409 4.61050 0.602528 4.61100 0.741940 4.61150 0.784682 4.61200 0.789679 4.61250 0.795773 4.61300 0.807854 4.61350 0.795450 4.61400 0.776823 4.61450 0.791077 4.61500 0.818900 4.61550 0.817382 4.61600 0.782341 4.61650 0.757909 4.61700 0.686499 4.61750 0.521999 4.61800 0.555692 4.61850 0.712795 4.61900 0.793219 4.61950 0.818556 4.62000 0.809968 4.62050 0.807923 4.62100 0.813304 4.62150 0.801805 4.62200 0.758106 4.62250 0.753093 4.62300 0.789792 4.62350 0.788880 4.62400 0.779832 4.62450 0.744626 4.62500 0.582666 4.62550 0.390198 4.62600 0.452906 4.62650 0.673806 4.62700 0.801396 4.62750 0.824734 4.62800 0.810855 4.62850 0.809199 4.62900 0.826816 4.62950 0.831380 4.63000 0.824072 4.63050 0.819503 4.63100 0.831220 4.63150 0.835848 4.63200 0.816133 4.63250 0.736666 4.63300 0.574816 4.63350 0.563968 4.63400 0.730104 4.63450 0.804920 4.63500 0.828817 4.63550 0.843008 4.63600 0.834292 4.63650 0.789443 4.63700 0.757682 4.63750 0.800739 4.63800 0.837821 4.63850 0.835727 4.63900 0.832705 4.63950 0.841019 4.64000 0.829555 4.64050 0.745867 4.64100 0.578996 4.64150 0.597468 4.64200 0.767462 4.64250 0.832102 4.64300 0.834120 4.64350 0.840825 4.64400 0.849192 4.64450 0.837295 4.64500 0.760230 4.64550 0.642034 4.64600 0.696561 4.64650 0.812627 4.64700 0.842540 4.64750 0.840044 4.64800 0.831914 4.64850 0.777556 4.64900 0.645457 4.64950 0.620872 4.65000 0.763553 4.65050 0.840963 4.65100 0.856535 4.65150 0.855341 4.65200 0.851941 4.65250 0.855203 4.65300 0.857114 4.65350 0.857082 4.65400 0.848935 4.65450 0.835950 4.65500 0.832774 4.65550 0.840946 4.65600 0.815956 4.65650 0.723193 4.65700 0.641081 4.65750 0.648287 4.65800 0.762565 4.65850 0.839436 4.65900 0.854972 4.65950 0.853870 4.66000 0.822492 4.66050 0.708598 4.66100 0.617091 4.66150 0.725282 4.66200 0.818992 4.66250 0.835667 4.66300 0.849907 4.66350 0.858283 4.66400 0.861696 4.66450 0.866448 4.66500 0.869763 4.66550 0.867957 4.66600 0.869060 4.66650 0.872489 4.66700 0.871943 4.66750 0.864440 4.66800 0.858034 4.66850 0.866041 4.66900 0.871219 4.66950 0.868547 4.67000 0.867641 4.67050 0.867601 4.67100 0.865296 4.67150 0.866575 4.67200 0.868571 4.67250 0.860030 4.67300 0.829256 4.67350 0.723717 4.67400 0.525465 4.67450 0.538248 4.67500 0.746182 4.67550 0.844205 4.67600 0.845041 4.67650 0.784261 4.67700 0.757775 4.67750 0.812389 4.67800 0.803883 4.67850 0.684300 4.67900 0.601512 4.67950 0.704575 4.68000 0.768835 4.68050 0.728408 4.68100 0.564779 4.68150 0.505015 4.68200 0.620747 4.68250 0.603898 4.68300 0.681057 4.68350 0.820861 4.68400 0.865183 4.68450 0.867401 4.68500 0.859359 4.68550 0.860573 4.68600 0.867831 4.68650 0.868084 4.68700 0.865430 4.68750 0.861845 4.68800 0.854960 4.68850 0.850938 4.68900 0.846527 4.68950 0.834266 4.69000 0.821477 4.69050 0.747092 4.69100 0.579122 4.69150 0.578919 4.69200 0.728203 4.69250 0.766684 4.69300 0.771687 4.69350 0.795203 4.69400 0.786570 4.69450 0.746081 4.69500 0.685483 4.69550 0.569517 4.69600 0.543056 4.69650 0.607816 4.69700 0.676755 4.69750 0.712564 4.69800 0.702857 4.69850 0.704621 4.69900 0.691231 4.69950 0.557957 4.70000 0.453651 4.70050 0.577039 4.70100 0.676542 4.70150 0.680948 4.70200 0.726007 4.70250 0.736078 4.70300 0.704099 4.70350 0.655689 4.70400 0.703656 4.70450 0.714904 4.70500 0.660826 4.70550 0.573229 4.70600 0.511776 4.70650 0.574674 4.70700 0.603852 4.70750 0.539736 4.70800 0.528388 4.70850 0.470414 4.70900 0.466541 4.70950 0.593707 4.71000 0.614142 4.71050 0.620341 4.71100 0.630155 4.71150 0.632926 4.71200 0.663445 4.71250 0.635801 4.71300 0.600647 4.71350 0.610276 4.71400 0.577977 4.71450 0.606113 4.71500 0.648169 4.71550 0.614552 4.71600 0.606094 4.71650 0.611882 4.71700 0.570529 4.71750 0.451189 4.71800 0.436903 4.71850 0.563594 4.71900 0.634656 4.71950 0.656946 4.72000 0.703936 4.72050 0.708192 4.72100 0.642753 4.72150 0.615425 4.72200 0.646924 4.72250 0.670394 4.72300 0.714933 4.72350 0.731532 4.72400 0.692305 4.72450 0.653345 4.72500 0.620488 4.72550 0.624109 4.72600 0.624477 4.72650 0.494709 4.72700 0.450227 4.72750 0.440544 4.72800 0.310740 4.72850 0.367687 4.72900 0.513731 4.72950 0.595787 4.73000 0.716981 4.73050 0.762418 4.73100 0.787642 4.73150 0.793170 4.73200 0.772535 4.73250 0.815162 4.73300 0.861253 4.73350 0.864253 4.73400 0.845788 4.73450 0.843678 4.73500 0.781021 4.73550 0.607287 4.73600 0.564569 4.73650 0.742332 4.73700 0.835784 4.73750 0.813108 4.73800 0.749092 4.73850 0.682665 4.73900 0.663193 4.73950 0.684330 4.74000 0.701637 4.74050 0.684549 4.74100 0.663699 4.74150 0.660747 4.74200 0.657239 4.74250 0.680859 4.74300 0.687300 4.74350 0.662139 4.74400 0.657887 4.74450 0.570056 4.74500 0.408081 4.74550 0.437466 4.74600 0.584780 4.74650 0.663945 4.74700 0.623196 4.74750 0.545606 4.74800 0.600220 4.74850 0.657027 4.74900 0.649640 4.74950 0.655328 4.75000 0.696800 4.75050 0.744425 4.75100 0.748129 4.75150 0.740780 4.75200 0.738124 4.75250 0.745827 4.75300 0.743708 4.75350 0.706983 4.75400 0.588811 4.75450 0.503417 4.75500 0.604921 4.75550 0.704963 4.75600 0.739933 4.75650 0.755776 4.75700 0.771805 4.75750 0.755600 4.75800 0.745322 4.75850 0.730420 4.75900 0.731125 4.75950 0.726148 4.76000 0.708789 4.76050 0.589426 4.76100 0.515739 4.76150 0.643361 4.76200 0.724355 4.76250 0.726648 4.76300 0.670923 4.76350 0.578997 4.76400 0.515972 4.76450 0.641497 4.76500 0.735701 4.76550 0.763278 4.76600 0.758415 4.76650 0.731340 4.76700 0.712242 4.76750 0.596043 4.76800 0.525150 4.76850 0.651507 4.76900 0.759814 4.76950 0.773897 4.77000 0.763842 4.77050 0.762660 4.77100 0.749436 4.77150 0.756904 4.77200 0.752909 4.77250 0.695896 4.77300 0.593814 4.77350 0.537548 4.77400 0.633548 4.77450 0.707854 4.77500 0.709968 4.77550 0.648468 4.77600 0.556209 4.77650 0.447339 4.77700 0.456899 4.77750 0.600157 4.77800 0.685273 4.77850 0.749400 4.77900 0.779240 4.77950 0.764904 4.78000 0.749410 4.78050 0.697490 4.78100 0.715675 4.78150 0.774001 4.78200 0.768034 4.78250 0.675549 4.78300 0.510518 4.78350 0.406147 4.78400 0.288206 4.78450 0.172570 4.78500 0.262867 4.78550 0.457925 4.78600 0.648541 4.78650 0.751558 4.78700 0.760176 4.78750 0.674504 4.78800 0.601737 4.78850 0.668170 4.78900 0.750152 4.78950 0.734852 4.79000 0.612113 4.79050 0.431060 4.79100 0.427030 4.79150 0.548432 4.79200 0.694302 4.79250 0.715701 4.79300 0.622457 4.79350 0.650249 4.79400 0.727964 4.79450 0.703077 4.79500 0.612668 4.79550 0.688245 4.79600 0.769360 4.79650 0.774147 4.79700 0.802170 4.79750 0.811441 4.79800 0.741624 4.79850 0.614809 4.79900 0.615796 4.79950 0.707922 4.80000 0.763260 4.80050 0.790090 4.80100 0.809754 4.80150 0.753070 4.80200 0.592246 4.80250 0.497785 4.80300 0.469637 4.80350 0.526414 4.80400 0.711431 4.80450 0.779202 4.80500 0.722147 4.80550 0.614664 4.80600 0.569105 4.80650 0.688085 4.80700 0.761042 4.80750 0.765058 4.80800 0.760533 4.80850 0.736691 4.80900 0.620507 4.80950 0.490889 4.81000 0.480425 4.81050 0.401817 4.81100 0.279015 4.81150 0.238285 4.81200 0.180438 4.81250 9.74173E-02 4.81300 3.55621E-02 4.81350 8.14577E-03 4.81400 2.17195E-03 4.81450 2.01062E-02 4.81500 0.141790 4.81550 0.403876 4.81600 0.617347 4.81650 0.703843 4.81700 0.725496 4.81750 0.749887 4.81800 0.767941 4.81850 0.744553 4.81900 0.755550 4.81950 0.761361 4.82000 0.718029 4.82050 0.565820 4.82100 0.393617 4.82150 0.474360 4.82200 0.552953 4.82250 0.612290 4.82300 0.679701 4.82350 0.706343 4.82400 0.755354 4.82450 0.716698 4.82500 0.602095 4.82550 0.514619 4.82600 0.538330 4.82650 0.708099 4.82700 0.789166 4.82750 0.806050 4.82800 0.795635 4.82850 0.742044 4.82900 0.596950 4.82950 0.479017 4.83000 0.620498 4.83050 0.757390 4.83100 0.793663 4.83150 0.810594 4.83200 0.780772 4.83250 0.640200 4.83300 0.445442 4.83350 0.505438 4.83400 0.685670 4.83450 0.782357 4.83500 0.830806 4.83550 0.809275 4.83600 0.668628 4.83650 0.452788 4.83700 0.468499 4.83750 0.665062 4.83800 0.771693 4.83850 0.799321 4.83900 0.764617 4.83950 0.631356 4.84000 0.357103 4.84050 0.152292 4.84100 0.252014 4.84150 0.432618 4.84200 0.374438 4.84250 0.155458 4.84300 6.28569E-02 4.84350 0.113365 4.84400 0.228425 4.84450 0.479885 4.84500 0.703947 4.84550 0.797364 4.84600 0.818047 4.84650 0.783710 4.84700 0.612458 4.84750 0.397901 4.84800 0.494551 4.84850 0.720490 4.84900 0.814522 4.84950 0.828840 4.85000 0.798362 4.85050 0.670889 4.85100 0.433767 4.85150 0.413567 4.85200 0.576594 4.85250 0.510377 4.85300 0.277666 4.85350 0.282468 4.85400 0.458609 4.85450 0.410868 4.85500 0.383864 4.85550 0.620799 4.85600 0.798979 4.85650 0.848012 4.85700 0.849923 4.85750 0.789367 4.85800 0.592586 4.85850 0.417267 4.85900 0.574977 4.85950 0.788882 4.86000 0.863859 4.86050 0.870658 4.86100 0.834939 4.86150 0.685507 4.86200 0.460641 4.86250 0.530584 4.86300 0.766927 4.86350 0.865598 4.86400 0.880814 4.86450 0.857834 4.86500 0.741992 4.86550 0.512099 4.86600 0.493559 4.86650 0.725128 4.86700 0.847706 4.86750 0.867656 4.86800 0.861693 4.86850 0.790927 4.86900 0.589554 4.86950 0.482378 4.87000 0.677542 4.87050 0.822839 4.87100 0.847713 4.87150 0.870820 4.87200 0.849180 4.87250 0.694764 4.87300 0.515188 4.87350 0.631365 4.87400 0.816353 4.87450 0.877400 4.87500 0.889860 4.87550 0.865605 4.87600 0.752504 4.87650 0.562716 4.87700 0.598689 4.87750 0.790160 4.87800 0.859733 4.87850 0.857209 4.87900 0.846833 4.87950 0.801162 4.88000 0.645042 4.88050 0.597874 4.88100 0.769811 4.88150 0.867954 4.88200 0.892274 4.88250 0.898362 4.88300 0.866682 4.88350 0.730721 4.88400 0.612808 4.88450 0.735133 4.88500 0.842637 4.88550 0.795206 4.88600 0.607698 4.88650 0.526618 4.88700 0.622861 4.88750 0.616336 4.88800 0.709130 4.88850 0.844434 4.88900 0.885447 4.88950 0.889212 4.89000 0.874620 4.89050 0.811526 4.89100 0.655979 4.89150 0.525091 4.89200 0.362396 4.89250 0.230886 4.89300 0.422321 4.89350 0.688889 4.89400 0.787085 4.89450 0.733354 4.89500 0.697624 4.89550 0.784001 4.89600 0.824738 4.89650 0.802696 4.89700 0.725022 4.89750 0.542285 4.89800 0.259147 4.89850 6.65506E-02 4.89900 8.91169E-02 4.89950 0.309456 4.90000 0.576836 4.90050 0.736871 4.90100 0.804667 4.90150 0.804424 4.90200 0.758348 4.90250 0.795964 4.90300 0.868243 4.90350 0.890752 4.90400 0.895579 4.90450 0.899456 4.90500 0.887327 4.90550 0.837805 4.90600 0.820955 4.90650 0.842177 4.90700 0.791803 4.90750 0.605336 4.90800 0.450908 4.90850 0.550268 4.90900 0.701900 4.90950 0.805599 4.91000 0.880535 4.91050 0.910697 4.91100 0.916575 4.91150 0.919761 4.91200 0.917234 4.91250 0.896292 4.91300 0.873025 4.91350 0.886539 4.91400 0.906928 4.91450 0.912202 4.91500 0.905793 4.91550 0.860829 4.91600 0.753690 4.91650 0.729827 4.91700 0.836869 4.91750 0.901277 4.91800 0.914776 4.91850 0.921646 4.91900 0.923248 4.91950 0.914975 4.92000 0.899019 4.92050 0.897896 4.92100 0.914163 4.92150 0.922408 4.92200 0.923671 4.92250 0.923432 4.92300 0.921693 4.92350 0.912444 4.92400 0.903260 4.92450 0.910452 4.92500 0.914714 4.92550 0.905103 4.92600 0.894219 4.92650 0.901629 4.92700 0.911601 4.92750 0.905200 4.92800 0.902927 4.92850 0.914693 4.92900 0.920531 4.92950 0.919312 4.93000 0.914948 4.93050 0.910009 4.93100 0.897104 4.93150 0.872795 4.93200 0.844848 4.93250 0.748796 4.93300 0.508844 4.93350 0.328327 4.93400 0.268369 4.93450 0.304112 4.93500 0.558504 4.93550 0.761213 4.93600 0.850283 4.93650 0.888722 4.93700 0.901087 4.93750 0.904215 4.93800 0.905882 4.93850 0.903727 4.93900 0.890534 4.93950 0.884923 4.94000 0.896452 4.94050 0.897952 4.94100 0.885697 4.94150 0.853419 4.94200 0.753947 4.94250 0.511115 4.94300 0.286938 4.94350 0.415277 4.94400 0.691181 4.94450 0.831428 4.94500 0.872874 4.94550 0.885475 4.94600 0.892754 4.94650 0.885846 4.94700 0.871963 4.94750 0.881103 4.94800 0.887810 4.94850 0.873222 4.94900 0.846001 4.94950 0.841226 4.95000 0.840484 4.95050 0.804977 4.95100 0.749460 4.95150 0.652499 4.95200 0.433656 4.95250 0.178885 4.95300 0.122308 4.95350 0.206840 4.95400 0.167651 4.95450 8.32333E-02 4.95500 0.177114 4.95550 0.390483 4.95600 0.517064 4.95650 0.494249 4.95700 0.349747 4.95750 0.155425 4.95800 3.60030E-02 4.95850 2.91983E-02 4.95900 0.137671 4.95950 0.348444 4.96000 0.550491 4.96050 0.681161 4.96100 0.758091 4.96150 0.801530 4.96200 0.819314 4.96250 0.830355 4.96300 0.852599 4.96350 0.868109 4.96400 0.871210 4.96450 0.882879 4.96500 0.898115 4.96550 0.902808 4.96600 0.897266 4.96650 0.894569 4.96700 0.906400 4.96750 0.909608 4.96800 0.904752 4.96850 0.908448 4.96900 0.914552 4.96950 0.910934 4.97000 0.901875 4.97050 0.904815 4.97100 0.915721 4.97150 0.916645 4.97200 0.911112 4.97250 0.900285 4.97300 0.897409 4.97350 0.905036 4.97400 0.899399 4.97450 0.893851 4.97500 0.883525 4.97550 0.830868 4.97600 0.659202 4.97650 0.380964 4.97700 0.358490 4.97750 0.622520 4.97800 0.797679 4.97850 0.850080 4.97900 0.847964 4.97950 0.781486 4.98000 0.598841 4.98050 0.326375 4.98100 0.277042 4.98150 0.538119 4.98200 0.762314 4.98250 0.854728 4.98300 0.890579 4.98350 0.906904 4.98400 0.915272 4.98450 0.918136 4.98500 0.909510 4.98550 0.865938 4.98600 0.832236 4.98650 0.880441 4.98700 0.918884 4.98750 0.924630 4.98800 0.925437 4.98850 0.927141 4.98900 0.926206 4.98950 0.922568 4.99000 0.921826 4.99050 0.923215 4.99100 0.919328 4.99150 0.914689 4.99200 0.915728 4.99250 0.916695 4.99300 0.918148 4.99350 0.918296 4.99400 0.921394 4.99450 0.924775 4.99500 0.924046 4.99550 0.922574 4.99600 0.922327 4.99650 0.920933 4.99700 0.913692 4.99750 0.900774 4.99800 0.898561 4.99850 0.902930 4.99900 0.895375 4.99950 0.863371 5.00000 0.810439 5.00050 0.792185 5.00100 0.759818 5.00150 0.622549 5.00200 0.361530 5.00250 0.134370 5.00300 0.169277 5.00350 0.428795 5.00400 0.668715 5.00450 0.790279 5.00500 0.842940 5.00550 0.867617 5.00600 0.880745 5.00650 0.888906 5.00700 0.893607 5.00750 0.894915 5.00800 0.893267 5.00850 0.890179 5.00900 0.889614 5.00950 0.889363 5.01000 0.886010 5.01050 0.881820 5.01100 0.875621 5.01150 0.868860 5.01200 0.862267 5.01250 0.854039 5.01300 0.842320 5.01350 0.826701 5.01400 0.805162 5.01450 0.774552 5.01500 0.727762 5.01550 0.645511 5.01600 0.482977 5.01650 0.242374 5.01700 9.17023E-02 5.01750 7.43204E-02 5.01800 4.27656E-02 5.01850 8.54365E-03 5.01900 6.56119E-04 5.01950 2.49578E-04 5.02000 2.52280E-04 5.02050 2.95188E-03 5.02100 2.79753E-02 5.02150 0.117886 5.02200 0.271942 5.02250 0.428257 5.02300 0.547726 5.02350 0.629427 5.02400 0.682495 5.02450 0.713343 5.02500 0.725219 5.02550 0.729537 5.02600 0.727122 5.02650 0.700811 5.02700 0.638108 5.02750 0.517540 5.02800 0.325704 5.02850 0.126351 5.02900 2.71894E-02 5.02950 4.31819E-02 5.03000 0.178813 5.03050 0.391822 5.03100 0.549878 5.03150 0.587374 5.03200 0.619323 5.03250 0.730223 5.03300 0.809465 5.03350 0.841637 5.03400 0.859447 5.03450 0.865773 5.03500 0.853044 5.03550 0.860358 5.03600 0.889567 5.03650 0.902932 5.03700 0.906848 5.03750 0.908614 5.03800 0.903818 5.03850 0.874708 5.03900 0.841169 5.03950 0.865926 5.04000 0.904428 5.04050 0.919123 5.04100 0.922079 5.04150 0.921388 5.04200 0.913046 5.04250 0.888076 5.04300 0.881021 5.04350 0.901204 5.04400 0.916111 5.04450 0.920106 5.04500 0.911696 5.04550 0.907305 5.04600 0.916366 5.04650 0.916023 5.04700 0.904224 5.04750 0.897815 5.04800 0.843133 5.04850 0.760297 5.04900 0.822202 5.04950 0.906813 5.05000 0.927036 5.05050 0.930295 5.05100 0.931945 5.05150 0.932626 5.05200 0.932100 5.05250 0.929855 5.05300 0.929166 5.05350 0.930793 5.05400 0.930583 5.05450 0.929289 5.05500 0.928592 5.05550 0.923506 5.05600 0.901121 5.05650 0.873174 5.05700 0.889699 5.05750 0.912139 5.05800 0.910428 5.05850 0.891843 5.05900 0.836764 5.05950 0.661715 5.06000 0.399055 5.06050 0.422180 5.06100 0.689779 5.06150 0.846123 5.06200 0.890673 5.06250 0.903984 5.06300 0.913172 5.06350 0.920136 5.06400 0.922859 5.06450 0.923789 5.06500 0.924867 5.06550 0.925355 5.06600 0.925271 5.06650 0.923685 5.06700 0.920150 5.06750 0.919730 5.06800 0.920786 5.06850 0.920327 5.06900 0.919307 5.06950 0.917704 5.07000 0.917066 5.07050 0.915409 5.07100 0.910191 5.07150 0.905888 5.07200 0.906961 5.07250 0.906373 5.07300 0.903445 5.07350 0.899589 5.07400 0.894700 5.07450 0.887531 5.07500 0.872825 5.07550 0.854889 5.07600 0.850293 5.07650 0.842098 5.07700 0.826790 5.07750 0.807216 5.07800 0.781552 5.07850 0.757399 5.07900 0.721259 5.07950 0.659412 5.08000 0.571396 5.08050 0.448757 5.08100 0.291219 5.08150 0.136710 5.08200 3.68731E-02 5.08250 4.71039E-03 5.08300 3.91930E-03 5.08350 2.69501E-02 5.08400 8.30485E-02 5.08450 0.119724 5.08500 8.06327E-02 5.08550 2.52612E-02 5.08600 3.05160E-02 5.08650 0.139254 5.08700 0.337136 5.08750 0.518571 5.08800 0.632302 5.08850 0.706217 5.08900 0.762331 5.08950 0.798324 5.09000 0.821899 5.09050 0.838093 5.09100 0.848732 5.09150 0.857370 5.09200 0.859084 5.09250 0.855978 5.09300 0.867364 5.09350 0.878281 5.09400 0.880940 5.09450 0.880451 5.09500 0.877144 5.09550 0.869747 5.09600 0.851047 5.09650 0.805936 5.09700 0.748649 5.09750 0.671230 5.09800 0.481400 5.09850 0.214377 5.09900 9.60914E-02 5.09950 0.240977 5.10000 0.500371 5.10050 0.684104 5.10100 0.776498 5.10150 0.832239 5.10200 0.857213 5.10250 0.839682 5.10300 0.817595 5.10350 0.854007 5.10400 0.889682 5.10450 0.894187 5.10500 0.881832 5.10550 0.882951 5.10600 0.895977 5.10650 0.895459 5.10700 0.873492 5.10750 0.780051 5.10800 0.631925 5.10850 0.668742 5.10900 0.769185 5.10950 0.732340 5.11000 0.724572 5.11050 0.757531 5.11100 0.659584 5.11150 0.414822 5.11200 0.320324 5.11250 0.505235 5.11300 0.614701 5.11350 0.568199 5.11400 0.409382 5.11450 0.182705 5.11500 5.41840E-02 5.11550 0.115497 5.11600 0.335816 5.11650 0.564735 5.11700 0.708607 5.11750 0.779458 5.11800 0.815410 5.11850 0.848578 5.11900 0.869560 5.11950 0.880131 5.12000 0.887105 5.12050 0.891743 5.12100 0.893909 5.12150 0.890819 5.12200 0.882312 5.12250 0.884941 5.12300 0.893333 5.12350 0.893618 5.12400 0.881341 5.12450 0.820364 5.12500 0.694098 5.12550 0.696540 5.12600 0.813965 5.12650 0.861952 5.12700 0.861497 5.12750 0.828332 5.12800 0.807869 5.12850 0.833132 5.12900 0.826795 5.12950 0.721543 5.13000 0.558831 5.13050 0.597463 5.13100 0.757413 5.13150 0.821759 5.13200 0.827909 5.13250 0.820017 5.13300 0.806262 5.13350 0.786976 5.13400 0.760254 5.13450 0.722022 5.13500 0.667854 5.13550 0.588341 5.13600 0.464204 5.13650 0.288559 5.13700 0.114223 5.13750 2.32351E-02 5.13800 1.20032E-02 5.13850 4.90075E-02 5.13900 9.38655E-02 5.13950 7.71044E-02 5.14000 2.68936E-02 5.14050 9.03515E-03 5.14100 4.39264E-02 5.14150 0.152071 5.14200 0.290068 5.14250 0.384634 5.14300 0.418595 5.14350 0.406507 5.14400 0.366361 5.14450 0.301549 5.14500 0.213296 5.14550 0.118738 5.14600 4.52747E-02 5.14650 9.84843E-03 5.14700 1.01084E-03 5.14750 4.19953E-05 5.14800 1.96166E-05 5.14850 3.83762E-04 5.14900 2.54328E-03 5.14950 5.69234E-03 5.15000 8.87359E-03 5.15050 4.41223E-02 5.15100 0.154988 5.15150 0.302670 5.15200 0.427612 5.15250 0.519266 5.15300 0.577431 5.15350 0.588458 5.15400 0.600293 5.15450 0.674730 5.15500 0.729347 5.15550 0.746004 5.15600 0.747026 5.15650 0.715593 5.15700 0.685806 5.15750 0.734386 5.15800 0.769297 5.15850 0.799457 5.15900 0.825902 5.15950 0.806353 5.16000 0.760983 5.16050 0.792554 5.16100 0.831361 5.16150 0.786951 5.16200 0.705295 5.16250 0.720726 5.16300 0.806409 5.16350 0.852306 5.16400 0.850559 5.16450 0.853556 5.16500 0.869450 5.16550 0.873811 5.16600 0.870794 5.16650 0.858359 5.16700 0.837914 5.16750 0.838405 5.16800 0.832950 5.16850 0.814648 5.16900 0.808555 5.16950 0.793523 5.17000 0.761679 5.17050 0.713669 5.17100 0.640197 5.17150 0.518682 5.17200 0.327264 5.17250 0.127788 5.17300 5.70592E-02 5.17350 0.115877 5.17400 0.192733 5.17450 0.289853 5.17500 0.442921 5.17550 0.529036 5.17600 0.648778 5.17650 0.797829 5.17700 0.854241 5.17750 0.868641 5.17800 0.873097 5.17850 0.868955 5.17900 0.862379 5.17950 0.866251 5.18000 0.875896 5.18050 0.881051 5.18100 0.881590 5.18150 0.880150 5.18200 0.875689 5.18250 0.859303 5.18300 0.836699 5.18350 0.848280 5.18400 0.866882 5.18450 0.869520 5.18500 0.867005 5.18550 0.862763 5.18600 0.851654 5.18650 0.803548 5.18700 0.717678 5.18750 0.733727 5.18800 0.806562 5.18850 0.827511 5.18900 0.819998 5.18950 0.783954 5.19000 0.725039 5.19050 0.719037 5.19100 0.707834 5.19150 0.681905 5.19200 0.720184 5.19250 0.738519 5.19300 0.723167 5.19350 0.692405 5.19400 0.630119 5.19450 0.541790 5.19500 0.487970 5.19550 0.437082 5.19600 0.414073 5.19650 0.377037 5.19700 0.289568 5.19750 0.182818 5.19800 8.60844E-02 5.19850 2.57030E-02 5.19900 4.01882E-03 5.19950 2.75968E-04 5.20000 1.30486E-05 5.20050 4.30023E-05 5.20100 8.63149E-05 5.20150 6.32951E-05 5.20200 5.35923E-04 5.20250 6.79118E-03 5.20300 3.49327E-02 5.20350 9.02334E-02 5.20400 0.175752 5.20450 0.285733 5.20500 0.362380 5.20550 0.389308 5.20600 0.405660 5.20650 0.436620 5.20700 0.438379 5.20750 0.385614 5.20800 0.310848 5.20850 0.284028 5.20900 0.228665 5.20950 0.174114 5.21000 0.154803 5.21050 0.116951 5.21100 6.36240E-02 5.21150 2.37161E-02 5.21200 5.42342E-03 5.21250 6.39848E-04 5.21300 3.18588E-05 5.21350 5.39736E-07 5.21400 2.50503E-06 5.21450 8.78982E-05 5.21500 1.16953E-03 5.21550 6.82322E-03 5.21600 2.40752E-02 5.21650 6.77174E-02 5.21700 0.138812 5.21750 0.220242 5.21800 0.302018 5.21850 0.374292 5.21900 0.433874 5.21950 0.469450 5.22000 0.445407 5.22050 0.421820 5.22100 0.481066 5.22150 0.547905 5.22200 0.610377 5.22250 0.582492 5.22300 0.505570 5.22350 0.560000 5.22400 0.574775 5.22450 0.456961 5.22500 0.490434 5.22550 0.646032 5.22600 0.718339 5.22650 0.733201 5.22700 0.732826 5.22750 0.715325 5.22800 0.632760 5.22850 0.497242 5.22900 0.530176 5.22950 0.634497 5.23000 0.628541 5.23050 0.584769 5.23100 0.579590 5.23150 0.559925 5.23200 0.490666 5.23250 0.368046 5.23300 0.261685 5.23350 0.193110 5.23400 1.03328E-01 5.23450 3.23885E-02 5.23500 5.15729E-03 5.23550 3.64237E-04 5.23600 4.14137E-04 5.23650 5.64691E-03 5.23700 3.39909E-02 5.23750 1.01348E-01 5.23800 0.183745 5.23850 0.240334 5.23900 0.244682 5.23950 0.188810 5.24000 9.72294E-02 5.24050 2.74259E-02 5.24100 3.66085E-03 5.24150 4.95273E-03 5.24200 3.96735E-02 5.24250 0.144874 5.24300 0.297385 5.24350 0.434553 5.24400 0.529351 5.24450 0.565579 5.24500 0.554369 5.24550 0.605048 5.24600 0.671332 5.24650 0.697228 5.24700 0.720877 5.24750 0.729514 5.24800 0.715889 5.24850 0.665501 5.24900 0.550117 5.24950 0.402360 5.25000 0.238537 5.25050 9.64640E-02 5.25100 2.98057E-02 5.25150 7.42896E-02 5.25200 0.246921 5.25250 0.450395 5.25300 0.560536 5.25350 0.631215 5.25400 0.720876 5.25450 0.769450 5.25500 0.772097 5.25550 0.713149 5.25600 0.668164 5.25650 0.722128 5.25700 0.720141 5.25750 0.600486 5.25800 0.417051 5.25850 0.421536 5.25900 0.655543 5.25950 0.795730 5.26000 0.832484 5.26050 0.836954 5.26100 0.813990 5.26150 0.787221 5.26200 0.812185 5.26250 0.840031 5.26300 0.847299 5.26350 0.847621 5.26400 0.844812 5.26450 0.835543 5.26500 0.812124 5.26550 0.784372 5.26600 0.791169 5.26650 0.800164 5.26700 0.797749 5.26750 0.801877 5.26800 0.794326 5.26850 0.757307 5.26900 0.687718 5.26950 0.602093 5.27000 0.510715 5.27050 0.548764 5.27100 0.646577 5.27150 0.663878 5.27200 0.630361 5.27250 0.578754 5.27300 0.508348 5.27350 0.412193 5.27400 0.299225 5.27450 0.178532 5.27500 7.21419E-02 5.27550 1.65859E-02 5.27600 1.89778E-03 5.27650 1.41956E-04 5.27700 8.95184E-04 5.27750 8.95282E-03 5.27800 5.15688E-02 5.27850 0.151602 5.27900 0.271827 5.27950 0.359408 5.28000 0.350505 5.28050 0.281107 5.28100 0.368060 5.28150 0.512502 5.28200 0.571348 5.28250 0.588420 5.28300 0.547481 5.28350 0.434788 5.28400 0.427643 5.28450 0.521540 5.28500 0.546084 5.28550 0.521431 5.28600 0.481271 5.28650 0.435904 5.28700 0.389857 5.28750 0.336538 5.28800 0.269811 5.28850 0.195754 5.28900 0.123185 5.28950 6.22444E-02 5.29000 2.25138E-02 5.29050 5.05633E-03 5.29100 6.02626E-04 5.29150 3.27799E-05 5.29200 6.77782E-07 5.29250 1.11436E-06 5.29300 3.80502E-05 5.29350 5.40606E-04 5.29400 4.49051E-03 5.29450 2.26492E-02 5.29500 6.65216E-02 5.29550 0.131820 5.29600 0.206955 5.29650 0.282080 5.29700 0.350745 5.29750 0.410799 5.29800 0.462355 5.29850 0.506744 5.29900 0.544430 5.29950 0.574371 5.30000 0.597596 5.30050 0.614588 5.30100 0.623953 5.30150 0.623177 5.30200 0.595165 5.30250 0.514671 5.30300 0.436550 5.30350 0.357991 5.30400 0.202466 5.30450 0.134934 5.30500 0.117748 5.30550 5.26849E-02 5.30600 1.03618E-02 5.30650 1.08774E-02 5.30700 6.68695E-02 5.30750 0.205550 5.30800 0.376458 5.30850 0.512955 5.30900 0.604199 5.30950 0.664720 5.31000 0.705791 5.31050 0.733412 5.31100 0.752264 5.31150 0.767509 5.31200 0.781033 5.31250 0.792011 5.31300 0.798645 5.31350 0.803287 5.31400 0.809857 5.31450 0.814803 5.31500 0.816750 5.31550 0.813565 5.31600 0.805610 5.31650 0.804818 5.31700 0.812509 5.31750 0.812451 5.31800 0.788680 5.31850 0.731763 5.31900 0.633332 5.31950 0.449155 5.32000 0.336063 5.32050 0.377198 5.32100 0.388218 5.32150 0.485758 5.32200 0.650292 5.32250 0.762931 5.32300 0.804616 5.32350 0.810988 5.32400 0.809552 5.32450 0.801709 5.32500 0.777613 5.32550 0.758283 5.32600 0.774160 5.32650 0.784196 5.32700 0.770434 5.32750 0.717428 5.32800 0.554756 5.32850 0.328373 5.32900 0.363668 5.32950 0.592174 5.33000 0.714935 5.33050 0.747010 5.33100 0.753955 5.33150 0.752597 5.33200 0.747233 5.33250 0.740214 5.33300 0.733915 5.33350 0.727762 5.33400 0.719693 5.33450 0.709782 5.33500 0.698029 5.33550 0.686103 5.33600 0.673174 5.33650 0.657913 5.33700 0.641196 5.33750 0.623665 5.33800 0.605031 5.33850 0.584078 5.33900 0.560221 5.33950 0.533246 5.34000 0.503052 5.34050 0.470341 5.34100 0.434475 5.34150 0.394457 5.34200 0.348988 5.34250 0.297257 5.34300 0.239931 5.34350 0.176061 5.34400 0.105977 5.34450 4.34366E-02 5.34500 9.53290E-03 5.34550 1.61476E-03 5.34600 2.34086E-03 5.34650 2.67856E-03 5.34700 1.29685E-03 5.34750 2.82016E-04 5.34800 2.68318E-05 5.34850 9.78214E-07 5.34900 9.67941E-09 5.34950 0. 5.35000 0. 5.35050 0. 5.35100 0. 5.35150 0. 5.35200 0. 5.35250 0. 5.35300 0. 5.35350 5.31888E-09 5.35400 6.76332E-07 5.35450 1.98471E-05 5.35500 2.07038E-04 5.35550 8.71145E-04 5.35600 1.59270E-03 5.35650 1.28280E-03 5.35700 4.33812E-04 5.35750 5.68169E-05 5.35800 2.72815E-05 5.35850 5.92407E-04 5.35900 5.78010E-03 5.35950 2.71195E-02 5.36000 7.37757E-02 5.36050 0.139559 5.36100 0.210247 5.36150 0.276243 5.36200 0.333970 5.36250 0.380503 5.36300 0.403590 5.36350 0.370415 5.36400 0.311511 5.36450 0.317030 5.36500 0.434225 5.36550 0.548620 5.36600 0.594000 5.36650 0.613379 5.36700 0.618761 5.36750 0.598626 5.36800 0.572816 5.36850 0.597325 5.36900 0.644184 5.36950 0.653085 5.37000 0.633495 5.37050 0.596025 5.37100 0.498532 5.37150 0.305357 5.37200 0.211598 5.37250 0.369970 5.37300 0.455031 5.37350 0.348942 5.37400 0.400163 5.37450 0.587523 5.37500 0.660934 5.37550 0.635057 5.37600 0.504596 5.37650 0.263859 5.37700 0.136754 5.37750 0.273915 5.37800 0.470142 5.37850 0.523232 5.37900 0.449242 5.37950 0.287111 5.38000 0.111127 5.38050 2.30591E-02 5.38100 3.38744E-02 5.38150 0.154613 5.38200 0.355937 5.38250 0.513644 5.38300 0.592182 5.38350 0.643199 5.38400 0.688041 5.38450 0.710676 5.38500 0.715305 5.38550 0.709487 5.38600 0.668920 5.38650 0.518145 5.38700 0.312264 5.38750 0.361780 5.38800 0.584335 5.38850 0.702335 5.38900 0.735180 5.38950 0.745742 5.39000 0.750866 5.39050 0.753501 5.39100 0.752142 5.39150 0.745741 5.39200 0.732120 5.39250 0.678858 5.39300 0.539583 5.39350 0.469545 5.39400 0.584904 5.39450 0.680037 5.39500 0.704656 5.39550 0.699818 5.39600 0.685307 5.39650 0.660266 5.39700 0.596827 5.39750 0.443420 5.39800 0.218199 5.39850 0.140282 5.39900 0.307492 5.39950 0.501188 5.40000 0.591786 5.40050 0.623304 5.40100 0.631564 5.40150 0.628824 5.40200 0.622599 5.40250 0.613445 5.40300 0.601816 5.40350 0.588640 5.40400 0.572516 5.40450 0.553674 5.40500 0.531586 5.40550 0.505317 5.40600 0.470968 5.40650 0.431782 5.40700 0.392937 5.40750 0.349234 5.40800 0.287778 5.40850 0.184708 5.40900 0.105626 5.40950 8.74172E-02 5.41000 5.53687E-02 5.41050 1.94159E-02 5.41100 3.50767E-03 5.41150 2.85380E-04 5.41200 3.30660E-05 5.41250 5.04517E-04 5.41300 4.17533E-03 5.41350 1.59843E-02 5.41400 3.43283E-02 5.41450 4.96089E-02 5.41500 5.45485E-02 5.41550 4.84713E-02 5.41600 3.57202E-02 5.41650 2.00637E-02 5.41700 6.68404E-03 5.41750 1.01431E-03 5.41800 6.39959E-05 5.41850 2.26461E-06 5.41900 4.90469E-08 5.41950 2.11769E-11 5.42000 0. 5.42050 0. 5.42100 0. 5.42150 0. 5.42200 0. 5.42250 0. 5.42300 0. 5.42350 0. 5.42400 1.54473E-08 5.42450 1.08025E-06 5.42500 2.32928E-05 5.42550 2.08633E-04 5.42600 1.00074E-03 5.42650 3.01453E-03 5.42700 5.61037E-03 5.42750 5.71928E-03 5.42800 2.83265E-03 5.42850 3.27122E-03 5.42900 1.82158E-02 5.42950 5.54411E-02 5.43000 1.02955E-01 5.43050 0.147112 5.43100 0.186721 5.43150 0.222270 5.43200 0.252659 5.43250 0.279555 5.43300 0.300973 5.43350 0.304245 5.43400 0.312035 5.43450 0.347732 5.43500 0.374681 5.43550 0.380081 5.43600 0.350363 5.43650 0.270256 5.43700 0.198636 5.43750 0.230506 5.43800 0.321131 5.43850 0.340056 5.43900 0.290104 5.43950 0.250537 5.44000 0.236616 5.44050 0.190614 5.44100 0.118617 5.44150 5.12054E-02 5.44200 1.28428E-02 5.44250 1.54251E-03 5.44300 9.99536E-05 5.44350 6.84990E-04 5.44400 7.20201E-03 5.44450 3.46486E-02 5.44500 9.10499E-02 5.44550 0.154074 5.44600 0.177344 5.44650 0.127918 5.44700 1.02419E-01 5.44750 0.204017 5.44800 0.317710 5.44850 0.358619 5.44900 0.322736 5.44950 0.202020 5.45000 8.94850E-02 5.45050 8.54677E-02 5.45100 0.193745 5.45150 0.294163 5.45200 0.323718 5.45250 0.337427 5.45300 0.339716 5.45350 0.305743 5.45400 0.216641 5.45450 0.106806 5.45500 1.02932E-01 5.45550 0.174774 5.45600 0.204172 5.45650 0.191135 5.45700 0.162969 5.45750 0.130052 5.45800 9.90518E-02 5.45850 7.15272E-02 5.45900 4.75445E-02 5.45950 2.79514E-02 5.46000 1.37297E-02 5.46050 5.32218E-03 5.46100 1.49865E-03 5.46150 2.72313E-04 5.46200 2.67848E-05 5.46250 1.12693E-06 5.46300 1.45533E-08 5.46350 0. 5.46400 0. 5.46450 0. 5.46500 0. 5.46550 0. 5.46600 0. 5.46650 0. 5.46700 0. 5.46750 3.03549E-08 5.46800 2.13489E-06 5.46850 4.57886E-05 5.46900 3.95639E-04 5.46950 1.84622E-03 5.47000 5.58748E-03 5.47050 1.19854E-02 5.47100 2.22257E-02 5.47150 3.51332E-02 5.47200 4.62472E-02 5.47250 5.36877E-02 5.47300 5.60791E-02 5.47350 5.30440E-02 5.47400 4.50682E-02 5.47450 3.35459E-02 5.47500 2.09950E-02 5.47550 1.04162E-02 5.47600 3.76425E-03 5.47650 8.81722E-04 5.47700 1.14532E-04 5.47750 6.80370E-06 5.47800 1.46597E-07 5.47850 3.88584E-10 5.47900 0. 5.47950 6.44391E-08 5.48000 3.98057E-06 5.48050 8.43082E-05 5.48100 8.00408E-04 5.48150 4.12506E-03 5.48200 1.34735E-02 5.48250 3.14761E-02 5.48300 5.76296E-02 5.48350 8.80779E-02 5.48400 0.116000 5.48450 0.129750 5.48500 0.111790 5.48550 5.99735E-02 5.48600 1.92124E-02 5.48650 3.39319E-02 5.48700 0.107147 5.48750 0.169993 5.48800 0.154242 5.48850 0.110705 5.48900 0.113003 5.48950 0.182524 5.49000 0.184652 5.49050 0.198950 5.49100 0.348086 5.49150 0.473069 5.49200 0.523173 5.49250 0.532916 5.49300 0.502346 5.49350 0.491116 5.49400 0.550281 5.49450 0.597412 5.49500 0.615233 5.49550 0.623942 5.49600 0.630923 5.49650 0.636958 5.49700 0.638342 5.49750 0.634499 5.49800 0.633647 5.49850 0.634415 5.49900 0.629426 5.49950 0.614256 5.50000 0.583224 5.50050 0.514827 5.50100 0.358177 5.50150 0.149966 5.50200 3.93236E-02 5.50250 6.04801E-02 5.50300 0.182845 5.50350 0.366480 5.50400 0.511792 5.50450 0.574686 5.50500 0.587514 5.50550 0.537074 5.50600 0.385606 5.50650 0.300847 5.50700 0.431592 5.50750 0.538398 5.50800 0.551068 5.50850 0.579611 5.50900 0.611067 5.50950 0.603653 5.51000 0.550708 5.51050 0.499873 5.51100 0.528999 5.51150 0.560841 5.51200 0.557640 5.51250 0.544033 5.51300 0.522033 5.51350 0.489253 5.51400 0.448917 5.51450 0.406163 5.51500 0.344978 5.51550 0.262421 5.51600 0.183616 5.51650 1.01786E-01 5.51700 3.41895E-02 5.51750 5.70567E-03 5.51800 8.79390E-04 5.51850 4.19959E-03 5.51900 1.44159E-02 5.51950 2.31335E-02 5.52000 2.00896E-02 5.52050 1.01954E-02 5.52100 2.96492E-03 5.52150 4.52524E-04 5.52200 3.15935E-05 5.52250 8.44875E-07 5.52300 2.65635E-08 5.52350 2.44013E-06 5.52400 7.59352E-05 5.52450 9.61195E-04 5.52500 5.94500E-03 5.52550 2.10886E-02 5.52600 4.75673E-02 5.52650 7.14471E-02 5.52700 9.69348E-02 5.52750 0.153943 5.52800 0.195797 5.52850 0.174543 5.52900 0.137876 5.52950 0.151738 5.53000 0.144315 5.53050 9.59912E-02 5.53100 3.67680E-02 5.53150 6.64104E-03 5.53200 2.44910E-03 5.53250 1.90113E-02 5.53300 7.91757E-02 5.53350 0.178494 5.53400 0.274202 5.53450 0.338950 5.53500 0.366048 5.53550 0.386210 5.53600 0.415615 5.53650 0.416930 5.53700 0.375013 5.53750 0.323370 5.53800 0.280787 5.53850 0.190303 5.53900 7.70454E-02 5.53950 1.64788E-02 5.54000 2.01648E-02 5.54050 8.46797E-02 5.54100 0.186551 5.54150 0.272068 5.54200 0.325513 5.54250 0.344228 5.54300 0.338002 5.54350 0.316207 5.54400 0.279811 5.54450 0.234387 5.54500 0.181547 5.54550 0.120555 5.54600 6.17196E-02 5.54650 2.08133E-02 5.54700 3.85526E-03 5.54750 3.29736E-04 5.54800 2.20920E-05 5.54850 1.54864E-04 5.54900 7.87325E-04 5.54950 2.22683E-03 5.55000 3.53652E-03 5.55050 2.30437E-03 5.55100 6.17192E-04 5.55150 2.98423E-04 5.55200 1.83642E-03 5.55250 5.67850E-03 5.55300 8.04342E-03 5.55350 6.04253E-03 5.55400 2.62397E-03 5.55450 6.55999E-04 5.55500 8.58096E-05 5.55550 4.95827E-06 5.55600 1.00570E-07 5.55650 1.42085E-10 5.55700 2.29469E-10 5.55750 1.13957E-07 5.55800 4.96854E-06 5.55850 6.98112E-05 5.55900 3.80852E-04 5.55950 1.44136E-03 5.56000 6.34001E-03 5.56050 1.76034E-02 5.56100 2.89334E-02 5.56150 4.56527E-02 5.56200 7.00865E-02 5.56250 8.54037E-02 5.56300 8.68363E-02 5.56350 7.69647E-02 5.56400 5.46624E-02 5.56450 2.37520E-02 5.56500 5.58176E-03 5.56550 3.80790E-03 5.56600 4.92004E-03 5.56650 2.47108E-03 5.56700 4.45268E-04 5.56750 2.74335E-05 5.56800 1.23263E-06 5.56850 2.22203E-05 5.56900 2.09185E-04 5.56950 6.39915E-04 5.57000 7.02770E-04 5.57050 4.22882E-04 5.57100 1.24942E-03 5.57150 3.97686E-03 5.57200 6.09825E-03 5.57250 5.46108E-03 5.57300 3.38366E-03 5.57350 1.52947E-03 5.57400 4.71708E-04 5.57450 9.52347E-05 5.57500 1.21855E-05 5.57550 6.98121E-07 5.57600 1.25897E-08 5.57650 0. 5.57700 0. 5.57750 0. 5.57800 0. 5.57850 0. 5.57900 0. 5.57950 0. 5.58000 0. 5.58050 0. 5.58100 0. 5.58150 0. 5.58200 0. 5.58250 0. 5.58300 0. 5.58350 1.76648E-10 5.58400 1.05957E-07 5.58450 4.98160E-06 5.58500 7.68187E-05 5.58550 4.42321E-04 5.58600 9.72458E-04 5.58650 1.25999E-03 5.58700 5.45207E-03 5.58750 2.09932E-02 5.58800 4.50466E-02 5.58850 6.94624E-02 5.58900 9.17806E-02 5.58950 0.109747 5.59000 0.112703 5.59050 8.17191E-02 5.59100 3.71259E-02 5.59150 1.94040E-02 5.59200 3.41528E-02 5.59250 7.01345E-02 5.59300 0.129605 5.59350 0.205564 5.59400 0.251872 5.59450 0.272518 5.59500 0.283161 5.59550 0.286849 5.59600 0.271564 5.59650 0.252346 5.59700 0.261812 5.59750 0.270169 5.59800 0.258529 5.59850 0.230035 5.59900 0.184633 5.59950 0.131931 5.60000 8.66785E-02 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/conf.py0000644000175100001660000001652314764317313015512 0ustar00runnerdocker# -*- coding: utf-8 -*- # Licensed under a 3-clause BSD style license - see LICENSE.rst # # Astropy documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this file. # # All configuration values have a default. Some values are defined in # the global Astropy configuration which is loaded here before anything else. # See astropy.sphinx.conf for which values are set there. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) # IMPORTANT: the above commented section was generated by sphinx-quickstart, but # is *NOT* appropriate for astropy or Astropy affiliated packages. It is left # commented out with this explanation to make it clear why this should not be # done. If the sys.path entry above is added, when the astropy.sphinx.conf # import occurs, it will import the *source* version of astropy instead of the # version installed (if invoked as "make html" or directly with sphinx), or the # version in the build directory (if "python setup.py build_sphinx" is used). # Thus, any C-extensions that are needed to build the documentation will *not* # be accessible, and the documentation will not build correctly. import sys import datetime import sphinx from specreduce import __version__ try: from sphinx_astropy.conf.v1 import * # noqa except ImportError: print('ERROR: the documentation requires the sphinx-astropy package to be installed') sys.exit(1) # xref: https://github.com/sphinx-doc/sphinx/issues/13232#issuecomment-2608708175 if sys.version_info[:2] >= (3, 13) and sphinx.version_info[:2] < (8, 2): import pathlib from sphinx.util.typing import _INVALID_BUILTIN_CLASSES _INVALID_BUILTIN_CLASSES[pathlib.Path] = "pathlib.Path" # -- General configuration ---------------------------------------------------- # By default, highlight as Python 3. highlight_language = 'python3' # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.2' # To perform a Sphinx version check that needs to be more specific than # major.minor, call `check_sphinx_version("x.y.z")` here. # check_sphinx_version("1.2.1") # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns.append('_templates') # This is added to the end of RST files - a good place to put substitutions to # be used globally. rst_epilog += """ """ # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does project = "specreduce" author = "Astropy Specreduce contributors" copyright = '{0}, {1}'.format( 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. # The short X.Y version. version = __version__.split('-', 1)[0] # The full version, including alpha/beta/rc tags. release = __version__ # -- Options for HTML output -------------------------------------------------- # A NOTE ON HTML THEMES # The global astropy configuration uses a custom theme, 'bootstrap-astropy', # which is installed along with astropy. A different theme can be used or # the options for this theme can be modified by overriding some of the # variables set in the global configuration. The variables set in the # global configuration are listed below, commented out. # Add any paths that contain custom themes here, relative to this directory. # To use a different custom theme, add the directory containing the theme. #html_theme_path = [] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. To override the custom theme, set this to the # name of a builtin theme or the name of a custom theme in html_theme_path. #html_theme = None html_static_path = ['_static'] # html_theme = None html_style = 'specreduce.css' html_theme_options = { '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. #html_logo = '' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = '_static/logo_icon.ico' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = '{0} v{1}'.format(project, release) # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # Prefixes that are ignored for sorting the Python module index modindex_common_prefix = ["specreduce."] # -- Options for LaTeX output ------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [('index', project + '.tex', project + u' Documentation', author, 'manual')] # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [('index', project.lower(), project + u' Documentation', [author], 1)] # -- Options for the edit_on_github extension --------------------------------- # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- # nitpicky = True intersphinx_mapping.update( { 'astropy': ('https://docs.astropy.org/en/stable/', None), 'ccdproc': ('https://ccdproc.readthedocs.io/en/stable/', None), 'specutils': ('https://specutils.readthedocs.io/en/stable/', None), 'gwcs': ('https://gwcs.readthedocs.io/en/stable/', None) } ) # # Some warnings are impossible to suppress, and you can list specific references # that should be ignored in a nitpick-exceptions file which should be inside # the docs/ directory. The format of the file should be: # # # # for example: # # py:class astropy.io.votable.tree.Element # py:class astropy.io.votable.tree.SimpleElement # py:class astropy.io.votable.tree.SimpleElementWithContent # # Uncomment the following lines to enable the exceptions: # # for line in open('nitpick-exceptions'): # if line.strip() == "" or line.startswith("#"): # continue # dtype, target = line.split(None, 1) # target = target.strip() # nitpick_ignore.append((dtype, six.u(target))) # -- Options for linkcheck output ------------------------------------------- linkcheck_retry = 5 linkcheck_ignore = [ "https://www.aanda.org/", ] linkcheck_timeout = 180 linkcheck_anchors = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/extinction.rst0000644000175100001660000001157714764317313017135 0ustar00runnerdockerAtmospheric Extinction ====================== Introduction ------------ The Earth's atmosphere is highly chromatic in its transmission of light. The wavelength-dependence is dominated by scattering in the optical (320-700 nm) and molecular features in the near-infrared and infrared. Supported Optical Extinction Models ----------------------------------- ``specreduce`` offers support for average optical extinction models for a set of observatories: .. csv-table:: :header: "Model Name", "Observatory", "Lat", "Lon", "Elevation (m)", "Ref" "ctio", "Cerro Tololo Int'l Observatory", "-30.165", "70.815", "2215", "1" "kpno", "Kitt Peak Nat'l Observatory", "31.963", "111.6", "2120", "2" "lapalma", "Roque de Los Muchachos Observatory", "28.764", "17.895", "2396", "3" "mko", "Mauna Kea Int'l Observatory", "19.828", "155.48", "4160", "4" "mtham", "Lick Observatory, Mt. Hamilton Station", "37.341", "121.643", "1290", "5" "paranal", "European Southern Obs., Paranal Station", "-24.627", "70.405", "2635", "6" "apo", "Apache Point Observatory", "32.780", "105.82", "2788", "7" 1. The CTIO extinction curve was originally distributed with IRAF and comes from the work of Stone & Baldwin (1983 MN 204, 347) plus Baldwin & Stone (1984 MN 206, 241). The first of these papers lists the points from 3200-8370A while the second extended the flux calibration from 6056 to 10870A but the derived extinction curve was not given in the paper. The IRAF table follows SB83 out to 6436, the redder points presumably come from BS84 with averages used in the overlap region. More recent CTIO extinction curves are shown as Figures in Hamuy et al (92, PASP 104, 533 ; 94 PASP 106, 566). 2. The KPNO extinction table was originally distributed with IRAF. The ultimate provenance of this data is unclear, but it has been used as-is in this form for over 30 years. 3. Extinction table for Roque de Los Muchachos Observatory, La Palma. Described in https://www.ing.iac.es/Astronomy/observing/manuals/ps/tech_notes/tn031.pdf. 4. Median atmospheric extinction data for Mauna Kea Observatory measured by the Nearby SuperNova Factory: https://www.aanda.org/articles/aa/pdf/2013/01/aa19834-12.pdf. 5. Extinction table for Lick Observatory on Mt. Hamilton constructed from https://mthamilton.ucolick.org/techdocs/standards/lick_mean_extinct.html. 6. Updated extinction table for ESO-Paranal taken from https://www.aanda.org/articles/aa/pdf/2011/03/aa15537-10.pdf. 7. Extinction table for Apache Point Observatory. Based on the extinction table used for SDSS and available at https://www.apo.nmsu.edu/arc35m/Instruments/DIS/ (https://www.apo.nmsu.edu/arc35m/Instruments/DIS/images/apoextinct.dat). In each case, the extinction is given in magnitudes per airmass and the wavelengths are in Angstroms. Here is an example that uses the `~specreduce.calibration_data.AtmosphericExtinction` class to load each model and plots the extinction in magnitudes as well as fractional transmission as a function of wavelength: .. plot:: :include-source: import matplotlib.pyplot as plt from specreduce.calibration_data import AtmosphericExtinction, SUPPORTED_EXTINCTION_MODELS fig, ax = plt.subplots(2, 1, sharex=True) for model in SUPPORTED_EXTINCTION_MODELS: ext = AtmosphericExtinction(model=model) ax[0].plot(ext.spectral_axis, ext.extinction_mag, label=model) ax[1].plot(ext.spectral_axis, ext.transmission) ax[0].legend(fancybox=True, shadow=True) ax[1].set_xlabel("Wavelength (Angstroms)") ax[0].set_ylabel("Extinction (mag)") ax[1].set_ylabel("Transmission") plt.tight_layout() fig.show() A convenience class, `~specreduce.calibration_data.AtmosphericTransmission`, is provided for loading data files containing atmospheric transmission versus wavelength. The common use case for this would be loading the output of telluric models. By default it loads a telluric model for an airmass of 1 and 1 mm of precipitable water. Some resources for generating more realistic model atmospheric transmission spectra include https://mwvgroup.github.io/pwv_kpno/1.0.0/documentation/html/index.html and http://www.eso.org/sci/software/pipelines/skytools/molecfit. .. plot:: :include-source: import matplotlib.pyplot as plt from specreduce.calibration_data import AtmosphericTransmission, SUPPORTED_EXTINCTION_MODELS fig, ax = plt.subplots() ext_default = AtmosphericTransmission() ext_custom = AtmosphericTransmission(data_file="atm_transmission_secz1.5_1.6mm.dat") ax.plot(ext_default.spectral_axis, ext_default.transmission, label=r"sec $z$ = 1; 1 mm H$_{2}$O", linewidth=1) ax.plot(ext_custom.spectral_axis, ext_custom.transmission, label=r"sec $z$ = 1.5; 1.6 mm H$_{2}$O", linewidth=1) ax.legend(loc="upper center", bbox_to_anchor=(0.5, 1.12), ncol=2, fancybox=True, shadow=True) ax.set_xlabel("Wavelength (microns)") ax.set_ylabel("Transmission") fig.show() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/extraction_quickstart.rst0000644000175100001660000001601314764317313021371 0ustar00runnerdocker.. _extraction_quickstart: =============================== Spectral Extraction Quick Start =============================== Specreduce provides flexible functionality for extracting a 1D spectrum from a 2D spectral image, including steps for determining the trace of a spectrum, background subtraction, and extraction. Tracing ======= The `specreduce.tracing` module defines the trace of a spectrum on the 2D image. These traces can either be determined semi-automatically or manually, and are provided as the inputs for the remaining steps of the extraction process. Supported trace types include: * `~specreduce.tracing.ArrayTrace` * `~specreduce.tracing.FlatTrace` * `~specreduce.tracing.FitTrace` Each of these trace classes takes the 2D spectral image as input, as well as additional information needed to define or determine the trace (see the API docs above for required parameters for each of the available trace classes):: trace = specreduce.tracing.FlatTrace(image, 15) .. note:: The fit for `~specreduce.tracing.FitTrace` may be adversely affected by noise where the spectrum is faint. Narrowing the window parameter or lowering the order of the fitting function may improve the result for noisy data. Background ========== The `specreduce.background` module generates and subtracts a background image from the input 2D spectral image. The `~specreduce.background.Background` object is defined by one or more windows, and can be generated with: * `~specreduce.background.Background` * `Background.one_sided ` * `Background.two_sided ` The center of the window can either be passed as a float/integer or as a trace:: bg = specreduce.background.Background.one_sided(image, trace, separation=5, width=2) or, equivalently:: bg = specreduce.background.Background.one_sided(image, 15, separation=5, width=2) The background image can be accessed via `~specreduce.background.Background.bkg_image` and the background-subtracted image via `~specreduce.background.Background.sub_image` (or ``image - bg``). The background and trace steps can be done iteratively, to refine an automated trace using the background-subtracted image as input. Extraction ========== The `specreduce.extract` module extracts a 1D spectrum from an input 2D spectrum (likely a background-extracted spectrum from the previous step) and a defined window, using one of the following implemented methods: * `~specreduce.extract.BoxcarExtract` * `~specreduce.extract.HorneExtract` Each of these takes the input image and trace as inputs (see the API above for other required and optional parameters):: extract = specreduce.extract.BoxcarExtract(image-bg, trace, width=3) or:: extract = specreduce.extract.HorneExtract(image-bg, trace) For the Horne algorithm, the variance array is required. If the input image is an ``astropy.NDData`` object with ``image.uncertainty`` provided, then this will be used. Otherwise, the ``variance`` parameter must be set.:: extract = specreduce.extract.HorneExtract(image-bg, trace, variance=var_array) An optional mask array for the image may be supplied to HorneExtract as well. This follows the same convention and can either be attached to ``image`` if it is an ``astropy.NDData`` object, or supplied as a keyword argument. The extraction methods automatically detect non-finite pixels in the input image and combine them with the user-supplied mask to prevent them from biasing the extraction. In the boxcar extraction, the treatment of these pixels is controlled by the ``mask_treatment`` option. When set to ``exclude`` (the default), non-finite pixels within the extraction window are excluded from the extraction, and the extracted flux is scaled according to the effective number of unmasked pixels. When using other options (``filter`` or ``omit``), the non-finite values may be propagated or treated differently as documented in the API. The previous examples in this section show how to initialize the BoxcarExtract or HorneExtract objects with their required parameters. To extract the 1D spectrum:: spectrum = extract.spectrum The ``extract`` object contains all the set options. The extracted 1D spectrum can be accessed via the ``spectrum`` property or by calling (e.g ``extract()``) the ``extract`` object (which also allows temporarily overriding any values):: spectrum2 = extract(width=6) or, for example to override the original ``trace_object``:: spectrum2 = extract(trace_object=new_trace) Spatial profile options ----------------------- The Horne algorithm provides two options for fitting the spatial profile to the cross dispersion direction of the source: a Gaussian fit (default), or an empirical ``interpolated_profile`` option. If the default Gaussian option is used, an optional background model may be supplied as well (default is a 2D Polynomial) to account for residual background in the spatial profile. This option is not supported for ``interpolated_profile``. 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 ``interp_degree_interpolated_profile``, which defaults to linear in x and y) to generate an empirical interpolated spatial profile. Since this option has two optional parameters to control the fit, the input can either be a string to indicate that ``interpolated_profile`` should be used for the spatial profile and to use the defaults for bins and interpolation degree, or to override these defaults a dictionary can be passed in. For example, to use the ``interpolated_profile`` option with default bins and interpolation degree:: interp_profile_extraction = extract(spatial_profile='interpolated_profile') Or, to override the default of 10 samples and use 20 samples:: interp_profile_extraction = extract(spatial_profile={'name': 'interpolated_profile', 'n_bins_interpolated_profile': 20) Or, to do a cubic interpolation instead of the default linear:: interp_profile_extraction = extract(spatial_profile={'name': 'interpolated_profile', 'interp_degree_interpolated_profile': 3) As usual, parameters can either be set when instantiating the HorneExtraxt object, or supplied/overridden when calling the extraction method on that object. Example Workflow ================ This will produce a 1D spectrum, with flux in units of the 2D spectrum. The wavelength units will be pixels. Wavelength and flux calibration steps are not included here. Putting all these steps together, a simple extraction process might look something like:: from specreduce.tracing import FlatTrace from specreduce.background import Background from specreduce.extract import BoxcarExtract trace = FlatTrace(image, 15) bg = Background.two_sided(image, trace, separation=5, width=2) extract = BoxcarExtract(image-bg, trace, width=3) spectrum = extract.spectrum ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/index.rst0000644000175100001660000000442014764317313016045 0ustar00runnerdocker######################## Specreduce Documentation ######################## The `specreduce `_ package aims to provide a data reduction toolkit for optical and infrared spectroscopy, on which applications such as pipeline processes for specific instruments can be built. The scope of its functionality is limited to basic spectroscopic reduction, currently encompassing the following three tasks: #. Determining the trace of a spectrum dispersed in a 2D image, either by setting a flat trace, providing a custom trace array, or fitting a spline, polynomial, or other model to the positions of the dispersed spectrum. #. Generating a background based on a region on one or both sides of this trace, and making available the background image, 1D spectrum of the background, and the background-subtracted image. #. Performing either a Horne (a.k.a. "optimal") or boxcar extraction on either the original or background-subtracted 2D spectrum, using the trace generated by the first task to generate a 1D spectrum. Further details about these capabilities are detailed in the sections linked below. Beyond these tasks, basic *image* processing steps (such as bias subtraction) are covered by `ccdproc `_ and other packages, data analysis by `specutils `_, and visualization by `matplotlib `_. A few examples will be provided that feature ``specreduce`` in conjunction with these complementary packages. .. note:: Specreduce is currently an incomplete work-in-progress and is liable to change. Please feel free to contribute code and suggestions through github. .. _spectral-extraction: ******************* Spectral Extraction ******************* .. toctree:: :maxdepth: 2 extraction_quickstart.rst *********** Calibration *********** .. toctree:: :maxdepth: 1 wavelength_calibration.rst extinction.rst specphot_standards.rst mask_treatment/mask_treatment.rst ***** Index ***** .. toctree:: :maxdepth: 1 api.rst * :ref:`genindex` * :ref:`modindex` * :ref:`search` **************** Development Docs **************** .. toctree:: :maxdepth: 1 process/index terms ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/make.bat0000644000175100001660000001064114764317313015613 0ustar00runnerdocker@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Astropy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Astropy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1741790933.7204971 specreduce-1.5.1/docs/mask_treatment/0000755000175100001660000000000014764317326017226 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_apply.svg0000644000175100001660000007762014764317313023442 0ustar00runnerdocker 2025-02-27T11:02:27.464870 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_apply_mask_only.svg0000644000175100001660000010104514764317313025503 0ustar00runnerdocker 2025-02-27T11:02:30.168638 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_apply_nan_only.svg0000644000175100001660000010075514764317313025333 0ustar00runnerdocker 2025-02-27T11:02:29.756590 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_ignore.svg0000644000175100001660000007667714764317313023613 0ustar00runnerdocker 2025-02-27T11:02:27.971940 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_nan_fill.svg0000644000175100001660000010005314764317313024062 0ustar00runnerdocker 2025-02-27T11:02:29.355840 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_propagate.svg0000644000175100001660000007773214764317313024303 0ustar00runnerdocker 2025-02-27T11:02:28.520732 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/fig_masking_zero_fill.svg0000644000175100001660000010043214764317313024266 0ustar00runnerdocker 2025-02-27T11:02:28.955119 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/mask_treatment/mask_treatment.rst0000644000175100001660000000373614764317313023003 0ustar00runnerdocker.. _mask_treatment: Treatment of masked and non-finite pixel values =============================================== Specreduce provides several options for handling images that contain masked or non-finite pixels. These options allow users to decide whether to preserve, modify, or remove invalid data. The mask treatment option is selected using the ``mask_treatment`` argument in class initializers, methods, and functions where applicable. Note that different classes and procedures within Specreduce may support only a subset of these options. Available Options ----------------- - ``apply`` The input image is left unmodified. Any pixel that is non-finite (e.g., NaN or infinity) is marked as masked, and if an existing mask is present, it is combined with the mask derived from non-finite values. .. image:: fig_masking_apply.svg - ``ignore`` The input image remains unchanged, and any existing mask is discarded. Non-finite values are neither masked nor replaced. .. image:: fig_masking_ignore.svg - ``propagate`` The image data remains unchanged. However, if any pixel is masked or non-finite, the entire cross-dispersion axis containing that pixel is marked as masked. .. image:: fig_masking_propagate.svg - ``zero_fill`` All pixels that are masked or non-finite are replaced with ``0.0`` and the the mask is cleared from the image. .. image:: fig_masking_zero_fill.svg - ``nan_fill`` Similar to zero_fill, but instead of replacing invalid pixels with ``0.0``, they are replaced with ``nan``. The mask is then removed from the image. .. image:: fig_masking_nan_fill.svg - ``apply_mask_only`` The image and its mask are returned as provided. Only the existing mask is applied without incorporating non-finite value checks. .. image:: fig_masking_apply_mask_only.svg - ``apply_nan_only`` The input image is returned unaltered, but any existing mask is ignored. A new mask is generated solely based on non-finite pixels. .. image:: fig_masking_apply_nan_only.svg././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.722497 specreduce-1.5.1/docs/process/0000755000175100001660000000000014764317326015666 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_MOS_arc.odg0000644000175100001660000003132714764317313020356 0ustar00runnerdockerPKxL.++mimetypeapplication/vnd.oasis.opendocument.graphicsPKxL_Thumbnails/thumbnail.pngPNG  IHDRKPLTE555>>>AAAKKK\\\dddh#:IDATx܋r(v[Gs ɉݸIgGaD5ge|jjjjjjz4uv?_]/p)ݩUۖ,dR=;#j`u@Bd,̑"aaڵCƉyqڎ#2:$M5IBd5REs̋OQ{5J-Τ̹Cc,C]H#S d"uH⬔XdZ#H [9s1d1XjeDKHoԅ(@>x:Ӗ-]73J~ռh!jEMUͤI5aԚ0~峂m¬"s^Cl?j\QՋh Ѥ]\-E,QD±9u}$jj87ڹ$o#L&o=I9Ϩkk,IJ){/25eRɦs:L1M'&9u۬/_gBy'[yؐ߬A4 >yc˓[Ȯg>؋ףĺi0n%KNu_;jTjzUi Ӟfmuqm[d#mN,ij?9} 9|;b^cEşSIX$ȾCU (퀇PR￴Ԃ(ul:'hօ?:]vbSׂeD2'n.o{UcZU[*IDI(UK{4S$-YN5M蹮v>ϧwVeF{vr Cp\6үuelan.N^H9\ROڤZ 5+XREVzp ۖnJ]?bVpQHlyCn5n]\bO %k;W'sŐ(;s)(]my:N wp5Zy5!{= N9[[8U\s.o9Sk]ӾlۯmL{.cHJ9M7oPsOe}n% r߀mF^ҋqD:%ZI6؆%=IwϛnEk-=t1>%of6uEýy\p`ËkPKҰ$PKxL settings.xmlZr8}HQ0SSC$]f10b7a)Dܒ 4m⩮ŖιtϽK@ѫ\!3*:v"K}T̏De^ )UqSQ^ܾEX)(KBDWo[ŕUn&eX.8>Oi!GBA>oi}Ft_EcF% 㴰j-Lm> Be󄼛+@q/n1Unھ\7.R]aחY|DemUNQ1_'>z,ꦜ&2w}zV:,lv=mbtrr0C9j|Og[/^|< ΅QX\3SH\!wn&ÖX1]?"!6gt}?%,ub*vHHۘ0#YzLl#h uaU:[zꌔW'lJP]fI&Y,`Ua&새 È_7*j7CDԉTH\`S5I?9E!I3@hB>9$"C|pUӢW`(UL|u37LnDQE4\d4LJ2i}H`AҮ; OXVȻ>Z 軮9gQC%k;)+0l4u[)F@X ny[.- 캽6lo ܿ$rL C2w5 + jwQH璠 AXI:V%d8PjWYv(O~N=2?i YjN=HE;DtVmY9Co^k+m' hZNJ4F%u\Vj:?q+gn@7,m(AZBy/Z")T{"( [ Hr|X>Dlʜ G`!)2)'U;ap0A) دPu>M2@Fz\QuG.(n-%ZrBH\lM>SVzJEL9g(:[OG`>퇔X[*p*$|&ьDzQn+#gyܒidU?)_ yBž\}ܱ~Woaou|חn3-uj,c|]PB6 LZq50U/Qϫ|bؘݘLT;e8Yq$K֞cݸv/>4W?û˜^0ܔOy*a"1Aٔ|+V)*X $zcgY®x] PKoJy%&PKxL content.xml]ْ}WHޞncWndz8~CDE@JO1/7")B#yi /#eMY&iDa$YӋ WK窟9@8(k/'+_$$ ,ivIJE[xWYtB:lc.+%wo.ۭF6,n>Kdg:KeJpG(\Ny^LFZSy޴mtŢB*4eihZ.iNdm2ihHN:2|.'\G6siv0 ¤yVT1yA䋁u7PY-ee|ҟYJ'IҨ^uݜ-^ sZ^qD~x $To 4Ӳ΂tIḰYN-22GdHKP \c:aqkh|E]E,zEAc 曉!fǚBXҧ\q'@mb&ΒUPHSB^EمC{+q!H4=WI$̀`Izj-<iv{qX~y>:|j՛Z\yMDziS0FCb|z_NCv-K@E4.'ߒ4ɾۑ+ '5W4 ձdIbA" s`T^5r&|ltBMes[eAP'Jˏ#'CQÙ,\ILfx8wE+;T~W*JȟL鐧*Ox8բ?/ RԼjdt:M猤ЯS|xPF#ZK¾PmiD(rO_k>Z 1\uy5xdGR!rϷ_bu<̂rh!ah<{%_'Q.'ş‘vm b~` GgJ{"R?+75pY3Bwf`| \o#t뜹bͱсp) Ɂ`H|iŴ:SFoށ>:S/d@" $MI>Za":XaYȪJRq%ޝܩw'CO;6{1YQC%Bi.bd!*VyDjy;O_e,,aeѲ/[E4K:Hz'CZ!!da|'Cx8idO{'Dui~DY/Qtz:xK桺gpX %2Ϩک+ۺPf9e52#@ j )vT\_h6 ͏‹0A-&>E_RU^^FleZ6B>ֽˆVikLH6q3"0_iͪQ-K+xhFuN%C 4]֫5!c km6[zGUG+I+yiUPuI{.gb1Q!pfQ)UgU oBw ЯυtV/c ϯ֧D!?nPf!L@Yvüt/uo3o7UG`I) - /_ۺ; )5bW%i vfl`xl D֮"MݶH1[cl:AWBAdmKÖY--e43`k#O~z֦(ի_^^+ZZc ǬlcF<=0Аybmp5w ׵-=uQ]xӵ")9j/UZ.40܄'atmsĮŨ[E:k+V,7G8drns84`lCH!Ҽ–`X0Iĝq@}'4525Ǡ@ЗwAGv>qH {zSc0%RS.%0`L@HEeIeZIS%ZMF]xNNXA޳>aYf~܄<'Bɢ0WrF-Bdl[c+0elݐHg8=qq+Pw={ "T &6n4#HFFSN?3 ޹t ҉C8A25 C-dR;S^ŧ<7` NΓ.YEXputӾ!#k6wN=&a%92TMM%WND4Q Yӈ|ΦɰI[M^sY^Cٰ x4s<4%I&fwH#u=k<9>I'QxHN~9Vcnķ 䂜k!)[1R a6'I~i) }kJ~QBrHfs{LqmBR,yމw > >Y|4{,4^74C6 <QXDS኱fmOV/b7܏q~\ϫ` +f_膉) Yz5Yt CG"Pmm} XJ4v4r`"Xބ?#Ȝ\ݮzwoşs;|Heăd㾳]nݶwn&2[BB3־ߏo?۸}¸e`Bdp%b s00x}ahCj J*#dXX[ C9-!6 ?giհuz'8xGa1:qvN!tl- q$1̠a5lG°(  a*/*wM_ӁPK^ OtPKxLϣ[[5Pictures/100000000000002000000020B2FF2A327EABC87D.pngPNG  IHDR DPLTE3f333f3333f3ffffff3f̙3f3f333f333333333f33333333f33f3ff3f3f3f3333f33̙33333f3333333f3333f3ffffff3f33ff3f3f3f3fff3ffffffffffff3ffff̙fff3fffffff3ffffff3f333f3333f3ffffff3f̙̙3̙f̙̙̙̙3f3f̙333f3̙333f3fff̙fff3f̙̙3f̙3f̙3f333f3333f3ffffff3f̙3f3fOIDATxc'F*U0RU<4IENDB`PKxL styles.xml[_s۸覝ɱƹI.M^k;"! Ip@в%wA9N!v+Y0Yr_q0X?~yC.F߿s\W)]J&/G炖4c\sQNsы يݪ5.﬙ى0u/ɷeJ"+;Rܦp^rQ+6َ![S4{Ny& jRRTybn &9h[+{,Y?gVR,Qs!HVbnE>fD>E Y) /iH|Df3(C{i!ղ]@ Qy OV,h(!4PɵmE%dC/-rsdgyWXʻzaS"]XY)\y _,[Es?,~d{7WEzܔ/cDDAOFu\ %e;,:~̨l=p{.?5 RjCXy@IpwBo@k5`ruPSAM¶v%}N`Es,߂qY=,{DK.-+_E|-\\~汪5_EoD/'_^Wv uWhB_ X$#C 8E~%hhQIWRͼxQM3i]c) qpK%GZZ5<.⯣5>FHv'/jd*C&,_a:E$EmΨs %Һ~\Gpb孟IW&gFFdN덵QYy4[XK1RDa+QA%;$Z)Q Ś8TT X[S2t d!`E>E2`D-Z 76I*(n,iZ)(1հ^{V%#Ql޼QɊ̚^O@c) )1\h~jo]ͻ׷|fz#Kt{kI|_%]v g CE؞V-1>b@GJLF{X`d! Oϣwd+%HLbǓ&V1 Ěw_tP;&c@h Y7WS}_4N'nsJkM+JiQ0:CB(M.Zʖ ){WkbwMI L6m]'x?0< M!f벥솥,4e3DYF?qg}!'1e vl鋕H#T3Y:>ÃҔ$ x37KMNP8$B"t$:;Bx$zv2&OD>8=;!BO3N :C@;ؼ*Z¦Pz QSǚ-53MF5r\7) k/ znsI RT5[ZnQdZ|ǧLp2JxQwM̘Lf4bm+=+8iD.&vM}rvڛ@RggH_V߇%vv_ҷ4 .QZrp90GTf~/ G? K NCmq,+ԘoIrlﴺ~%X6\A7MNmRټ@F G5:[W?:?2xu4MxH8tKh>%:uC~n_|Do.7a t+hCkxpStȷ!B$w){j}xQh>T%Xiw5ĥ|˻4wԯW#.;dUa!OG2>N,&_Cq{m =8iӮ,q<}]g0.8BW}rT2Y3i9[y>FJw0";/wӶak[u.%:oc`w PK* D:PKxLConfigurations2/toolpanel/PKxLConfigurations2/menubar/PKxLConfigurations2/floater/PKxLConfigurations2/statusbar/PKxLConfigurations2/popupmenu/PKxLConfigurations2/images/Bitmaps/PKxLConfigurations2/progressbar/PKxLConfigurations2/toolbar/PKxL'Configurations2/accelerator/current.xmlPKPKxLMETA-INF/manifest.xmlTn0 UM;*Zlw`n8U H@ih%vld'YN$GA;,،OY\)~.ت,D]Cj$][1w2萣rRk+{~~ay+'ɝҮޟu4&m% &܏-TZtn`mV4qĊ_Nx L Raw arc exposures Standardize meta-data Linearity correction Initialize variance Add mask Attach slit traces Cut out slits Resample to linear co-ords Map spectral distortions Map pixels -> wavelength Calibrated arc Rectified arc Subtract dark Part-processed flats Processed dark ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_MOS_flat.odg0000644000175100001660000003631514764317313020541 0ustar00runnerdockerPK!L.++mimetypeapplication/vnd.oasis.opendocument.graphicsPK!Lۗ Thumbnails/thumbnail.pngPNG  IHDRZPLTE''',,,555===EEESSSZZZcccnnnsss}}}5 IDATx훉v8 ,wmI9dJE-8 P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P {Qta֑ҭ8By7V9JYVRy߸Ŵ୳u.ֵ R1Ot85r2(7ppl G t_Щ ;J8꾏_Cϗ#ժjgC9: ap.V\<'ѕXvl3XFec2*K;U7&P&Q=b0yUP}cN,@*Yk;Ͽ|8uPک?J6DOPjzqp6v/7? /5Qgb}NSzo!4 uX8Lj-Sn0xc#^)˿ɿʥnPT4FG&WPe*D]j{c[cZmmU-V#Zf[MK%ZѦ'˳J8ydku xvDG`7{0+Ki-$%gɅBםB֌c:{zձi {.: Uk;S[3\@܊F!nyhW>R#5odй8nQS~S*T{1ԶW#M t+YeչJ!OJV'vk hWˢS*M\puPlm{ q4!a*lE9aEH!ZCn m7`ds8獃)WI{Ԇ{w엸Bf m5МګMvRle 5\j^97 [\r5`Bl۲n9?tѱY; u& mQä; s}׍GXSӪ nr)Ml,ݟ<`H![ajN& ޽IOɉq]lw~ԄtE>z7ߣS(mn#ne[k} 'LQFqxN+F+/!%"(}"(m,+OU7[esI5PÅ vh8^rn*E8hs5)/_bD})(y@ekV?X*!kÍu566IxA8.z+R84;nуsbR7֧cJdBj׵O9ww:(䠧h]sBQ)t 9vye[GG`y -Wv@wËhx/L iR:S0^SO%4,'ke}?ʼb5ȸ^PQ_<#D_deb/(u_g]S/a7`=]N}!q^C,Op>{NNؿf5V3hΐ-NPrTx~?D$/5Nݩ:i|辽n.:>SgP+ya?4{{)6]M,!|iszQ+}ls#%mտkȍ)NilBP P P P P P P P P P P P P P P P P P P/g 9(IENDB`PK!Lmeta.xmlM W:fzڪJE)- :58N6Z3/3CtdʍZ5 M ulǸO]_^i:t\㮍&x5`0 Uqźj5C$Հq1O9t]PF/\?(F!W0MRὦ<nhBS˱֬si/ln@!TжފA^U7gs}4[C@Ƌ>(}¶~?B&bh\XX (\?}3/F .g׹P}>|Qڂkfr+.cfBX)=0J=[|H@<%G`Ӑ],%vHNwȍ0y1w(p¯q `*ߕ_ Lrˣ F!(g֖L;MH0b?8ꬾx=T([Gs ' ][`5'Ã{폝I opFڮ[SKEs>iz)lNC:q 0Tk4Zqd+Ⱥvѯ>$W?ߍ!v+l2e]Ng݀䙞 ԗ}Gq9e-o]ḷs1#ce_ϳ;ov?j7k WiI@"eIfF4fCw|ajAݎa4MioGy;~/,i]y' ?X  m~_w*ovJCi%QM`55 j}5A1sviZ@KM%d/'Jh@@ |= Ҏx\T鴳o޽,z$.p(mh( Nۚ: CqVq4n)"[*Vd:U;d #:fD-cNSp%ڶu4-ơ {X$U~7zG+? =HF*`aWRv2 ÄW7Bk%ˇaIOg k$SǼN&Z$W_St\gS6 W_.֊].Ώ@KHaj>ގ~W pI]sz}"$^D 9Y.gxㄟ9:mU_㴻N$O׵vZnkiƖG g2 ?6 $EZ,hN&;OKӔ:9y? Jo6]a+ }ΓVy*^ __wT~2UMhidK5(,uG`$?__I]4 fM o#[`yo 3?Lv,.5 ϛ,m046͋X', ugmm6\bZUߎ~[㦁t¢|5H>N$`fotqhr6a04ѫ tuBPb}0UID0iy/}zj 2&Hdܹw>PSS.lx(3Og:D5M/dAUs9`溜X_,gA"60L"@PIiZyYtϹBn@%09^jA\z{˲Un> 9jVIPˋϴx:r} ᗱΰ"Ԅ /(YI-We,\Ё~>rĥxȠAz떖ײKDigb(LJ6RiRJ |ԅw3` ǟg5,އq+*2 JVo΋u? 졬.P?5qaʶ:dPZxQ m+pUtӯ.mwT%?@">ֽ*6Om^n#Epۓwm$6H.V%tNi}ޒc>h,\=,b”=ɺFit'd:ƌR{=dc)4{^Q#C?Edp义47I4`LvxOybEOLϹfڮ 7|d2-OB2P"*vWܬ)'`u*vuK R yQ<;YD5ȇP*j DN y kh) /fQn|  tzT;( WW&6$xͅP{Z&Hs)Ty7^20b%:4l肅#tj84 -&(B{uT && فCȐPvl8vt ǖaafy:M5?\R]Wof̰wqLgƆ]lH('wwfO z 7 ) 1ivUKSb:3y r]uvċ<`"#b=wwd 䏾K@3t5&4d{^EksmkB7T8A .'d=b;TnObg _'8ߊ5ҴЊ3)yJ>42{k?|ܿ|~`ۑ~2]RDT06\SΠSny %6 z}ӕ\i &&Ť 9x62:XO{Ӟ"ctRùF>^xT6qڠ61̦'W5ߔ-ŌL9NJW̸nnM7?GI=O˹q-Vl]sH{{Ø\)0GTֳ@GvfԒ*dqn#U0"CačV{}L*lA=,Y;TzBhԢ߭xHTJg+♝%rUx_lk hP?(YE c:,;PK{GPK!Lϣ[[5Pictures/100000000000002000000020B2FF2A327EABC87D.pngPNG  IHDR DPLTE3f333f3333f3ffffff3f̙3f3f333f333333333f33333333f33f3ff3f3f3f3333f33̙33333f3333333f3333f3ffffff3f33ff3f3f3f3fff3ffffffffffff3ffff̙fff3fffffff3ffffff3f333f3333f3ffffff3f̙̙3̙f̙̙̙̙3f3f̙333f3̙333f3fff̙fff3f̙̙3f̙3f̙3f333f3333f3ffffff3f̙3f3fOIDATxc'F*U0RU<4IENDB`PK!L styles.xml[_s۸覝ɱƹI.Cr6w/$$$AΗ}~JB9r7 `~MzL\䗣p<"rۯgx*Ky"*c"MY伜娒\Вfx. Is{2#z5;[5t25;;t3t2:LRXdU|G.Gko6f2r凳F+*j$Ypǡoy3P)j!Y ,.尅9-^ Uvۦ2I$qfT{ D7[нU,y1XMB4Z(yv77+& {LӸA\d]_ahOk =FBs>z3UjxKV@Ã.5!c?d ղdt88/lݨu6َ!)ɍ'qeN XlA! [P"#hhQIWRf^ (&kmc) qpK%ZZ%<.⯢>FHv/jd*C&,_a:E$EmΨs >ot ;UjJrY]xUZ2l|b孟IR[D;kgh!`,|KQ‡DTG0M#R,(j& +M5rm8.bB2(  VL$%QVy°^M%MK I: *zZ ;굧mU2ņkXK`z4-K!V Hz D]I8/%PSѼ+R\odion,Rr﫤˔ ؼܾvдj=&G V xq #R M9:D{=z|K: -z̰b[N:&c@h Y79<8O' 'G٤'(h^NjB)ltRTH#HZ;skO@g@W2EnڐD E&o 0[-e,5dQ)S!⸾g!]wٚlpmhHkǶNCjt'U}3[ 'xPtsyB!G]J ɣt<8}q24NNap2ξ@:+6/`oV)*EB{"u b|VQM ڋ>(*6C҂jMꮖ[3eglَuČYdF-ֶӸCObb'g׫) ѹ,u{ejkλ^`XƐ%jWk@.8j;B臣aiHAâ-eŚ-RvZ]hq{ WˁNmR|G G5:[WN@p84A>ᬣ5/yuF4{qi2gaCe߹q- [o1[Aoz2Lw'ݣ3F )"}HIuZJpZiחϚ7q)_݌jU򈤽sJf \tz!JCf)0Ցwctn#ߍýdC#:894-ؾjrdzuC[Þ!tqz'G]&kFN9A_9 URa &CwOa嫅n <\D3FJ~ u/chXbs^HZBDER\ՕЃT~oLbb #PAI:_s [&eޯ.^c˵|(B_H]|--mREO.`YقV>*Yj=Ri~ J6GFS=V! Part-processedflat spectra Processed flat Attach wavelength calibration Derive “slit function” Project to raw co-ords Normalize continuum Cut out slits Resample to linear co-ords Sum over spatial axis Fit continuum Project to raw co-ords Divide by continuum Spatial flatness correction Divide by slit function Cut out slits Resample tolinear co-ords Calibrated arc Processed twilight flat Inputs Resample tolinear co-ords Inputs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_MOS_science.odg0000644000175100001660000003730314764317313021222 0ustar00runnerdockerPKlL.++mimetypeapplication/vnd.oasis.opendocument.graphicsPKlLXz z Thumbnails/thumbnail.pngPNG  IHDRHPLTESSS[[[gggiiippp}}} IDATx흋ڸ;gVۺrU\({{M !͇p~%?ˏNݩ;uԝSwNݩ;uԝSwNݩ;uԝSw|Oqa_q$)ge'ˋURWr ap#|/Rz6ZY&m; njR9dTГwq a~/KTjqRpzFX چnyJSǺ}+ʼnԿEC+Z.Uۇ!F LکcT!sx7F·ENGz <GjൎD'i: K Cf}2E/7?6!^&mADZ98Cq 5f&g qK^GZK5ݹa(!DJD}鲃"դͯ!jJ VO|Y75_rNs}i܊t>:5vrR41!5NɤC}xtTz[ƹ~;`Շ[ Ռ^?Nz H$ZmW "Uj7W!!א%k)no¾Vl-n1Rryc" Hb%Ɣ up~;q$Uve/z+Xu赧?vڼl>oaٲKzEW\VSW۽NlQdW(ڞx,y~\Clq1SL>cr39ZĎڠT=T Fs1zNhmPJL~'y~=^.0'42B-QrVqԙ3rT 6g2<#f-J׊nYIpFS(Z>YO8`= L#ɺ!d݃P_?-}=^p_N~e7?'`"9z_^JQ%2.& !>֩ 0Wm}%Mεh@~i~ puZz?u3D#ԋ4Sv០׉οۢ.o-ANWE $mҋèQcUI%$۠ j^:,s )ddB 2r fjR KeԈ@!FQj#"oD <027zw^Hϓ!v=1VHwçg^_?Y5-w%%t,lL2>43sK瞧b[6s~ƗCyr'S[>]O[j3 al )אD$Ios(>A▯-sgR,f|> p|}5VA=KimSgWOC.5'zFmVd X''.}b5ru΋j8!ߍ}j,QԂ,<*7ߏTwW@t>w^x|܏zlu au2Iz6e'/Iꅍ:΢̚^nIǗnu|dȱr[VhVQ2 9ցlel%(BΩv[_ u~T@eLiO+~^j/-RQX->!;3ZEZo)R\ 8mH2A*~T!d'q7rokKM5N$ziA79݌%_ԶʵͰCF0#Vj(s؈=P l'ʚO^)s5d?U/펶|m{:yꛊ&sNjҏS+KfisckG ZaR ;R3Vf4^a^xErW)nmg;uR'zlg*ǶD[n1EW9NQ S;hFRN "" >0cD@X5owJАLWѥSwNݩ;uԝSwNݩ;uԝSwNݩ?Sr6M/IENDB`PKlLmeta.xml͎0} lETT."-6C0{qpjZFRK}1COO-(`*F1][i[q;Pi6%oNTKӹa< C4l"myui&Jp ӈo5ٵ΀_k75k:lLcBx>/meskFφ\ݘ\V5g_>!kC=o᳄=XվjbT-[Q1 T1YHlwMHm,O}xi W7j3V ؊/3|ZQ |\$"QwR,ݧ`;wlD$oe֯uӽ>x8^%9U/z/PKp/PKlL settings.xmlZ[s6~aNŐKcfb7a)D\IC}%,,+Ng[#H;|%"BUh ˂ŋW?}a8DU$BTR5Tw*ۗ**UVY[mjFB0},̥RӓόJe۶KuӘ# >m!xv(ʲWu2+uZZ^&rŐEyJ+@qt(n5UnھZ7.R)\aW_V(b"#VEY}(}®~ p<7"V71EKi;AB/Oc |yw_9*#1M.^88bᆙBr +ehq b5Sp@ໜ9Q{L3|"yr$~`R|@n(.Wsg3L$PHpH 9]lٿ<$8HgdM[G]ylYhXztBžM| iMK3[ꌔbMO~ߔz̑*LX3¶M<ه A_7*o9j"DCP(p[de0hK$ <"d934d\! #>dE0ԲzZ )oNvV\YS Cp7ls\g e8tW~,~֊53$9r4erDq/>qE&y5MjŖGNsPƢ[Cbokn $/--/$< ?Rcr,|xHҢ}vT?~%Q"(U,ԌU)uyC_G6sBCN"H3SMtG%x.S !q|Ir#X-h= df1Bw{~?5ĩ#Ԛ$-L')%) -ǻITͧΙXa:;D5E.Cpg &P̐UibJ"%q#FE@$th'DMMW?;OBH_|DQ^gkI,7Sx[N\XlDXZT"D(zr=UDxqnǭFH&ZGIz[ɲID'zyFqvkPqMJn ?;ެm|t8]GQda˵GK`|fMڶWD`# NP[LiI둭 T}NA9X ^ie[=\V]L*$ܿ]Ϛjvt^LZA [38 {br7!SN45Y97[k*/850f ^dp\Ӗg)z Ǻ}r˰u-N^"| vg ]YC֚AP״oVZxF%N#jB@P>% ݊!C%<Ѵ_o-S/PK+x'&PKlL content.xml]r㸕ORvSIHθ:{I;;;l&%J4EjIJ3'$[vk-B987߽\6(M'0'Zy,'7yKh^f&>Kj;ɯ&K $XU1Jab[_ϪJ)V^6f{cڙwO.YڙPTcP_"1/דX_M1l9EOvzel!}X>E6mWaelVwa MPY]gaMu)a݈vL]ۥ})Y٘'2W'2g^2T|UR}mA5ˢkViC*f/MӚVL뇽3loYZӕ4hB[&@xZU?}݇`8nGI^|ʳm%D$2)ж=(1HYN"O-F*0Z4]f) LA0ѷQ :'LCR6bׂ9mZq[e.M20|\YDv݁8[1sn¸\+ o*BMWLo~VjL;v,I!?NiN|X'1kJӶ`f>gqj}jꚎzrbW« H XDOד_4m]U8Ѹ[2LuY :*fl,*o>4Bils@G.!Zmqڍt`\6|:TLBn$XBtv>& ?G_D0nAG[K)iNe.6EJ^ާAܛר}X %L2u]f>5 ^yW2oE_" s +u&HT>͢TA-Aɋhm횭"XQe>aU`9.ҫUZ <[ќ*@؃b: @'Dkd_ fƄ+ x?KZ4e!> RV|F(pmҧ(l8GQ)꫉g ڧ&4PY%)N/x\H<f 9 P0;o,2 ׇ =R@߂./ZlFu"]\51МH МH>Whg6\p!͜5jLv0:=icھDVbEsDj2H?QqOßEem2~JW%߆]PXSf ɑV8~>+L!d44. ]ޢ^u1u([N˷P%,AGႎLUգKwի8JPRUwO`]%)LeޱzfYʵ}h·0nVVߠub=ȣ U48|ձ4"E4s,-}頲Ujnʕ_usu= 4ѿ[/B`1ޗ'Bh VvⳂ\."TF ='# HuM!BP8t۽ݫsw>/3ym&y}׸g=s{/9zꛝg>ǜ]XH6=99q3!w&l8܆q=aw8 0IuYU`?-Mvx[vE>M3zٲ"ۄ9)_}ˌxUUZt ]HzibEUC]qp}g@:[BUƛC:"BޛD>Bذ$B"oxQ9 !iM9M=G~]P.? +=n Vՙ scּ9clbjݔ fơ:ߨ<ԡMGR`~N\דh4 5>˫zlwʉeBjOe/Dס5j  _ |,Ԃd_q,[P==Vk/tS9,z>RFYL@eid:luW.M()r G 3G^к[M ;4gaR%.?i @-KALX0 ITDA zV^tD? 8[(~A@A>Q,~1H,MNٸPP}< $ 0BI1;Ij$\.dsټ mV+fYŌ1~-pLֶ,k  T-D*X*9gTbCPm*!8TeT1 ,pŊ-skG{O Dhe[z6x TzX>d asD ` a6S M[?wڬ@JHǪVd!2V;u9X >2 }~yD#!=J#- sw@sXҖ̞sbDccms)d#ƞ3a2øuPշ"шQyRx-$f GH1̾ [0K|솗6Ƞ襰`E26,iC{ZO9/3=Mn0", BGvP!_7=\B%Hy6_ܴ=>B⺣`\$jõp\i`4drze .f1&5QMbB9A&0/&Sċɠe.A1<$ [`yX1r ktQeމr4dz-DT9gW!}'}lܶB0!} @muJ#/? vd)-Zpc^[#l}ܣӹ»ؤA RL`FU9H -dqT\/)烝h8ک).6-!T~[{/#> ',xbWކذ/K/ru91<ia^GpN[zYG FqN̨ubkM [k9caC0mm"ޒCmqU Gp G.NPՅq1q7iyiqIm-[ڟhQ\b'is2& @,.$ޤ\wCQݧQ%~x1v;!"K!l`mɲ`,FqZrr˓'84|" 7*E~.b~L˽Zaq 3]Ǵڒz!0Thם tDm89`:ݿ=V*BE &}m`>g7 ɈߑX!#6~mZL|i)(l/B&{&A;E}ŔPǛ,i]f Z.mʆv\:򼧖‡ԃ:,ڢruCs[]XuEHJ]Aq0D+TLǒc;6&Kz,.%& w}iEi689No7^bxvoaE&ܗEl%AK&6mbhwA$0~^F,]D+Bs- ohiŞxy V"ai؇m Cs`ƃ6@ XyA0m hgHʱN|2VIBݵ@;\̾\6 nG;iiE̙cs\e0d[IC!ǟyw%4PqBX aj%x}$7%1LX K,֖%٩%  BeǹxXZKҬsAhkK.{Ɇ-bËKdy:kp=XG{æGBS^>G_,[Is?,~d7SwEjܔW/0CJ1 'j:]_ %E;,:~̨=p{n*Ԛ?9+,~LjlBxc_8  j4+8S(a6(wQ-ђ' Kˊb*9b@t52_KyJ 뿯7o—//_0x=̳]BZ4W=V6ɠ-N45IĤK)?f^[y8%$u8|`עFT[1<^A0`L_/z<$|B SOG"JOXhXkxf1yN)5>"kv'/ld*0x$,[a)04$MΰsR %-+aCM Zk ,oLr518+4:EƓ3:6vo)l/|K7D9TGEhDSTLVkjCe^fxaqko1ndj#E=IEMh_ +UPYҤ`5RpDE^`VfG- F6y%kuC5Nr%"LDpVu G4k/lp}gv`6ϷTmi)Uubn{l^sn_S[iZi5#txp8 n9bX<:|K6^;q8cE1,y70AwO9e21EQ}={e!ݷͧs>HK2/8ANclpZ'P#Ja_BAڡ]Xnx:lS ixJNG Okٺl aa&2I uяDMw9mYdk~Wz0wB$>&gRU{jDl)ζxP$tsj9w3dY"49!Bg g?K|.N3ӓ!4yqzvBgF' r`h BA[$4GM!kjX 7Rqݤ03艢Ta]9$-ZKQ֤jiE>B-}c(Ꚙ1LiVt{p\Lrz71U3":ӝ~OL|ys",}_Gc  {DJ5=wQstQt4 `|Md OvZ],R ^{k˶^q#{͆a-apvlx@p8=4A!ᬢ / ԍ{hyqi2gaCeĿ߸ӭy2eL} #߆ =Z}, piu5%|˻4wԯW#.;dzU򤁡!O&G2q]FOg\FluA'7à: -{\ Raw science and sky exposures Standardizemeta-data? Dark subtraction Divide by flat Linearity correction Initialize variance ADU -> electrons Add BPM Reduced data Object - sky Sky subtraction Select sky exposures Associate sky exposures Sky sub. along slit Subtract sky Interpolate sky Register WCS shifts Attach slit traces Attach wavelength calibration Cut out slits Trace object spectra Extract 1D spectra DAR flux correction Correct telluric absorption Resample to linear co-ords Scale exposures tocommon level Stack exposures Apply flux calibration ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_MOS_trace.odg0000644000175100001660000003151314764317313020704 0ustar00runnerdockerPK{L.++mimetypeapplication/vnd.oasis.opendocument.graphicsPK{LaThumbnails/thumbnail.pngPNG  IHDRKPLTE"""***555@@@ppp~~~Tw$IDATx : =cVM'- =p γݒ*{l_PC 5PC 5PC 5PC 5PC 5PC 5PC R.{o#^=uR=8ǍL|zw.zwԾacZYF|=HOQ$.I<4>cE7[%ё'k݉:o\ͥOXڡmޅ {QŔ5MQ݌"6F(1@ij޸ڷAc )bb_Ve~Ԗ6=N>ז='ډu45. H}ՆGH  \މzjv^|[BGg|nl[S)3O+ukߪM2,%PWTo˥Z}Mj;ѦZm.Mb{T4*j1Zx}l_sKʵڭX87R^׭}P?׭kV8eu/-O4̺:Nb!zGv|ϝuA>xˬsZxJY_[Ҏ^ןe$X.P#BmvlZNФ!).]-Bs.9:bZk '/>NW浭Crh\N9ܭȗ|i|n;1uw-4 I}}NrMutS-n*6݌[Y_ !k#{ 65b`^ͪsKyeGl#׃k-79M9.~J-Stĺ3PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PCo1IENDB`PK{Lmeta.xmlMo0++# \i{jWڬ[Dl7u 6M5dU93gñwi28A  x`_ ǍU)Tjj*U#VYB4 M7ZxROUb<Q\zCqo55KK.D+?6K21lzM R8g54m/zZiܚf`?>X!kC W0tUd.מ`xyˆqBpHFht %)]I&$[pƊqR(V7~nq=UKk >H-XV=u,!Jw&eiv5{N"&"M xo> Ӿ$c/ePKRIPK{L settings.xmlZ[s6~aNŐ&s16 oR@,y%9 Y.Y*NiuYxYPgW)} =Gcqa}S.buSY:b|-m#zg/G1]'hkg퓋CB&Jw/VG_b1JpL!•L8m )cZzDc]z[(=Yr>Tc<9Dw0E{)>` GJE Oq ($H8$W`6R{_sha $3Ȧ.HG<,t4\y,j=:`"&>Uť-}uFJ_'lJP=Hy,`a&Ä Èc7Cqu u!?-2W2TE%jOzMFch2eOHt"jZ )oNvV\Y[-9Å3$Zl߱(tWI׽ 5~z532$9r4erDq/>qE&x5MjGFsƢ[Cbo+n $/+-/$< 1YE K5PO?}K uEPY1(,ҵO]L],4#m17愆$D*ڑ-'ʕ3SMtGKx.S !q|Ar#X-h= Tf1Bw{85ĩ#Ԛ$-L')%)-ǻITͧʙXa:;D5E.uCog &P̐UibJ"%q#FE@$thŊ̑%1 ؙ|JE8$:[3O$`9T2X*pG*$vb&Ңь$zGQk#>gŋsҟP"[u?r^괶3kr׶$Q`ubN{NZl&Hvj!.ĪGEHO[/sI䊰LbR!ɤ~w=kqVi8[Oe{1i)l&݄LU;e$[sd+־n9ʾ4 n»˜t{qO[3n)-օ\: z17pZXvo5 ;vg FZkA; ^ӾZAshF:a"1 A|2w+V~ PRR,PwD~RNi?PKA}#&PK{L content.xml][s~(m'Iz47ƛv%fy+IYvδ%=/DR"ɷ83kK9\輺#E&33$^|?^AzOULRҤh'YU{>[Yaq1)J,HhgYUIQEՙ0]r2tOf¼T@ՃtmA Ǚ[[VFa|,l>_ZS|1GYmke*˛kO-Ψf9)@K9!^Gb2n0{K73&,RESEy-kB%rë<,*+@a64ik*U&;3>s<,IΉ{;=7ZӸ4Cs |;(ŀWխp6啷$Dž0)J7 Sa4y@vnNLaqc,vA[e 0Zۈ.rs93p&MH֟̄贛1SaB|,ة9i PuE8Tی!r#v&V&x̵ DjLqD fgvZst~B+9iȇV?KuuT~7rzD_U-e+] x "(Lͻo,-~%W$i*//H@M,,=7n2ͧMlm\۾ U']&H݇YoVM-)%tr&tsK^;IrB{H&t9Z6hly]) Ǟi])P bCLWuwT-C)ܜRiV3$/CRHpD$M Hꊡ=W^ycJF mڞCMs"ykyI@FY&mH|4[5. ъ#A-3fB!O/rj鍪`1=z{ "xdkfGXӠE]YQGˁ('& 6e~=ATRB[,g|LK "|_4nτ6pw>8gacQS,HSAhO {a=2 ibf*ڞyx#RVv.X'\ x?e&uȆ(dp x$։c}'kʹ4?u'.o?nyNfWyRjvG0񡤒nw'vWVaX=C{1VY?B%BY)l+b-B7c݉9viƸQ[FuQyFuښC}G1N~xl=}#w={j)ʻN:xnPhNzG==(,j{,h DD5׫("TUrhV}dz|ݢ5dW.\.N\֑Kd&YC[+nu}Wg*Y]t^q&AFcE{ :Ie-BBCH?"BD8BXџ%BҞ%>y~>B3z~^18Dr\uN/-ש~o'~6jJw8kh5@(I.7͵]EtUvH!v=SH: Y7bȽ6¯tUVwꑲb\e#QԚBMk-)њzYMުvAI"7Τ rKȈWn=RT$K//H2cJj>4Ns`9+ъY&e `Ǥ@6C7%1hOk,.(]72evX]"+Ju#Z8j說DЭa`$0iG?ў?TlzD>^tπ/0dޱS? BQ%].B;w>o*t~j@U:7 Cȥ|V*}^ɧ|4 V?lw#xV\C6[YVΏ[O+lqEs[:-״z{@V:8 9:ܩ~T;38"3Xӷlu9'"q*/1PVmigA?k~ܬ3e 4;%a%]Qub؇-ju]WJ׮q +9$Ld(ɀ>*#-̶Ldb{f0nw4H+xIO"uD6O"[ =Jv}cm !琦`8CKčBRhx^!Ph`G,4mm~8B_{* 8k;6pճobxN$#v)?rgxG7*3CCG>ifW޽ RR*Z4"/ E^/8 H6ばX!Tq/;1o+jV_RQMaA][/LƆiEl]ILܤ@ ֑,4ƳR4p|L|vbFpv&tc$4烡G=S~gdd9<.Q2*KCR-J&I'+3!|k6ߍZ!v AOMLn=8! |iLc(IeKQ9 )H6oV*dZIFFe YIx"3r﷿H#!8hf1# Sa|F!/Go8~`R-+8菴_vk-dr_-B5\ۆ?\4Xr9H6d +7,?G_xJWח%{OYyo^xvv[*}q5WKvMs9ǹEV˽d}7Ƴ!%фC'z* GH-isx\Wp?fT6cpoܞ ~蟚)!,Ok {gR7+ݮ23Z5\T0TP]qzS/XD˷`DqVKaA#ђ)Kˊb*f@t52_K_y*aDѫWIt?^.n ]V++xdPz$vT4dT~`⹔bco3/ h^xCZ~XJB\GfRI#{ Y\EW`W) {N ܏$e pvNǰ5dmY6gùkMKؒViݞ1d65GL >18+)X_LivK9fR7Zh^( JbMm,\[1,Np:0~,'$j J ɣt<8}q24NNap2ξ@:+6/`oV)*EB{"u b|*QM ڋ>(*6C҂jMꖖ[3vdgDl؎uČYdF-ֶӸCObb'g׫) ѹ,u{ejkλ=رJXƐ%jWk@.8j;B臣aiHAâ-eŚ-RvZ]hq{ WMÿe[T6/fQͰ8<;<8:?2xu4MиO8tKhht!`?U7MZ>l7¿vc,^.QdhWAhN>8p8F9ABÁj6BSXjv4'?ьѲ__$ylP wkQ+!N_B$UU]i=_~u$&`\1m09`_=`gd3Q_ \43 ;l{Ɩk$P/7^$.>6)"a5< 4{VotGe^PU2Z*mۗA)†Ѻyswc1ب1zj.WJ,b~7m^R~VPK7# :PK{LConfigurations2/toolpanel/PK{LConfigurations2/menubar/PK{LConfigurations2/floater/PK{LConfigurations2/statusbar/PK{LConfigurations2/popupmenu/PK{LConfigurations2/images/Bitmaps/PK{LConfigurations2/progressbar/PK{LConfigurations2/toolbar/PK{L'Configurations2/accelerator/current.xmlPKPK{LMETA-INF/manifest.xmlTn0 UM;*Zlw`n8U H@ih%vld'YN$GA;,،OY\)~.ت,D]Cj$][1w2萣rRk+{~~ay+'ɝҮޟu4&m% &܏-TZtn`mV4qĊ_Nx L Raw lamp flat spectra Standardizemeta-data Group lamp on/off exposures Subtract background Linearity correction Initialize variance Add mask Trace slits Part-processed flatswith slit traces ADU → electrons Stack Lamp on – lamp off ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_arcs.rst0000644000175100001660000000052514764317313020056 0ustar00runnerdocker************************ Near-IR, multi-slit arcs ************************ .. figure:: NIR_MOS_arc.svg :alt: DR flowchart for MOS wavelength calibration. :height: 1200 :width: 600 :scale: 50 % Reduction of arc exposures using previously-determined flat traces. Steps in grey are optional or alternative processing steps. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_flats.rst0000644000175100001660000000106314764317313020235 0ustar00runnerdocker************************* Near-IR, multi-slit flats ************************* .. figure:: NIR_MOS_trace.svg :alt: DR flowchart for MOS slit traces. :height: 1200 :width: 600 :scale: 50 % Tracing of lamp flat spectra. Steps in grey are optional or alternative processing steps. .. figure:: NIR_MOS_flat.svg :alt: DR flowchart for multi-slit lamp flat processing. :height: 1200 :width: 600 :scale: 50 % Reduction of lamp flat spectra to produce a normalized flat. Steps in grey are optional or alternative processing steps. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/NIR_science_data.rst0000644000175100001660000000072314764317313021530 0ustar00runnerdocker******************************** Near-IR, multi-slit science data ******************************** This example is based on plans for reduction of near-infrared, multi-slit spectra with a single detector array. .. figure:: NIR_MOS_science.svg :alt: DR flowchart for MOS science data. :height: 1200 :width: 600 :scale: 50 % Reduction of science exposures using previously-processed calibrations. Steps in grey are alternative processing options. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/process/index.rst0000644000175100001660000000147014764317313017525 0ustar00runnerdocker.. _specreduce-process: ************************************ Spectroscopic data reduction outline ************************************ Here are some examples of complete DR processes, to help guide development (including basic image reduction steps, provided by other packages). These are not a summary of functionality actually implemented in `specreduce `_ today. .. _specreduce-nir-mos: Near-IR, multi-slit spectroscopy ================================ This example, from Gemini, is based on plans for the reduction of near-infrared, multi-slit spectra (considering future use with optical data to a lesser extent). Some adjustments might be needed as the functionality takes form. .. toctree:: :maxdepth: 1 NIR_science_data NIR_flats NIR_arcs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/specphot_standards.rst0000644000175100001660000002431714764317313020635 0ustar00runnerdocker.. _specphot_standards: Spectrophotometric Standards ============================ Introduction ------------ Instrument sensitivity as a function of wavelength is calibrated using observations of spectrophotometric standard stars. `specreduce `_ offers some convenience functions for accessing some databases of commonly used standard stars and loading the data into `~specutils.Spectrum1D` instances. Supported Databases ------------------- Probably the most well-curated database of spectrophotometric calibration data is the `CALSPEC `_ database at `MAST `_ (Ref.: `Bohlin, Gordon, & Tremblay 2014 `_). It also has the advantage of including data that extends well into both the UV and the IR. The `~specreduce.calibration_data.load_MAST_calspec` function provides a way to easily load CALSPEC data either directly from `MAST `_ (specifically, https://archive.stsci.edu/hlsps/reference-atlases/cdbs/calspec/) or from a previously downloaded local file. Here is an example of how to use it and of a CALSPEC standard that has both UV and IR coverage: .. plot:: :include-source: import matplotlib.pyplot as plt from specreduce.calibration_data import load_MAST_calspec spec = load_MAST_calspec("agk_81d266_stisnic_007.fits") fig, ax = plt.subplots() ax.step(spec.spectral_axis, spec.flux, where="mid") ax.set_yscale('log') ax.set_xlabel(f"Wavelength ({spec.spectral_axis.unit})") ax.set_ylabel(f"Flux ({spec.flux.unit})") ax.set_title("AGK+81 266") fig.show() The `specreduce_data `_ package provides several datasets of spectrophotometric standard spectra. The bulk of them are inherited from IRAF's `onedstds `_ datasets, but some more recently curated datasets from `ESO `_, the `Nearby Supernova Factory `_, and `Gemini `_ are included as well. The `~specreduce.calibration_data.load_onedstds` function is provided to load these data into `~specutils.Spectrum1D` instances. If `specreduce_data `_ is not installed, the data will be downloaded from the GitHub `repository `_. The available database names and their descriptions are listed here. Please refer to the `specreduce-data repository `_ for details on the specific data files that are available: - `bstdscal `_: Directory of the brighter KPNO IRS standards (i.e., those with HR numbers) at 29 bandpasses, data from various sources transformed to the Hayes and Latham system, unpublished. - `ctiocal `_: Directory containing fluxes for the southern tertiary standards as published by `Baldwin & Stone, 1984, MNRAS, 206, 241 `_ and `Stone and Baldwin, 1983, MNRAS, 204, 347 `_. - `ctionewcal `_: Directory containing fluxes at 50 Å steps in the blue range 3300-7550 Å for the tertiary standards of Baldwin and Stone derived from the revised calibration of `Hamuy et al., 1992, PASP, 104, 533 `_. This directory also contains the fluxes of the tertiaries in the red (6050-10000 Å) at 50 Å steps as will be published in PASP (Hamuy et al 1994). The combined fluxes are obtained by gray shifting the blue fluxes to match the red fluxes in the overlap region of 6500-7500 Å and averaging the red and blue fluxes in the overlap. The separate red and blue fluxes may be selected by following the star name with "red" or "blue"; i.e. CD 32 blue. - `iidscal `_: Directory of the KPNO IIDS standards at 29 bandpasses, data from various sources transformed to the Hayes and Latham system, unpublished. - `irscal `_: Directory of the KPNO IRS standards at 78 bandpasses, data from various sources transformed to the Hayes and Latham system, unpublished (note that in this directory the brighter standards have no values - the `bstdscal `_ directory must be used for these standards). - `oke1990 `_: Directory of spectrophotometric standards observed for use with the HST, Table VII, `Oke 1990, AJ, 99, 1621 `_ (no correction was applied). An arbitrary 1 Å bandpass is specified for these smoothed and interpolated flux "points". - `redcal `_: Directory of standard stars with flux data beyond 8370 Å. These stars are from the IRS or the IIDS directory but have data extending as far out into the red as the literature permits. Data from various sources. - `spechayescal `_: The KPNO spectrophotometric standards at the Hayes flux points, Table IV, Spectrophotometric Standards, `Massey et al., 1988, ApJ 328, p. 315 `_. - `spec16cal `_: Directory containing fluxes at 16 Å steps in the blue range 3300-7550 Å for the secondary standards, published in `Hamuy et al., 1992, PASP, 104, 533 `_. This directory also contains the fluxes of the secondaries in the red (6020-10300 Å) at 16 Å steps as will be published in PASP (`Hamuy et al 1994 `_). The combined fluxes are obtained by gray shifting the blue fluxes to match the red fluxes in the overlap region of 6500-7500 Å and averaging the blue and red fluxes in the overlap. The separate red and blue fluxes may be selected by following the star name with "red" or "blue"; i.e. HR 1544 blue. - `spec50cal `_: The KPNO spectrophotometric standards at 50 Å intervals. The data are from (1) Table V, Spectrophotometric Standards, `Massey et al., 1988, ApJ 328, p. 315 `_ and (2) Table 3, The Kitt Peak Spectrophotometric Standards: Extension to 1 micron, `Massey and Gronwall, 1990, ApJ 358, p. 344 `_. - `snfactory `_: Preferred standard stars from the LBL Nearby Supernova Factory project: https://ui.adsabs.harvard.edu/abs/2002SPIE.4836...61A/abstract Data compiled from https://snfactory.lbl.gov/snf/snf-specstars.html. See notes there for details and references. - `eso`_: Directories of spectrophotometric standards copied from ftp://ftp.eso.org/pub/stecf/standards/. See https://www.eso.org/sci/observing/tools/standards/spectra/stanlis.html for links, notes, and details. - `gemini `_: Directory of spectrophotometric standards used by Gemini. Originally copied from https://github.com/GeminiDRSoftware/DRAGONS/tree/master/geminidr/gemini/lookups/spectrophotometric_standards. Selecting Spectrophotometric Standard Stars ------------------------------------------- Many commonly used standard stars have spectra in multiple datasets, but the quality and systematics can differ. The `~specreduce.calibration_data.load_MAST_calspec` and `~specreduce.calibration_data.load_onedstds` functions can be useful tools for exploring and comparing spectra from the various databases. An example is shown here for `LTT 9491 `_ which has spectra available from MAST, ESO, and the Nearby Supernova factory: .. plot:: :include-source: import matplotlib.pyplot as plt from specreduce.calibration_data import load_MAST_calspec, load_onedstds s1 = load_MAST_calspec("ltt9491_002.fits") s2 = load_onedstds("snfactory", "LTT9491.dat") s3 = load_onedstds("eso", "ctiostan/ltt9491.dat") fig, ax = plt.subplots() ax.step(s1.spectral_axis, s1.flux, label="MAST", where="mid") ax.step(s2.spectral_axis, s2.flux, label="SNFactory", where="mid") ax.step(s3.spectral_axis, s3.flux, label="ESO", where="mid") ax.set_yscale('log') ax.set_xlabel(f"Wavelength ({s1.spectral_axis.unit})") ax.set_ylabel(f"Flux ({s1.flux.unit})") ax.set_title("LTT 9491") ax.legend() fig.show() The `MAST`_ data have the best UV coverage, but that's not useful from the ground and they only extend to 0.9 microns in the red in this case. The other data extend to 1.0 microns, but both spectra show systematics due to telluric absorption. The `SNFactory`_ data extend well past the atmospheric cutoff with no correction applied for atmospheric transmission. The `ESO`_ data, on the other hand, are not corrected for the telluric features in the near-IR while the `SNFactory`_ data are. Regions affected by such telluric systematics should be masked out before these spectra are used for calibration purposes. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/terms.rst0000644000175100001660000003705114764317313016076 0ustar00runnerdocker Terminology ----------- As part of a workshop at NOIRLab, 13-16 Nov 2024, the attendees extensively discussed terminology in an effort to generate a common understanding of, and the nuances in, many of the terms bandied about when discussing spectroscopy data and its reduction and analysis. The following is a living document that stemmed from that original discussion. .. note:: Below, relative terms that can have multiple meanings are highlighted in italics. 2D Spectrum/Image ================= - Image = 2D Image - Optical/IR, different for other wavelengths - Something that is not necessarily pure “raw” data, but data that exists prior to extraction - Refers to e.g. the 2D CCD images (optionally pre-processed) from which 1D spectra (flux vs. wavelength) are extracted .. KBW: I missed this discussion, so I don't know how we want to capture "Alt" definitions here... Seems a bit awkward. Alt Spectrum 2D =============== - Spectrum as a function of slit position? - Fiber dispersed - Lots of alt Spectra 2D - Multiple spectral orders? - Can get up to something like 5D for solar data: wavelength x space x space x time x polarization - Is there an IVOA definition that differs from both of the above? 1D Spectrum =========== - Flux versus spectral axis (wavelength/frequency). Could be calibrated but not necessarily. *Preprocessing* =============== - (As related to spectral reduction): Things like flat-fielding and bias subtraction that are done on a full CCD image before extractions are performed. - Alt term: instrument signature removal - Meaning of this depends on context (“What do others do before I start?”) - Typically done on raw/2d image - Work done before mine. *Post-processing* ================= - Typically done on the extracted spectra? Extraction ========== - The process of converting raw spectrum data on 2D image into flux versus spectral axis or pixel (i.e. Spectrum1D), not necessarily flux or spectral calibration. Rectified ND spectrum ===================== - Non-dispersion axes means something like ra/dec, or polarization? - Nearly always (maybe always?) Implies some amount of resampling - There is one dimension that is ENTIRELY a spectral axis, all others are not specified by calling it a “Rectified ND Spectrum”, although they often do have some kind of meaning Calibrated 2D image =================== - Not-resampled. - Calibrated from raw pixel to wavelength. .. KBW: Not really sure this is terminology, but I've left it as written in the workshop notes. Facilitating Data sharing, cross-matching, etc., standards via IVOA standards ============================================================================= - Data models (but see below) - COM - ObsCore - SSA - Not widely adopted, but could be! *Workflow* ========== - More holistic than a pipeline. Often a superset of pipeline with additional steps to facilitate data ingest, job orchestration, data collection/coordination, data archiving, etc. - Also analysis. *Pipeline* ========== - Organized code for **automatically** processing raw data into calibrated spectra and - Optionally derived quantities like redshifts, line fits, ... - More generically, the linking of several steps/jobs/codes/methods where outputs of one feed as inputs to another. - “Spectral reduction pipeline”? vs. “analysis pipeline” vs. “XYZ pipeline” - SDSS and JWST pipelines are different, Dragons has three pipelines. DESI has different pipelines depending on who you talk to. Classification ============== - Identifying the type of object a spectrum represents: star, galaxy, QSO, ... - Result of Model fitting (auto or by-eye). *Redshifting* ============= - Determining the redshift of a spectrum, which may or may not be independent of classification. - Sometimes also means “changing the redshift of the object to its redshift”, e.g. converting the “wavelength/frequency” axis to rest-frame - Related issues on GitHub: - `#258 `__ - `#455 `__ - `#820 `__ Heliocentric / barycentric correct ================================== - Converting a spectrum to some rest frame in order to measure a radial velocity for a nearby stellar source. - Heliocentric is “the frame where the sun is at rest” - Barycentric is “the frame where the barycenter of the solar system is at rest” - (Some of this is very very precisely defined at the GR level) *Archive* ========= - A physical or virtual location from which processed data can be accessed. This could include both PI/collaboration access and public access - Or unprocessed. - And metadata needed to reduce raw data. - Raw data bundle (science+all cals needed to reduce it) - Ideally would Supporting FAIR principles (Findable, Accessible, Interoperable, Reusable) - Noun or verb. Data Assembly ============= - A bundle of data prepared for collaboration access (to write papers, etc.) that will eventually become a data release. - Used internally by DESI, but deprecated. - It becomes the data release at the DR date - Sloan synonym: “Internal Product Launch” Data release ============ - A bundle of data specifically intended to be public - Can be raw or not raw - Somehow “pinned” data raw/reduced/analyzed with a particular version of pipelines. - Aspires to be frozen. - Can be either a noun or a verb Open Development ================ - Developing software in a way that the community can see both how it has been developed and why it was developed that way. - Usually, but not absolutely necessarily, implies the community is also free to contribute. - Not necessarily open source. - Repos are publicly visible, including issue tracker. Flux calibration ================ - Converting a 1D/2D spectrum from “counts” to astrophysical units of flux density *Telluric Correction* ===================== - Removing the telluric (atmospheric) absorption bands from spectra - Removing the *multiplicative* component of the sky - absorption - But there was some disagreement over whether this includes sky *Sky subtraction* ================= - Removing the *additive* component of the sky/emission so all “photons” come from the source - But there was some disagreement over whether this is overlapping with a Telluric correction IFU (Integral-Field Unit) ========================= - Covers a “contiguous” 2d field on the sky with spatial information along both axes - Fibers or similar are tightly bundled and contiguously cover a region on the sky. Or an image slicer. Or a microlens array. - May or may not be multi-object. - IFS (Integral field Spectrograph) and IFU are sometimes distinguished where IFS is the whole instrument but IFU is the head-unit that does the IF part MOS (Multi-Object Spectroscopy) =============================== - Could be a fiber or a slit - Multiple objects observed in the same exposure *Flux* ====== - Energy per time per area - Also used as a shorthand for “the not spectral unit part of a 1D spectrum” (would that be the “dependent variable”?) - Oftentimes used to mean “flux density” - `Spectrum1D `__ uses the attribute 'flux'. Should this be renamed to 'flux_density'? - The intent in specutils was to not agonize over this but just accept that it's a shorthand astronomers use, and there wasn't a better name (“y”, “data”, etc) Flux Density ============ - Flux per unit wavelength/energy/wavenumber, usually(?) in astrophysical units, e.g., W/m^2/nm *Row-stacked spectra* ===================== - Collection of 1D spectra in a 2D array (image?), one spectrum per row. - Shared spectral axis. - This is the format of specutils.Spectrum1D when it's a “vector” spectrum1D *Data cube* =========== - Spectral 3D matrix with 2 spatial dimensions and one spectral one. Product of IFU data with contiguous sky coverage - Doesn't even have to be spectral, although in the spectral context it usually is - Not always 3D (data hypercuboid??). - “Multi dimensional data blob” *Spectral data cube* ==================== - At least one axis is a spectral axis but who knows about the rest! - Hypercube. *[Spectral] Data format* ======================== - “Format” can mean data structure (i.e., in-memory, possibly bound to a particular language, though it doesn't have to be - see Apache Arrow) - “Format” can also mean a file format - “Format” can also be something even more technical like “how the bytes in a struct are packed“ Data Structures =============== - Python Data structures, which are Python classes. - NDData/NDCube/SpectrumCollection, Spectrum1D etc. - CCDData. Subclass of NDData - AstroData - from DRAGONS (collection of NDData-like objects, mapped to a file, plus metadata abstraction etc.) - Lots of classes to represent spectra - Link to issue about renaming Spectrum1D class in specutils. - arrays *Data Model* ============ - In the SDSS/DESI sphere, this has a meaning that is known to differ from other uses of the term. In SDSS/DESI this means a documentation product that describes all of the files in a data release, both file formats and how they are organized into a hierarchy of directories on disk. For example, see the `desidatamodel `__. - IVOA data model is a formalized thing that follows a specific XML schema - Data model is abstract, implementation could potentially be different. - In the IVOA it is not yet allowed to be anything other than XML although there's a lot of interest in changing that - Which is different from SQL data models - The word 'schema' is sometimes used here, but that is also ambiguous even within SQL flavors itself. *Spectroscopic search - Data discovery* ======================================= - Search for spectra from any/particular instrument based on position or other known properties of the sources. If available, all the spectra will be listed. - Example tool to do this: SPARCL (How-To Jupyter notebook available here) SSA = Simple Spectral Access [VO protocol] ========================================== - "Uniform interface to remotely discover and access one-dimensional spectra." See `here `__. - Not commonly (used in the US?). (Example of use Data Central) Reduction (of spectroscopic data) ================================= - Getting data from raw-off-the-instrument (or nearly so) to the point where analysis can be done. - The process of turning 2D spectral images to 1D spectra. Can be wavelength calibrated, sky subtracted, flux calibrated, but intermediate products are "reduced" compared to earlier steps of the process. - MAYBE: Can potentially be done automatically without a human-in-the-loop? - Required products vs optional products - Removing instrument signature. - "Reduction" is in the sense of reducing complexity, but it is often an inflation of bytes (in radio it is a literal reduction, in optical usually not) - Astronomy specific word. - Spectroscopic reduction: the process of going from raw data to science-ready spectra Analysis (of spectroscopic data) ================================ - Taking scientific measurements or achieving scientific results from already-reduced spectroscopic data - Analysis does not depend on the instrument.Rem - MAYBE: cannot be done automatically, requires a human to make some sort of judgment - Optional. - Spectroscopic analysis: the process of going from science-ready spectra to science Sky === - Model or observed sky background (really a foreground!) which was usually subtracted from the observed spectrum *Stacking* ========== - Combination of multiple spectra in a prescribed way to increase quality (e.g., S/N) - in some contexts like numpy arrays and astropy.table.vstack, it can refer to combining multiple objects / tables into a single object without coadding data. *Coadding* ========== - combining multiple spectra of the same object into a single spectrum, e.g. to improve signal-to-noise or combine spectra across multiple wavelength ranges. - Alternative term for “stacking” to disambiguate meanings Spectral fitting ================ - Modeling an observed spectrum with templates (possibly physically motivated) and/or mathematical functions Digital Twins ============= - Realistic fake data that potentially adapts to new states of the system over time. Trace fitting ============= - on a 2D CCD image from a multi-object spectrometer, typically wavelengths span in 1 direction and fibers/objects in the other direction. “trace fitting” is mapping the y vs. x of where the spectra actually go on the CCD image. - Different for slit-based and fiber-based spectrographs - slit-based: trace the *edges* of the slit spectrum along the spectral direction - fiber-based: trace the *center* of the fiber spectrum spatial point-spread function - Spectral tracing Wavelength calibration ====================== - calibrating what true wavelength is represented by the observed photons on a detector, e.g. what wavelength is row y of a detector? - Possibly a 2d process - The process of adding (the spectral part of) a WCS Visual Inspection (of spectra) ============================== - Humans looking at spectra and making decisions about what the “truth” is. - Can include identifying the presence/absence of features (qualitative) or assessing a quantitative fit (e.g., best-fit redshift value) *Spectral resolution* ===================== - Changes in spectral dispersion power as a function of wavelength due to instrument - Resolving power vs Resolution/Dispersion - Resolving power is the ability to distinguish close features - Dispersion is the change in wavelength/energy per pixel API === - Application Programming Interface - What makes a good API for spectroscopic software? - What is needed for different aspects of spectroscopic software (e.g., reduction vs. archive access)? *Spectral class* ================ - E.g., Spectrum1D - In SDSS, 'class' is short for 'classification'. - DESI uses SPECTYPE for spectral type (QSO, GALAXY, STAR) Package ======= - A software tool or collection of tools developed in the same “space” - Has a specific meaning in a Python context that’s more specific, but can be used more generally for multiple languages Spectral data visualization =========================== - Tools and procedures to display reduced spectral data Spectral decomposition ====================== - a form of spectral fitting that identifies separate components that appear in a spectrum; e.g, quasar + galaxy. - Goes with spectral fitting. Processing Steps ================ - For DESI (largely inherited from SDSS usage): - pre-processing (of CCD images, bias, dark, pixel-flat fielding) - extraction (getting counts vs. wavelength from 2D images) - sky subtraction (subtracting the additive non-signal sky component) - flux calibration (includes both instrument throughput and telluric absorption multiplicative corrections) - classification and redshift fitting (is it a galaxy, star, or quasar; at what redshift?) ---- Mentioned but not defined ------------------------- - WCS & Database archive - Cloud archiving - Modular functions which can be used by other pipelines. - Interactive Dashboard ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/docs/wavelength_calibration.rst0000644000175100001660000000474714764317313021465 0ustar00runnerdocker.. _wavelength_calibration: Wavelength Calibration ====================== Wavelength calibration is currently supported for 1D spectra. Given a list of spectral lines with known wavelengths and estimated pixel positions on an input calibration spectrum, you can currently use ``specreduce`` to: #. Fit an ``astropy`` model to the wavelength/pixel pairs to generate a spectral WCS solution for the dispersion. #. Apply the generated spectral WCS to other `~specutils.Spectrum1D` objects. 1D Wavelength Calibration ------------------------- The `~specreduce.wavelength_calibration.WavelengthCalibration1D` class can be used to fit a dispersion model to a list of line positions and wavelengths. Future development will implement catalogs of known lamp spectra for use in matching observed lines. In the example below, the line positions (``pixel_centers``) have already been extracted from ``lamp_spectrum``:: import astropy.units as u from specreduce import WavelengthCalibration1D pixel_centers = [10, 22, 31, 43] wavelengths = [5340, 5410, 5476, 5543]*u.AA test_cal = WavelengthCalibration1D(lamp_spectrum, line_pixels=pixel_centers, line_wavelengths=wavelengths) calibrated_spectrum = test_cal.apply_to_spectrum(science_spectrum) The example above uses the default model (`~astropy.modeling.functional_models.Linear1D`) to fit the input spectral lines, and then applies the calculated WCS solution to a second spectrum (``science_spectrum``). Any other 1D ``astropy`` model can be provided as the input ``model`` parameter to the `~specreduce.wavelength_calibration.WavelengthCalibration1D`. In the above example, the model fit and WCS construction is all done as part of the ``apply_to_spectrum()`` call, but you could also access the `~gwcs.wcs.WCS` object itself by calling:: test_cal.wcs The calculated WCS is a cached property that will be cleared if the ``line_list``, ``model``, or ``input_spectrum`` properties are updated, since these will alter the calculated dispersion fit. You can also provide the input pixel locations and wavelengths of the lines as an `~astropy.table.QTable` with (at minimum) columns ``pixel_center`` and ``wavelength``, using the ``matched_line_list`` input argument:: from astropy.table import QTable pixels = [10, 20, 30, 40]*u.pix wavelength = [5340, 5410, 5476, 5543]*u.AA line_list = QTable([pixels, wavelength], names=["pixel_center", "wavelength"]) test_cal = WavelengthCalibration1D(lamp_spectrum, matched_line_list=line_list)././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.722497 specreduce-1.5.1/licenses/0000755000175100001660000000000014764317326015065 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/licenses/KOSMOS_LICENSE0000644000175100001660000000217714764317313017170 0ustar00runnerdocker# NOTE: This license applies only to code used in part of the FitTrace class. MIT License Copyright (c) 2019 James Davenport Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/licenses/LICENSE.rst0000644000175100001660000000273514764317313016704 0ustar00runnerdockerCopyright (c) 2017, Astropy-specreduce Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/pyproject.toml0000644000175100001660000000550514764317313016175 0ustar00runnerdocker[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.10" dependencies = [ "numpy", "astropy", "scipy", "specutils>=1.9.1", "gwcs", ] [project.optional-dependencies] test = [ "pytest-astropy", "photutils", "tox", ] 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.black] line-length = 100 target-version = ['py311', 'py312', 'py313'] [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", "ignore:.*unclosed self.image.shape[self.crossdisp_axis]: warnings.warn( "background window extends beyond image boundaries " + f"({windows_max} >= {self.image.shape[self.crossdisp_axis]})" ) if windows_min < 0: warnings.warn( "background window extends beyond image boundaries " + f"({windows_min} < 0)" ) # pass trace.trace.data to ignore any mask on the trace bkg_wimage += _ap_weight_image( trace, self.width, self.disp_axis, self.crossdisp_axis, self.image.shape ) if np.any(bkg_wimage > 1): raise ValueError("background regions overlapped") if np.any(np.sum(bkg_wimage, axis=self.crossdisp_axis) == 0): raise ValueError( "background window does not remain in bounds across entire dispersion axis" ) # noqa # check if image contained within background window is fully-nonfinite and raise an error if np.all(img.mask[bkg_wimage > 0]): raise ValueError( "Image is fully masked within background window determined by `width`." ) # noqa if self.statistic == "median": # make it clear in the expose image that partial pixels are fully-weighted bkg_wimage[bkg_wimage > 0] = 1 self.bkg_wimage = bkg_wimage if self.statistic == "average": self._bkg_array = np.ma.average(img, weights=self.bkg_wimage, axis=self.crossdisp_axis) elif self.statistic == "median": # combine where background weight image is 0 with image masked (which already # accounts for non-finite data that wasn't already masked) img.mask = np.logical_or(self.bkg_wimage == 0, self.image.mask) self._bkg_array = np.ma.median(img, axis=self.crossdisp_axis) else: raise ValueError("statistic must be 'average' or 'median'") def _set_traces(self): """Determine `traces` from input. If an integer/float or list if int/float is passed in, use these to construct FlatTrace objects. These values must be positive. If None (which is initialized to an empty list), construct a FlatTrace using the center of image (according to disp. axis). Otherwise, any Trace object or list of Trace objects can be passed in.""" if self.traces == []: # assume a flat trace at the image center if nothing is passed in. trace_pos = self.image.shape[self.disp_axis] / 2.0 self.traces = [FlatTrace(self.image, trace_pos)] if isinstance(self.traces, Trace): # if just one trace, turn it into iterable. self.traces = [self.traces] return # finally, if float/int is passed in convert to FlatTrace(s) if isinstance(self.traces, (float, int)): # for a single number self.traces = [self.traces] if np.all([isinstance(x, (float, int)) for x in self.traces]): self.traces = [FlatTrace(self.image, trace_pos) for trace_pos in self.traces] return else: if not np.all([isinstance(x, Trace) for x in self.traces]): raise ValueError( "`traces` must be a `Trace` object or list of " "`Trace` objects, a number or list of numbers to " "define FlatTraces, or None to use a FlatTrace in " "the middle of the image." ) @classmethod def two_sided(cls, image, trace_object, separation, **kwargs): """ Determine the background from an image for subtraction centered around an input trace. Example: :: trace = FitTrace(image, guess=trace_pos) bg = Background.two_sided(image, trace, bkg_sep, width=bkg_width) Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like Image with 2-D spectral image data. Assumes cross-dispersion (spatial) direction is axis 0 and dispersion (wavelength) direction is axis 1. trace_object: `~specreduce.tracing.Trace` estimated trace of the spectrum to center the background traces separation: float separation from ``trace_object`` for the background regions width : float width of each background aperture in pixels statistic: string statistic to use when computing the background. 'average' will account for partial pixel weights, 'median' will include all partial pixels. disp_axis : int dispersion axis crossdisp_axis : int cross-dispersion axis mask_treatment : string The method for handling masked or non-finite data. Choice of ``filter``, ``omit`, or ``zero_fill``. If `filter` is chosen, masked/non-finite data will be filtered during the fit to each bin/column (along disp. axis) to find the peak. If ``omit`` is chosen, columns along disp_axis with any masked/non-finite data values will be fully masked (i.e, 2D mask is collapsed to 1D and applied). If ``zero_fill`` is chosen, masked/non-finite data will be replaced with 0.0 in the input image, and the mask will then be dropped. For all three options, the input mask (optional on input NDData object) will be combined with a mask generated from any non-finite values in the image data. """ image = _ImageParser._get_data_from_image(image) if image is not None else cls.image kwargs["traces"] = [trace_object - separation, trace_object + separation] return cls(image=image, **kwargs) @classmethod def one_sided(cls, image, trace_object, separation, **kwargs): """ Determine the background from an image for subtraction above or below an input trace. Example: :: trace = FitTrace(image, guess=trace_pos) bg = Background.one_sided(image, trace, bkg_sep, width=bkg_width) Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like Image with 2-D spectral image data. Assumes cross-dispersion (spatial) direction is axis 0 and dispersion (wavelength) direction is axis 1. trace_object: `~specreduce.tracing.Trace` estimated trace of the spectrum to center the background traces separation: float separation from ``trace_object`` for the background, positive will be above the trace, negative below. width : float width of each background aperture in pixels statistic: string statistic to use when computing the background. 'average' will account for partial pixel weights, 'median' will include all partial pixels. disp_axis : int dispersion axis crossdisp_axis : int cross-dispersion axis mask_treatment : string The method for handling masked or non-finite data. Choice of ``filter``, ``omit``, or ``zero_fill``. If `filter` is chosen, masked/non-finite data will be filtered during the fit to each bin/column (along disp. axis) to find the peak. If ``omit`` is chosen, columns along disp_axis with any masked/non-finite data values will be fully masked (i.e, 2D mask is collapsed to 1D and applied). If ``zero_fill`` is chosen, masked/non-finite data will be replaced with 0.0 in the input image, and the mask will then be dropped. For all three options, the input mask (optional on input NDData object) will be combined with a mask generated from any non-finite values in the image data. """ image = _ImageParser._get_data_from_image(image) if image is not None else cls.image kwargs["traces"] = [trace_object + separation] return cls(image=image, **kwargs) def bkg_image(self, image=None): """ Expose the background tiled to the dimension of ``image``. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, optional Image with 2-D spectral image data. Assumes cross-dispersion (spatial) direction is axis 0 and dispersion (wavelength) direction is axis 1. If None, will extract the background from ``image`` used to initialize the class. [default: None] Returns ------- `~specutils.Spectrum1D` object with same shape as ``image``. """ image = self._parse_image(image) return Spectrum1D( np.tile(self._bkg_array, (image.shape[0], 1)) * image.unit, spectral_axis=image.spectral_axis, ) def bkg_spectrum(self, image=None, bkg_statistic="sum"): """ Expose the 1D spectrum of the background. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, optional Image with 2-D spectral image data. Assumes cross-dispersion (spatial) direction is axis 0 and dispersion (wavelength) direction is axis 1. If None, will extract the background from ``image`` used to initialize the class. [default: None] bkg_statistic : {'average', 'median', 'sum'}, optional Statistical method used to collapse the background image. [default: ``'sum'``] Supported values are: - ``'average'`` : Uses the mean (`numpy.nanmean`). - ``'median'`` : Uses the median (`numpy.nanmedian`). - ``'sum'`` : Uses the sum (`numpy.nansum`). Returns ------- spec : `~specutils.Spectrum1D` The background 1-D spectrum, with flux expressed in the same units as the input image (or u.DN if none were provided) and the spectral axis expressed in pixel units. """ bkg_image = self.bkg_image(image) if bkg_statistic == 'sum': statistic_function = np.nansum elif bkg_statistic == 'median': statistic_function = np.nanmedian elif bkg_statistic == 'average': statistic_function = np.nanmean else: raise ValueError(f"Background statistic {bkg_statistic} is not supported. " "Please choose from: average, median, or sum.") try: return bkg_image.collapse(statistic_function, axis=self.crossdisp_axis) except u.UnitTypeError: # can't collapse with a spectral axis in pixels because # SpectralCoord only allows frequency/wavelength equivalent units... ext1d = statistic_function(bkg_image.flux, axis=self.crossdisp_axis) return Spectrum1D(ext1d, bkg_image.spectral_axis) def sub_image(self, image=None): """ Subtract the computed background from ``image``. Parameters ---------- image : nddata-compatible image or None image with 2-D spectral image data. If None, will extract the background from ``image`` used to initialize the class. Returns ------- `~specutils.Spectrum1D` object with same shape as ``image``. """ image = self._parse_image(image) # a compare_wcs argument is needed for Spectrum1D.subtract() in order to # avoid a TypeError from SpectralCoord when image's spectral axis is in # pixels. it is not needed when image's spectral axis has physical units kwargs = {"compare_wcs": None} if image.spectral_axis.unit == u.pix else {} # https://docs.astropy.org/en/stable/nddata/mixins/ndarithmetic.html return image.subtract(self.bkg_image(image), **kwargs) def sub_spectrum(self, image=None): """ Expose the 1D spectrum of the background-subtracted image. Parameters ---------- image : nddata-compatible image or None image with 2-D spectral image data. If None, will extract the background from ``image`` used to initialize the class. Returns ------- spec : `~specutils.Spectrum1D` The background 1-D spectrum, with flux expressed in the same units as the input image (or u.DN if none were provided) and the spectral axis expressed in pixel units. """ sub_image = self.sub_image(image=image) try: return sub_image.collapse(np.nansum, axis=self.crossdisp_axis) except u.UnitTypeError: # can't collapse with a spectral axis in pixels because # SpectralCoord only allows frequency/wavelength equivalent units... ext1d = np.nansum(sub_image.flux, axis=self.crossdisp_axis) return Spectrum1D(ext1d, spectral_axis=sub_image.spectral_axis) def __rsub__(self, image): """ Subtract the background from an image. """ return self.sub_image(image) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/calibration_data.py0000644000175100001660000004027014764317313021233 0ustar00runnerdocker""" Utilities for defining, loading, and handling spectroscopic calibration data """ import warnings from pathlib import Path from typing import Sequence, Literal from urllib.error import URLError 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 from astropy.coordinates import SpectralCoord from specutils import Spectrum1D from specutils.utils.wcs_utils import vac_to_air __all__ = [ 'get_available_line_catalogs', 'load_pypeit_calibration_lines', 'load_MAST_calspec', 'load_onedstds', 'AtmosphericExtinction', 'AtmosphericTransmission' ] SUPPORTED_EXTINCTION_MODELS = [ "kpno", "ctio", "apo", "lapalma", "mko", "mtham", "paranal" ] SPECPHOT_DATASETS = [ "bstdscal", "ctiocal", "ctionewcal", "eso", "gemini", "iidscal", "irscal", "oke1990", "redcal", "snfactory", "spec16cal", "spec50cal", "spechayescal" ] PYPEIT_CALIBRATION_LINELISTS = [ 'Ne_IR_MOSFIRE', 'ArII', 'CdI', 'OH_MOSFIRE_H', 'OH_triplespec', 'Ar_IR_MOSFIRE', 'OH_GNIRS', 'ThAr_XSHOOTER_VIS', 'ThAr_MagE', 'HgI', 'NeI', 'XeI', 'OH_MODS', 'ZnI', 'OH_GMOS', 'CuI', 'ThAr_XSHOOTER_VIS_air', 'ThAr_XSHOOTER_UVB', 'OH_NIRES', 'HeI', 'FeI', 'OH_MOSFIRE_J', 'KrI', 'Cd_DeVeny1200', 'Ar_IR_GNIRS', 'OH_MOSFIRE_Y', 'ThAr', 'FeII', 'OH_XSHOOTER', 'OH_FIRE_Echelle', 'OH_MOSFIRE_K', 'OH_R24000', 'Hg_DeVeny1200', 'ArI' ] SPECREDUCE_DATA_URL = ("https://raw.githubusercontent.com/astropy/specreduce-data/" "main/specreduce_data/reference_data/") PYPEIT_DATA_URL = ("https://raw.githubusercontent.com/pypeit/" "pypeit/release/pypeit/data/") def get_available_line_catalogs() -> dict: """ Returns a dictionary of available line catalogs. Currently only ``pypeit`` catalogs are fully supported. """ return { 'pypeit': PYPEIT_CALIBRATION_LINELISTS } def load_pypeit_calibration_lines( lamps: Sequence | None = None, wave_air: bool = False, cache: bool | Literal['update'] = True, show_progress: bool = False ) -> QTable | None: """ Load reference calibration lines from ``pypeit`` linelists. The ``pypeit`` linelists are well-curated and have been tested across a wide range of spectrographs. The available linelists are defined by ``PYPEIT_CALIBRATION_LINELISTS``. Parameters ---------- lamps : Lamp string, comma-separated list of lamps, or sequence of lamps to include in output reference linelist. The parlance of "lamp" is retained here for consistency with its use in ``pypeit`` and elsewhere. In several of the supported cases the "lamp" is the sky itself (e.g. OH lines in the near-IR). The available lamps are defined by ``PYPEIT_CALIBRATION_LINELISTS``. wave_air : If True, convert the vacuum wavelengths used by ``pypeit`` to air wavelengths. cache : Toggle caching of downloaded data show_progress : Show download progress bar Returns ------- linelist: Table containing the combined calibration line list. ``pypeit`` linelists have the following columns: * ``ion``: Ion or molecule generating the line. * ``wavelength``: Vacuum wavelength of the line in Angstroms. * ``NIST``: Flag denoting if NIST is the ultimate reference for the line's wavelength. * ``Instr``: ``pypeit``-specific instrument flag. * ``amplitude``: Amplitude of the line. Beware, not consistent between lists. * ``Source``: Source of the line information. """ if lamps is None: return None if not isinstance(lamps, Sequence): raise ValueError(f"Invalid calibration lamps specification: {lamps}") if isinstance(lamps, str): if ',' in lamps: lamps = [lamp.strip() for lamp in lamps.split(',')] else: lamps = [lamps] linelists = [] for lamp in lamps: if lamp in PYPEIT_CALIBRATION_LINELISTS: data_url = f"{PYPEIT_DATA_URL}/arc_lines/lists/{lamp}_lines.dat" try: data_path = download_file(data_url, cache=cache, show_progress=show_progress, pkgname='specreduce') linelists.append(Table.read(data_path, format='ascii.fixed_width', comment='#')) except URLError as e: warnings.warn(f"Downloading of {data_url} failed: {e}", AstropyUserWarning) else: warnings.warn( f"{lamp} not in the list of supported calibration " f"line lists: {PYPEIT_CALIBRATION_LINELISTS}." ) if len(linelists) == 0: warnings.warn(f"No calibration lines loaded from {lamps}.") linelist = None else: linelist = QTable(vstack(linelists)) linelist.rename_column('wave', 'wavelength') # pypeit linelists use vacuum wavelengths in angstroms linelist['wavelength'] *= u.Angstrom if wave_air: linelist['wavelength'] = vac_to_air(linelist['wavelength']) return linelist def load_MAST_calspec( filename: str | Path, cache: bool | Literal['update'] = True, show_progress: bool = False ) -> Spectrum1D | None: """ Load a standard star spectrum from the ``calspec`` database at MAST. These spectra are provided in FITS format and are described in detail at: https://www.stsci.edu/hst/instrumentation/reference-data-for-calibration-and-tools/astronomical-catalogs/calspec 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 : FITS filename of a standard star spectrum, e.g. g191b2b_005.fits. If this is a local file, it will be loaded. If not, then a download from MAST will be attempted. cache : Toggle whether downloaded data is cached or not. show_progress : Toggle whether download progress bar is shown. Returns ------- spectrum : If the spectrum can be loaded, return it as a `~specutils.Spectrum1D`. Otherwise return None. The spectral_axis units are Å and the flux units are milli-Janskys. """ filename = Path(filename) if filename.exists() and filename.is_file(): file_path = filename else: try: data_url = f"https://archive.stsci.edu/hlsps/reference-atlases/cdbs/calspec/{filename}" file_path = download_file(data_url, cache=cache, show_progress=show_progress, pkgname='specreduce') except URLError as e: warnings.warn(f"Downloading of {filename} failed: {e}", AstropyUserWarning) file_path = None 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. flux_mjy = synphot.units.convert_flux(wave, flux, u.mJy) spectrum = Spectrum1D(spectral_axis=wave, flux=flux_mjy) return spectrum def load_onedstds( dataset: str = "snfactory", specfile: str = "EG131.dat", cache: bool | Literal['update'] = True, show_progress: bool = False ) -> Spectrum1D | None: """ This is a convenience function for loading a standard star spectrum from the 'onedstds' dataset in the ``specreduce_data`` package. They will be downloaded from the repository on GitHub and cached by default. Parameters ---------- dataset : Standard star spectrum database. Valid options are described in :ref:`specphot_standards`. specfile : Filename of the standard star spectrum. cache : Enable caching of downloaded data. show_progress : Show download progress bar if data is downloaded. Returns ------- spectrum : If the spectrum can be loaded, return it as a `~specutils.Spectrum1D`. Otherwise return None. The spectral_axis units are Å and the flux units are milli-Janskys. """ if dataset not in SPECPHOT_DATASETS: msg = (f"Specfied dataset, {dataset}, not in list of supported datasets of " f"spectrophotometric standard stars: f{SPECPHOT_DATASETS}") warnings.warn(msg, AstropyUserWarning) return None try: data_path = download_file(f"{SPECREDUCE_DATA_URL}/onedstds/{dataset}/{specfile}", cache=cache, show_progress=show_progress, pkgname="specreduce") t = Table.read(data_path, format="ascii", names=['wavelength', 'ABmag', 'binsize']) except URLError as e: msg = f"Can't load {specfile} from {dataset}: {e}." warnings.warn(msg, AstropyUserWarning) return None # the specreduce_data standard star spectra all provide wavelengths in angstroms spectral_axis = t['wavelength'].data * u.angstrom # the specreduce_data standard star spectra all provide fluxes in AB mag flux = t['ABmag'].data * u.ABmag flux = flux.to(u.mJy) # convert to linear flux units spectrum = Spectrum1D(spectral_axis=spectral_axis, flux=flux) return spectrum class AtmosphericExtinction(Spectrum1D): """ Spectrum container for atmospheric extinction in magnitudes as a function of wavelength. If extinction and spectral_axis are provided, this will use them to build a custom model. If they are not, the 'model' parameter will be used to lookup and load a pre-defined atmospheric extinction model from the ``specreduce_data`` package. Parameters ---------- model : str Name of atmospheric extinction model provided by ``specreduce_data``. Valid options are: * kpno - Kitt Peak National Observatory (default) * ctio - Cerro Tololo International Observatory * apo - Apache Point Observatory * lapalma - Roque de los Muchachos Observatory, La Palma, Canary Islands * mko - Mauna Kea Observatories * mtham - Lick Observatory, Mt. Hamilton station * paranal - European Southern Observatory, Cerro Paranal station extinction : float, `~astropy.units.Quantity`, or `None`, optional Provides extinction data for this spectrum. Used along with spectral_axis to build custom atmospheric extinction model. If no units are provided, assumed to be given in magnitudes. spectral_axis : `~astropy.coordinates.SpectralCoord`, `~astropy.units.Quantity`, or `None`, optional Dispersion information with the same shape as the last (or only) dimension of flux, or one greater than the last dimension of flux if specifying bin edges. Used along with flux to build custom atmospheric extinction model. Attributes ---------- extinction_mag : `~astropy.units.Quantity` Extinction expressed in dimensionless magnitudes transmission : `~astropy.units.Quantity` Extinction expressed as fractional transmission """ # noqa: E501 def __init__( self, model: str = "kpno", extinction: Sequence[float] | u.Quantity | None = None, spectral_axis: SpectralCoord | u.Quantity | None = None, cache: bool | Literal['update'] = True, show_progress: bool = False, **kwargs: str ) -> None: if extinction is not None: if not isinstance(extinction, u.Quantity): warnings.warn( "Input extinction is not a Quanitity. Assuming it is given in magnitudes...", AstropyUserWarning ) extinction = u.Magnitude( extinction, u.MagUnit(u.dimensionless_unscaled) ).to(u.dimensionless_unscaled) # Spectrum1D wants this to be linear elif isinstance(extinction, (u.LogUnit, u.Magnitude)) or extinction.unit == u.mag: # if in log or magnitudes, recast into Magnitude with dimensionless physical units extinction = u.Magnitude( extinction.value, u.MagUnit(u.dimensionless_unscaled) ).to(u.dimensionless_unscaled) elif extinction.unit != u.dimensionless_unscaled: # if we're given something linear that's not dimensionless_unscaled, # it's an error msg = "Input extinction must have unscaled dimensionless units." raise ValueError(msg) if extinction is None and spectral_axis is None: if model not in SUPPORTED_EXTINCTION_MODELS: msg = ( f"Requested extinction model, {model}, not in list " f"of available models: {SUPPORTED_EXTINCTION_MODELS}" ) raise ValueError(msg) data_file = download_file(f"{SPECREDUCE_DATA_URL}/extinction/{model}extinct.dat", cache=cache, show_progress=show_progress, pkgname='specreduce') t = Table.read(data_file, format="ascii", names=['wavelength', 'extinction']) # the specreduce_data models all provide wavelengths in angstroms spectral_axis = t['wavelength'].data * u.angstrom # the specreduce_data models all provide extinction in magnitudes at an airmass of 1 extinction = u.Magnitude( t['extinction'].data, u.MagUnit(u.dimensionless_unscaled) ).to(u.dimensionless_unscaled) if spectral_axis is None: msg = "Missing spectral axis for input extinction data." raise ValueError(msg) super(AtmosphericExtinction, self).__init__( flux=extinction, spectral_axis=spectral_axis, unit=u.dimensionless_unscaled, **kwargs ) @property def extinction_mag(self) -> u.Quantity: """ This property returns the extinction in magnitudes """ return self.flux.to(u.mag(u.dimensionless_unscaled)) @property def transmission(self) -> u.Quantity: """ This property returns the transmission as a fraction between 0 and 1 """ return self.flux class AtmosphericTransmission(AtmosphericExtinction): """ Spectrum container for atmospheric transmission as a function of wavelength. Parameters ---------- data_file : Name to file containing atmospheric transmission data. Data is assumed to have 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://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. wave_unit : Units for spectral axis. """ def __init__( self, data_file: str | Path | None = None, wave_unit: u.Unit = u.um, **kwargs: str ) -> None: if data_file is None: data_file = download_file(f"{SPECREDUCE_DATA_URL}/extinction/atm_trans_am1.0.dat") t = Table.read(data_file, format="ascii", names=['wavelength', 'extinction']) # spectral axis is given in microns spectral_axis = t['wavelength'].data * wave_unit # extinction is given in a dimensionless transmission fraction extinction = t['extinction'].data * u.dimensionless_unscaled super(AtmosphericTransmission, self).__init__( extinction=extinction, spectral_axis=spectral_axis, **kwargs ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/core.py0000644000175100001660000002343714764317313016711 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see LICENSE.rst from copy import deepcopy import inspect from dataclasses import dataclass from typing import Literal import numpy as np from astropy import units as u from astropy.nddata import VarianceUncertainty, NDData from specutils import Spectrum1D __all__ = ["SpecreduceOperation"] MaskingOption = Literal[ "apply", "ignore", "propagate", "zero_fill", "nan_fill", "apply_mask_only", "apply_nan_only" ] ImageLike = np.ndarray | NDData | u.Quantity class _ImageParser: """ Coerces images from accepted formats to Spectrum1D objects for internal use in specreduce's operation classes. Fills any and all of uncertainty, mask, units, and spectral axis that are missing in the provided image with generic values. Accepted image types are: - `~specutils.spectra.spectrum1d.Spectrum1D` (preferred) - `~astropy.nddata.ccddata.CCDData` - `~astropy.nddata.ndddata.NDDData` - `~astropy.units.quantity.Quantity` - `~numpy.ndarray` """ # The '_valid_mask_treatment_methods' in the Background, Trace, and Extract # classes is a subset of implemented methods. implemented_mask_treatment_methods = ( "apply", "ignore", "propagate", "zero_fill", "nan_fill", "apply_mask_only", "apply_nan_only", ) def _parse_image( self, image: ImageLike, disp_axis: int = 1, mask_treatment: MaskingOption = "apply" ) -> Spectrum1D: """ Convert all accepted image types to a consistently formatted Spectrum1D object. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like The image to be parsed. If None, defaults to class' own image attribute. disp_axis The index of the image's dispersion axis. Should not be changed until operations can handle variable image orientations. mask_treatment Specifies how to handle masked or non-finite values in the input image. The accepted values are: - ``apply``: The image remains unchanged, and any existing mask is combined\ with a mask derived from non-finite values. - ``ignore``: The image remains unchanged, and any existing mask is dropped. - ``propagate``: The image remains unchanged, and any masked or non-finite pixel\ causes the mask to extend across the entire cross-dispersion axis. - ``zero_fill``: Pixels that are either masked or non-finite are replaced with 0.0,\ and the mask is dropped. - ``nan_fill``: Pixels that are either masked or non-finite are replaced with nan,\ and the mask is dropped. - ``apply_mask_only``: The image and mask are left unmodified. - ``apply_nan_only``: The image is left unmodified, the old mask is dropped, and a\ new mask is created based on non-finite values. Returns ------- Spectrum1D """ # would be nice to handle (cross)disp_axis consistently across # operations (public attribute? private attribute? argument only?) so # it can be called from self instead of via kwargs... if image is None: # useful for Background's instance methods return self.image return self._get_data_from_image(image, disp_axis=disp_axis, mask_treatment=mask_treatment) @staticmethod def _get_data_from_image( image: ImageLike, disp_axis: int = 1, mask_treatment: MaskingOption = "apply" ) -> Spectrum1D: """ Extract data array from various input types for `image`. Parameters ---------- image Input image from which data is extracted. This can be a 2D numpy array, Quantity, or an NDData object. disp_axis The dispersion axis of the image. mask_treatment Specifies how to handle masked or non-finite values in the input image. Returns ------- Spectrum1D """ if isinstance(image, u.quantity.Quantity): img = image.value elif isinstance(image, np.ndarray): img = image else: # NDData, including CCDData and Spectrum1D img = image.data mask = getattr(image, "mask", None) crossdisp_axis = (disp_axis + 1) % 2 # next, handle masked and non-finite data in image. # A mask will be created from any non-finite image data, and combined # with any additional 'mask' passed in. If image is being parsed within # a specreduce operation that has 'mask_treatment' options, this will be # handled as well. Note that input data may be modified if a fill value # is chosen to handle masked data. The returned image will always have # `image.mask` even if there are no non-finite or masked values. img, mask = _ImageParser._mask_and_nonfinite_data_handling( image=img, mask=mask, mask_treatment=mask_treatment, crossdisp_axis=crossdisp_axis ) # mask (handled above) and uncertainty are set as None when they aren't # specified upon creating a Spectrum1D object, so we must check whether # these attributes are absent *and* whether they are present but set as None if hasattr(image, "uncertainty"): uncertainty = image.uncertainty else: uncertainty = VarianceUncertainty(np.ones(img.shape)) unit = getattr(image, "unit", u.Unit("DN")) spectral_axis = getattr(image, "spectral_axis", np.arange(img.shape[disp_axis]) * u.pix) img = Spectrum1D( img * unit, spectral_axis=spectral_axis, uncertainty=uncertainty, mask=mask ) return img @staticmethod def _mask_and_nonfinite_data_handling( image: ImageLike, mask: ImageLike | None = None, mask_treatment: str = "apply", crossdisp_axis: int = 1, ) -> tuple[np.ndarray, np.ndarray]: """ Handle the treatment of masked and non-finite data. All operations in Specreduce can take in a mask for the data as part of the input NDData. There are five options currently implemented for the treatment of masked and non-finite data - apply, ignore, zero_fill, nan_fill, apply_mask_only, and apply_nan_only. Depending on the routine, all or a subset of these three options are valid. Parameters ---------- image : array-like The input image data array that may contain non-finite values. mask : array-like of bool or None An optional Boolean mask array. Non-finite values in the image will be added to this mask. mask_treatment Specifies how to handle masked or non-finite values in the input image. """ if mask_treatment not in _ImageParser.implemented_mask_treatment_methods: raise ValueError( "'mask_treatment' must be one of " f"{_ImageParser.implemented_mask_treatment_methods}" ) if mask is not None and (mask.dtype not in (bool, int)): raise ValueError("'mask' must be a boolean or integer array.") match mask_treatment: case "apply": mask = mask | (~np.isfinite(image)) if mask is not None else ~np.isfinite(image) case "ignore": mask = np.zeros(image.shape, dtype=bool) case "propagate": if mask is None: mask = ~np.isfinite(image) else: mask = mask | (~np.isfinite(image)) mask[:] = mask.any(axis=crossdisp_axis, keepdims=True) case "zero_fill" | "nan_fill": mask = mask | (~np.isfinite(image)) if mask is not None else ~np.isfinite(image) image = deepcopy(image) if mask_treatment == "zero_fill": image[mask] = 0.0 else: image[mask] = np.nan mask[:] = False case "apply_nan_only": mask = ~np.isfinite(image) case "apply_mask_only": mask = mask.copy() if mask is not None else np.zeros(image.shape, dtype=bool) if mask.all(): raise ValueError("Image is fully masked. Check for invalid values.") return image, mask @dataclass class SpecreduceOperation(_ImageParser): """ An operation to perform as part of a spectroscopic reduction pipeline. This class primarily exists to define the basic API for operations: parameters for the operation are provided at object creation, and then the operation object is called with the data objects required for the operation, which then *return* the data objects resulting from the operation. """ def __call__(self): raise NotImplementedError("__call__ on a SpecreduceOperation needs to " "be overridden") @classmethod def as_function(cls, *args, **kwargs): """ Run this operation as a function. Syntactic sugar for e.g., ``Operation.as_function(arg1, arg2, keyword=value)`` maps to ``Operation(arg2, keyword=value)(arg1)`` (if the ``__call__`` of ``Operation`` has only one argument) """ argspec = inspect.getargs(cls.__call__.__code__) if argspec.varargs: raise NotImplementedError( "There is not a way to determine the " "number of inputs of a *args style " "operation" ) ninputs = len(argspec.args) - 1 callargs = args[:ninputs] noncallargs = args[ninputs:] op = cls(*noncallargs, **kwargs) return op(*callargs) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/extract.py0000644000175100001660000010050114764317313017417 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see LICENSE.rst import warnings 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 numpy import ndarray from scipy.integrate import trapezoid from scipy.interpolate import RectBivariateSpline from specutils import Spectrum1D from specreduce.core import SpecreduceOperation, ImageLike, MaskingOption from specreduce.tracing import Trace, FlatTrace __all__ = ["BoxcarExtract", "HorneExtract", "OptimalExtract"] def _get_boxcar_weights(center, hwidth, npix): """ Compute weights given an aperture center, half width, and number of pixels. Based on `get_boxcar_weights()` from a JDAT Notebook by Karl Gordon: https://github.com/spacetelescope/jdat_notebooks/blob/main/notebooks/MIRI_LRS_spectral_extraction/miri_lrs_spectral_extraction.ipynb Parameters ---------- center : float, required The index of the aperture's center pixel on the larger image's cross-dispersion axis. hwidth : float, required Half of the aperture's width in the cross-dispersion direction. npix : float, required The number of pixels in the larger image's cross-dispersion axis. Returns ------- weights : `~numpy.ndarray` A 2D image with weights assigned to pixels that fall within the defined aperture. """ weights = np.zeros((npix)) if hwidth == 0: # the logic below would return all zeros anyways, so might as well save the time # (negative widths should be avoided by earlier logic!) return weights if center - hwidth > npix - 0.5 or center + hwidth < -0.5: # entire window is out-of-bounds return weights lower_edge = max(-0.5, center - hwidth) # where -0.5 is lower bound of the image upper_edge = min(center + hwidth, npix - 0.5) # where npix-0.5 is upper bound of the image # let's avoid recomputing the round repeatedly int_round_lower_edge = int(round(lower_edge)) int_round_upper_edge = int(round(upper_edge)) # inner pixels that get full weight # the round in conjunction with the +1 handles the half-pixel "offset", # the upper bound doesn't have the +1 because array slicing is inclusive on the lower index and # exclusive on the upper-index # NOTE: round(-0.5) == 0, which is helpful here for the case where lower_edge == -0.5 weights[int_round_lower_edge + 1 : int_round_upper_edge] = 1 # handle edge pixels (for cases where an edge pixel is fully-weighted, this will set it again, # but should still compute a weight of 1. By using N:N+1, we avoid index errors if the edge # is outside the image bounds. But we do need to avoid negative indices which would count # from the end of the array. if int_round_lower_edge >= 0: weights[int_round_lower_edge : int_round_lower_edge + 1] = ( round(lower_edge) + 0.5 - lower_edge ) weights[int_round_upper_edge : int_round_upper_edge + 1] = upper_edge - ( round(upper_edge) - 0.5 ) return weights def _ap_weight_image(trace, width, disp_axis, crossdisp_axis, image_shape): """ Create a weight image that defines the desired extraction aperture. Based on `ap_weight_images()` from a JDAT Notebook by Karl Gordon: https://github.com/spacetelescope/jdat_notebooks/blob/main/notebooks/MIRI_LRS_spectral_extraction/miri_lrs_spectral_extraction.ipynb Parameters ---------- trace : `~specreduce.tracing.Trace`, required trace object width : float, required width of extraction aperture in pixels disp_axis : int, required dispersion axis crossdisp_axis : int, required cross-dispersion axis image_shape : tuple with 2 elements, required size (shape) of image Returns ------- wimage : `~numpy.ndarray` a 2D weight image defining the aperture """ wimage = np.zeros(image_shape) hwidth = 0.5 * width image_sizes = image_shape[crossdisp_axis] # loop in dispersion direction and compute weights. for i in range(image_shape[disp_axis]): # TODO trace must handle transposed data (disp_axis == 0) # pass trace.trace.data[i] to avoid any mask if part of the regions is out-of-bounds # ArrayTrace can have nonfinite or masked data in trace, and this will fail, # so figure out how to handle that... wimage[:, i] = _get_boxcar_weights(trace.trace.data[i], hwidth, image_sizes) return wimage @dataclass class BoxcarExtract(SpecreduceOperation): """ Standard boxcar extraction along a trace. Example: :: trace = FlatTrace(image, trace_pos) extract = BoxcarExtract(image, trace) spectrum = extract(width=width) Parameters ---------- image image with 2-D spectral image data trace_object trace object width width of extraction aperture in pixels disp_axis dispersion axis crossdisp_axis cross-dispersion axis mask_treatment Specifies how to handle masked or non-finite values in the input image. The accepted values are: - ``apply``: The image remains unchanged, and any existing mask is combined\ with a mask derived from non-finite values. - ``ignore``: The image remains unchanged, and any existing mask is dropped. - ``propagate``: The image remains unchanged, and any masked or non-finite pixel\ causes the mask to extend across the entire cross-dispersion axis. - ``zero_fill``: Pixels that are either masked or non-finite are replaced with 0.0,\ and the mask is dropped. - ``nan_fill``: Pixels that are either masked or non-finite are replaced with nan,\ and the mask is dropped. - ``apply_mask_only``: The image and mask are left unmodified. - ``apply_nan_only``: The image is left unmodified, the old mask is dropped, and a\ new mask is created based on non-finite values. Returns ------- spec : `~specutils.Spectrum1D` The extracted 1d spectrum expressed in DN and pixel units """ image: ImageLike trace_object: Trace width: float = 5 disp_axis: int = 1 crossdisp_axis: int = 0 # TODO: should disp_axis and crossdisp_axis be defined in the Trace object? mask_treatment: MaskingOption = "apply" _valid_mask_treatment_methods = ( "apply", "ignore", "propagate", "zero_fill", "nan_fill", "apply_mask_only", "apply_nan_only", ) @property def spectrum(self): return self.__call__() def __call__( self, image: ImageLike | None = None, trace: Trace | None = None, width: float | None = None, disp_axis: int | None = None, crossdisp_axis: int | None = None, ) -> Spectrum1D: """ Extract the 1D spectrum using the boxcar method. Parameters ---------- image The image with 2-D spectral image data trace The trace object width The width of extraction aperture in pixels disp_axis The dispersion axis crossdisp_axis The cross-dispersion axis Returns ------- spec The extracted 1d spectrum with flux expressed in the same units as the input image, or u.DN, and pixel units """ image = image if image is not None else self.image trace = trace or self.trace_object width = width or self.width disp_axis = disp_axis or self.disp_axis cdisp_axis = crossdisp_axis or self.crossdisp_axis if width <= 0: raise ValueError("The window width must be positive") self.image = self._parse_image( image, disp_axis=disp_axis, mask_treatment=self.mask_treatment ) # Spectrum extraction # =================== # Assign no weight to non-finite pixels outside the window. Non-finite pixels inside # the window will be propagated to the sum if mask treatment is either ``ignore`` or # ``propagate`` or excluded if the chosen mask treatment option is ``apply``. In the # latter case, the flux is calculated as the average of the non-masked pixels inside # the window multiplied by the window width. window_weights = _ap_weight_image(trace, width, disp_axis, cdisp_axis, self.image.shape) if self.mask_treatment == "apply": image_cleaned = np.where(~self.image.mask, self.image.data * window_weights, 0.0) weights = np.where(~self.image.mask, window_weights, 0.0) spectrum = ( image_cleaned.sum(axis=cdisp_axis) / weights.sum(axis=cdisp_axis) * window_weights.sum(axis=cdisp_axis) ) else: image_windowed = np.where(window_weights, self.image.data * window_weights, 0.0) spectrum = np.sum(image_windowed, axis=cdisp_axis) return Spectrum1D(spectrum * self.image.unit, spectral_axis=self.image.spectral_axis) @dataclass class HorneExtract(SpecreduceOperation): """ Perform a Horne (a.k.a. optimal) extraction on a two-dimensional spectrum. There are two options for fitting the spatial profile used for extraction - by default, a 1D gaussian is fit and as a uniform profile across the spectrum. Alternativley, the ``self profile`` option may be chosen - when this option is chosen, the spatial profile will be sampled (using a default of 10 sample bins, but can be modified with ``spatial_profile``) and interpolated between to produce a smoothly varying spatial profile across the spectrum. If using the Gaussian option for the spatial profile, a background profile may be fit (but not subtracted) simultaneously to the data. By default, this is done with a 2nd degree polynomial. If using the ``interpolated_profile`` option, the background model must be set to None. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, required The input 2D spectrum from which to extract a source. An NDData object must specify uncertainty and a mask. An array requires use of the ``variance``, ``mask``, & ``unit`` arguments. trace_object : `~specreduce.tracing.Trace`, required The associated 1D trace object created for the 2D image. disp_axis : int, optional The index of the image's dispersion axis. [default: 1] crossdisp_axis : int, optional The index of the image's cross-dispersion axis. [default: 0] bkgrd_prof : `~astropy.modeling.Model` or None, optional A model for the image's background flux when using the ``gaussian`` spatial profile. If ``spatial_profile`` is set to ``gaussian``, it defaults to ``models.Polynomial1D(2)``. Note that the ``interpolated_profile`` option does not support a background model, so ``bkgrd_prof`` must be left as ``None``. spatial_profile : str or dict, optional The shape of the object profile. The first option is 'gaussian' to fit a uniform 1D gaussian to the average of pixels in the cross-dispersion direction. The other option is 'interpolated_profile' - when this option is used, the profile is sampled in bins and these samples are interpolated between to construct a continuously varying, empirical spatial profile for extraction. For this option, if passed in as a string (i.e spatial_profile='interpolated_profile') the default values for the number of bins used (10) and degree of interpolation (linear in x and y, by default) will be used. To set these parameters, pass in a dictionary with the keys 'n_bins_interpolated_profile' (which accepts an integer number of bins) and 'interp_degree' (which accepts an int, or tuple of ints for x and y degree, respectively). [default: gaussian] variance : `~numpy.ndarray`, optional (Only used if ``image`` is not an NDData object.) The associated variances for each pixel in the image. Must have the same dimensions as ``image``. If all zeros, the variance will be ignored and treated as all ones. If any zeros, those elements will be excluded via masking. If any negative values, an error will be raised. [default: None] mask : `~numpy.ndarray`, optional (Only used if ``image`` is not an NDData object.) Whether to mask each pixel in the image. Must have the same dimensions as ``image``. If blank, all non-NaN pixels are unmasked. [default: None] unit : `~astropy.units.Unit` or str, optional (Only used if ``image`` is not an NDData object.) The associated unit for the data in ``image``. If blank, fluxes are interpreted in DN. [default: None] """ image: NDData trace_object: Trace bkgrd_prof: None | Model = None spatial_profile: str | dict = "gaussian" variance: np.ndarray = field(default=None) mask: np.ndarray = field(default=None) unit: np.ndarray = field(default=None) disp_axis: int = 1 crossdisp_axis: int = 0 # TODO: should disp_axis and crossdisp_axis be defined in the Trace object? @property def spectrum(self): return self.__call__() def _parse_image(self, image, variance=None, mask=None, unit=None, disp_axis=1): """ Convert all accepted image types to a consistently formatted Spectrum1D object. HorneExtract needs its own version of this method because it is more stringent in its requirements for input images. The extra arguments are needed to handle cases where these parameters were specified as arguments and those where they came as attributes of the image object. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, required The image to be parsed. If None, defaults to class' own image attribute. variance : `~numpy.ndarray`, optional (Only used if ``image`` is not an NDData object.) The associated variances for each pixel in the image. Must have the same dimensions as ``image``. If all zeros, the variance will be ignored and treated as all ones. If any zeros, those elements will be excluded via masking. If any negative values, an error will be raised. mask : `~numpy.ndarray`, optional (Only used if ``image`` is not an NDData object.) Whether to mask each pixel in the image. Must have the same dimensions as ``image``. If blank, all non-NaN pixels are unmasked. unit : `~astropy.units.Unit` or str, optional (Only used if ``image`` is not an NDData object.) The associated unit for the data in ``image``. If blank, fluxes are interpreted in DN. disp_axis : int, optional The index of the image's dispersion axis. Should not be changed until operations can handle variable image orientations. [default: 1] """ if isinstance(image, np.ndarray): img = image elif isinstance(image, u.quantity.Quantity): img = image.value else: # NDData, including CCDData and Spectrum1D img = image.data # mask is set as None when not specified upon creating a Spectrum1D # object, so we must check whether it is absent *and* whether it's # present but set as None if getattr(image, "mask", None) is not None: mask = image.mask elif mask is not None: pass else: # if user provides no mask at all, don't mask anywhere mask = np.zeros_like(img) if img.shape != mask.shape: raise ValueError("image and mask shapes must match.") # Process uncertainties, converting to variances when able and throwing # an error when uncertainties are missing or less easily converted if hasattr(image, "uncertainty") and image.uncertainty is not None: if image.uncertainty.uncertainty_type == "var": variance = image.uncertainty.array elif image.uncertainty.uncertainty_type == "std": warnings.warn( "image NDData object's uncertainty " "interpreted as standard deviation. if " "incorrect, use VarianceUncertainty when " "assigning image object's uncertainty." ) variance = image.uncertainty.array**2 elif image.uncertainty.uncertainty_type == "ivar": variance = 1 / image.uncertainty.array else: # other options are InverseUncertainty and UnknownUncertainty raise ValueError( "image NDData object has unexpected " "uncertainty type. instead, try " "VarianceUncertainty or StdDevUncertainty." ) elif hasattr(image, "uncertainty") and image.uncertainty is None: # ignore variance arg to focus on updating NDData object raise ValueError("image NDData object lacks uncertainty") else: if variance is None: raise ValueError( "if image is a numpy or Quantity array, a " "variance must be specified. consider " "wrapping it into one object by instead " "passing an NDData image." ) elif image.shape != variance.shape: raise ValueError("image and variance shapes must match") if np.any(variance < 0): raise ValueError("variance must be fully positive") if np.all(variance == 0): # technically would result in infinities, but since they're all # zeros, we can override ones to simulate an unweighted case variance = np.ones_like(variance) if np.any(variance == 0): # exclude such elements by editing the input mask mask[variance == 0] = True # replace the variances to avoid a divide by zero warning variance[variance == 0] = np.nan variance = VarianceUncertainty(variance) unit = getattr(image, "unit", u.Unit(unit) if unit is not None else u.Unit("DN")) spectral_axis = getattr(image, "spectral_axis", np.arange(img.shape[disp_axis]) * u.pix) return Spectrum1D(img * unit, spectral_axis=spectral_axis, uncertainty=variance, mask=mask) def _fit_gaussian_spatial_profile( self, img: ndarray, disp_axis: int, crossdisp_axis: int, or_mask: ndarray, bkgrd_prof: Model ): """Fit a 1D Gaussian spatial profile to spectrum in `img`. Fits an 1D Gaussian profile to spectrum in `img`. Takes the weighted mean of ``img`` along the cross-dispersion axis all ignoring masked pixels (i.e, takes the mean of each row for a horizontal trace). A Background model (optional) is fit simultaneously. Returns an `astropy.model.Gaussian1D` (or compound model, if `bkgrd_prof` is supplied) fit to data. """ nrows = img.shape[crossdisp_axis] xd_pixels = np.arange(nrows) # co-add signal in each image row, ignore masked pixels coadd = np.ma.masked_array(img, mask=or_mask).mean(disp_axis) # use the sum of brightest row as an inital guess for Gaussian amplitude, # the the location of the brightest row as an initial guess for the mean gauss_prof = models.Gaussian1D(amplitude=coadd.max(), mean=coadd.argmax(), stddev=2) # Fit extraction kernel (Gaussian + background model) to coadded rows # with combined model (must exclude masked indices manually; # LevMarLSQFitter does not) if bkgrd_prof is not None: ext_prof = gauss_prof + bkgrd_prof else: # add a trivial constant model so attribute names are the same ext_prof = gauss_prof + models.Const1D(0, fixed={"amplitude": True}) with warnings.catch_warnings(): warnings.simplefilter("ignore") fitter = fitting.LMLSQFitter() fit_ext_kernel = fitter(ext_prof, xd_pixels[~coadd.mask], coadd.compressed()) return fit_ext_kernel def _fit_spatial_profile( self, img: ndarray, disp_axis: int, crossdisp_axis: int, mask: ndarray, n_bins: int, kx: int, ky: int, ) -> RectBivariateSpline: """ Fit a spatial profile by sampling the median profile along the dispersion direction. This method extracts the spatial profile from an input spectrum by binning the data along the dispersion axis. It calculates the median profile for each bin, normalizes it, and then interpolates between these profiles to create a smooth 2D representation of the spatial profile. The resulting interpolator object can be used to evaluate the spatial profile at any coordinate within the bounds of the data. Parameters ---------- img The 2D array of spectral data to process. disp_axis The image axis corresponding to the dispersion direction. crossdisp_axis The image axis corresponding to the cross-dispersion direction. mask A boolean mask array with the same shape as the image. Values of ``True`` in the mask indicate invalid data points to be ignored during computation. n_bins The number of bins to use along the dispersion axis for sampling the median spatial profile. kx The degree of the spline along the dispersion axis. ky The degree of the spline along the cross-dispersion axis. Returns ------- RectBivariateSpline Interpolator object that provides a smoothed 2D spatial profile. """ img = np.where(~mask, img, np.nan) nrows = img.shape[crossdisp_axis] ncols = img.shape[disp_axis] samples = np.zeros((n_bins, nrows)) sample_locs = np.linspace(0, ncols - 1, n_bins + 1, dtype=int) bin_centers = [ (sample_locs[i] + sample_locs[i + 1]) // 2 for i in range(len(sample_locs) - 1) ] for i in range(n_bins): bin_median = np.nanmedian(img[:, sample_locs[i] : sample_locs[i + 1]], axis=disp_axis) samples[i, :] = bin_median / bin_median.sum() return RectBivariateSpline(x=bin_centers, y=np.arange(nrows), z=samples, kx=kx, ky=ky) def __call__( self, image=None, trace_object=None, disp_axis=None, crossdisp_axis=None, bkgrd_prof=None, spatial_profile=None, n_bins_interpolated_profile=None, interp_degree_interpolated_profile=None, variance=None, mask=None, unit=None, ): """ Run the Horne calculation on a region of an image and extract a 1D spectrum. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, required The input 2D spectrum from which to extract a source. An NDData object must specify uncertainty and a mask. An array requires use of the ``variance``, ``mask``, & ``unit`` arguments. trace_object : `~specreduce.tracing.Trace`, required The associated 1D trace object created for the 2D image. disp_axis : int, optional The index of the image's dispersion axis. crossdisp_axis : int, optional The index of the image's cross-dispersion axis. bkgrd_prof : `~astropy.modeling.Model`, optional A model for the image's background flux when using the ``gaussian`` spatial profile. If ``spatial_profile`` is set to ``gaussian``, it defaults to ``models.Polynomial1D(2)``. Note that the ``interpolated_profile`` option does not support a background model, so ``bkgrd_prof`` must be left as ``None``. spatial_profile : str or dict, optional The shape of the object profile. The first option is 'gaussian' to fit a uniform 1D gaussian to the average of pixels in the cross-dispersion direction. The other option is 'interpolated_profile' - when this option is used, the profile is sampled in bins and these samples are interpolated between to construct a continuously varying, empirical spatial profile for extraction. For this option, if passed in as a string (i.e spatial_profile='interpolated_profile') the default values for the number of bins used (10) and degree of interpolation (linear in x and y, by default) will be used. To set these parameters, pass in a dictionary with the keys 'n_bins_interpolated_profile' (which accepts an integer number of bins) and 'interp_degree' (which accepts an int, or tuple of ints for x and y degree, respectively). [default: gaussian] variance : `~numpy.ndarray`, optional (Only used if ``image`` is not an NDData object.) The associated variances for each pixel in the image. Must have the same dimensions as ``image``. If all zeros, the variance will be ignored and treated as all ones. If any zeros, those elements will be excluded via masking. If any negative values, an error will be raised. mask : `~numpy.ndarray`, optional (Only used if ``image`` is not an NDData object.) Whether to mask each pixel in the image. Must have the same dimensions as ``image``. If blank, all non-NaN pixels are unmasked. unit : `~astropy.units.Unit` or str, optional (Only used if ``image`` is not an NDData object.) The associated unit for the data in ``image``. If blank, fluxes are interpreted in DN. Returns ------- spec_1d : `~specutils.Spectrum1D` The final, Horne extracted 1D spectrum. """ image = image if image is not None else self.image trace_object = trace_object if trace_object is not None else self.trace_object disp_axis = disp_axis if disp_axis is not None else self.disp_axis crossdisp_axis = crossdisp_axis if crossdisp_axis is not None else self.crossdisp_axis bkgrd_prof = bkgrd_prof if bkgrd_prof is not None else self.bkgrd_prof profile = spatial_profile if spatial_profile is not None else self.spatial_profile variance = variance if variance is not None else self.variance mask = mask if mask is not None else self.mask unit = unit if unit is not None else self.unit profile_choices = ("gaussian", "interpolated_profile") if not isinstance(profile, (str, dict)): raise ValueError("spatial_profile must be a string or dictionary.") if isinstance(profile, str): profile = dict(name=profile) profile_type = profile["name"].lower() if profile_type not in profile_choices: raise ValueError("spatial_profile must be one of" f"{', '.join(profile_choices)}") n_bins_interpolated_profile = profile.get("n_bins_interpolated_profile", 10) interp_degree_interpolated_profile = profile.get("interp_degree_interpolated_profile", 1) if bkgrd_prof is None and profile_type == 'gaussian': bkgrd_prof = models.Polynomial1D(2) self.image = self._parse_image(image, variance, mask, unit, disp_axis) variance = self.image.uncertainty.represent_as(VarianceUncertainty).array mask = self.image.mask.astype(bool) | (~np.isfinite(self.image.data)) unit = self.image.unit img = self.image.data ncross = img.shape[crossdisp_axis] ndisp = img.shape[disp_axis] # If the trace is not flat, shift the rows in each column # so the image is aligned along the trace: if not isinstance(trace_object, FlatTrace): img = _align_along_trace( img, trace_object.trace, disp_axis=disp_axis, crossdisp_axis=crossdisp_axis ) if profile_type == "gaussian": fit_ext_kernel = self._fit_gaussian_spatial_profile( img, disp_axis, crossdisp_axis, mask, bkgrd_prof ) if isinstance(trace_object, FlatTrace): mean_cross_pix = trace_object.trace else: mean_cross_pix = np.broadcast_to(ncross // 2, ndisp) else: # interpolated_profile # determine interpolation degree from input and make tuple if int # this can also be moved to another method to parse the input # 'spatial_profile' arg, eventually if isinstance(interp_degree_interpolated_profile, int): kx = ky = interp_degree_interpolated_profile else: # if input is tuple of ints if not isinstance(interp_degree_interpolated_profile, tuple): raise ValueError( "``interp_degree_interpolated_profile`` must be ", "an integer or tuple of integers.", ) if not all(isinstance(x, int) for x in interp_degree_interpolated_profile): raise ValueError( "``interp_degree_interpolated_profile`` must be ", "an integer or tuple of integers.", ) kx, ky = interp_degree_interpolated_profile interp_spatial_prof = self._fit_spatial_profile( img, disp_axis, crossdisp_axis, mask, n_bins_interpolated_profile, kx, ky ) # add private attribute to save fit profile. should this be public? self._interp_spatial_prof = interp_spatial_prof xd_pixels = np.arange(ncross) kernel_vals = np.zeros(img.shape) norms = np.full(ndisp, np.nan) valid = ~mask if profile_type == "gaussian": norms[:] = fit_ext_kernel.amplitude_0 * fit_ext_kernel.stddev_0 * np.sqrt(2 * np.pi) for idisp in range(ndisp): if not np.any(valid[:, idisp]): continue if profile_type == "gaussian": fit_ext_kernel.mean_0 = mean_cross_pix[idisp] fitted_col = fit_ext_kernel(xd_pixels) kernel_vals[:, idisp] = fitted_col else: fitted_col = interp_spatial_prof(idisp, xd_pixels) kernel_vals[:, idisp] = fitted_col norms[idisp] = trapezoid(fitted_col, dx=1)[0] with np.errstate(divide="ignore", invalid="ignore"): num = np.sum(np.where(valid, img * kernel_vals / variance, 0.0), axis=crossdisp_axis) den = np.sum(np.where(valid, kernel_vals**2 / variance, 0.0), axis=crossdisp_axis) extraction = (num / den) * norms return Spectrum1D(extraction * unit, spectral_axis=self.image.spectral_axis) def _align_along_trace(img, trace_array, disp_axis=1, crossdisp_axis=0): """ Given an arbitrary trace ``trace_array`` (an np.ndarray), roll all columns of ``nddata`` to shift the NDData's pixels nearest to the trace to the center of the spatial dimension of the NDData. """ # TODO: this workflow does not support extraction for >2D spectra if not (disp_axis == 1 and crossdisp_axis == 0): # take the transpose to ensure the rows are the cross-disp axis: img = img.T n_rows, n_cols = img.shape # indices of all columns, in their original order rows = np.broadcast_to(np.arange(n_rows)[:, None], img.shape) cols = np.broadcast_to(np.arange(n_cols), img.shape) # we want to "roll" each column so that the trace sits in # the central row of the final image shifts = trace_array.astype(int) - n_rows // 2 # we wrap the indices so we don't index out of bounds shifted_rows = np.mod(rows + shifts[None, :], n_rows) return img[shifted_rows, cols] @dataclass class OptimalExtract(HorneExtract): """ An alias for `HorneExtract`. """ __doc__ += HorneExtract.__doc__ pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/fluxcal.py0000644000175100001660000002707214764317313017416 0ustar00runnerdockerimport os import numpy as np from astropy import units as u from astropy.constants import c as cc from astropy.table import Table from scipy.interpolate import UnivariateSpline from specutils import Spectrum1D from specreduce.core import SpecreduceOperation __all__ = ['FluxCalibration'] class FluxCalibration(SpecreduceOperation): """ Carries out routine flux calibration operations. Parameters ---------- object_spectrum : a Spectrum1D object The observed object spectrum to apply the sensfunc to, with the wavelength of the data points in Angstroms as the ``spectral_axis``, and the magnitudes of the data as the ``flux``. airmass : float The value of the airmass. Note: NOT the header keyword. zeropoint : float, optional Conversion factor for mag->flux. (Default is 48.60). """ def __call__(self, object_spectrum, airmass=1.00, zeropoint=48.60): self.object_spectrum = object_spectrum self.airmass = airmass self.zeropoint = zeropoint def mag2flux(self, spec_in=None): """ Convert magnitudes to flux units. This is important for dealing with standards and files from IRAF, which are stored in AB mag units. To be clear, this converts to "PHOTFLAM" units in IRAF-speak. Assumes the common flux zeropoint used in IRAF. Parameters ---------- spec_in: a Spectrum1D object, optional An input spectrum with wavelength of the data points in Angstroms as the ``spectral_axis`` and magnitudes of the data as the ``flux``. Returns ------- spec_out: specutils.Spectrum1D Containing both ``flux`` and ``spectral_axis`` data in which the ``flux`` has been properly converted from mag->flux. """ if spec_in is None: spec_in = self.object_spectrum lamb = spec_in.spectral_axis mag = spec_in.flux flux = (10.0**((mag + self.zeropt) / (-2.5))) * (cc.to('AA/s').value / lamb ** 2.0) flux = flux * u.erg / u.s / u.angstrom / (u.cm * u.cm) spec_out = Spectrum1D(spectral_axis=lamb, flux=flux) return spec_out @staticmethod def obs_extinction(obs_file): """ Load the observatory-specific airmass extinction file from the supplied library Parameters ---------- obs_file : str, {'apoextinct.dat', 'ctioextinct.dat', 'kpnoextinct.dat', 'ormextinct.dat'} The observatory-specific airmass extinction file. If not known for your observatory, use one of the provided files (e.g. `kpnoextinct.dat`). Following IRAF standard, extinction files have 2-column format wavelength (Angstroms), Extinction (Mag per Airmass) Returns ------- Xfile: an Astropy table Table with the observatory extinction data """ if len(obs_file) == 0: raise ValueError('Must select an observatory extinction file.') dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'datasets', 'extinction') if not os.path.isfile(os.path.join(dir, obs_file)): msg = "No valid standard star found at: " + os.path.join(dir, obs_file) raise ValueError(msg) # To read in the airmass extinction curve Xfile = Table.read(os.path.join(dir, obs_file), format='ascii', names=('wave', 'X')) Xfile['wave'].unit = 'AA' return Xfile def airmass_cor(self, Xfile): """ Correct the spectrum based on the airmass. Requires observatory extinction file. Parameters ---------- Xfile : astropy.table.Table The extinction table from `obs_extinction`, with columns ('wave', 'X') that have standard units of: (angstroms, mag/airmass). Returns ------- airmass_cor_spec: specutils.Spectrum1D The airmass-corrected Spectrum1D object. """ object_spectrum = self.mag2flux() airmass = self.airmass obj_wave, obj_flux = object_spectrum.spectral_axis, object_spectrum.flux # linear interpol airmass extinction onto observed wavelengths new_X = np.interp(obj_wave.value, Xfile['wave'], Xfile['X']) # air_cor in units of mag/airmass, convert to flux/airmass airmass_ext = 10.0**(0.4 * airmass * new_X) airmass_cor_spec = Spectrum1D(flux=obj_flux * airmass_ext, spectral_axis=obj_wave) return airmass_cor_spec def onedstd(self, stdstar): """ Load the onedstd from the supplied library. Parameters ---------- stdstar : str Name of the standard star file in the specreduce/datasets/onedstds directory to be used for the flux calibration. The user must provide the subdirectory and file name. For example: >>> 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. Returns ------- 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') if not os.path.isfile(os.path.join(std_dir, stdstar)): msg = "No valid standard star found at: " + os.path.join(std_dir, stdstar) raise ValueError(msg) standard = Table.read(os.path.join(std_dir, stdstar), format='ascii', names=('wave', 'mag', 'width')) standard['wave'].unit = u.angstrom standard['width'].unit = u.angstrom # Standard star spectrum is stored in magnitude units (IRAF conventions) std_flux = self.mag2flux(spec_in=Spectrum1D(flux=standard['mag'], spectral_axis=standard['wave'])) std_flux = std_flux.flux standard['mag'].unit = u.mag standard.add_column(std_flux, name='flux') return standard def standard_sensfunc(self, standard, mode='linear', polydeg=9, badlines=[6563, 4861, 4341], display=False): """ Compute the standard star sensitivity function. Parameters ---------- standard : astropy.table.Table output from ``onedstd``, has columns ('wave', 'width', 'mag', 'flux'). mode : str, optional Can be "linear", "spline", or "poly" (Default is linear). polydeg : float, optional if mode='poly', this is the order of the polynomial to fit through. (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. Returns ------- sensfunc_spec : specutils.Spectrum1D The sensitivity function in the covered wavelength range for the given standard star. """ spec = self.mag2flux() obj_wave, obj_flux = spec.spectral_axis, spec.flux # Automatically exclude some lines b/c resolution dependent response badlines = np.array(badlines, dtype='float') # Balmer lines # Down-sample (ds) the observed flux to the standard's bins obj_flux_ds = np.array([], dtype=np.float) obj_wave_ds = np.array([], dtype=np.float) std_flux_ds = np.array([], dtype=np.float) for i in range(len(standard['flux'])): rng = np.where((obj_wave.value >= standard['wave'][i] - standard['width'][i] / 2.0) & (obj_wave.value < standard['wave'][i] + standard['width'][i] / 2.0))[0] IsH = np.where((badlines >= standard['wave'][i] - standard['width'][i] / 2.0) & (badlines < standard['wave'][i] + standard['width'][i] / 2.0))[0] # Does this bin contain observed spectra, and no Balmer lines? if (len(rng) > 1) and (len(IsH) == 0): obj_flux_ds = np.append(obj_flux_ds, np.nanmean(obj_flux.value[rng])) obj_wave_ds = np.append(obj_wave_ds, standard['wave'][i]) std_flux_ds = np.append(std_flux_ds, standard['flux'][i]) # the ratio between the standard star catalog flux and observed flux ratio = np.abs(std_flux_ds / obj_flux_ds) # The actual fit the log of this sensfunc ratio # Since IRAF does the 2.5*log(ratio), everything would be in mag units LogSensfunc = np.log10(ratio) # If invalid interpolation mode selected, make it spline if mode.lower() not in ('linear', 'spline', 'poly'): mode = 'spline' import warnings warnings.warn("WARNING: invalid mode set. Changing to default mode 'spline'") # Interpolate the calibration (sensfunc) on to observed wavelength grid if mode.lower() == 'linear': sensfunc2 = np.interp(obj_wave.value, obj_wave_ds, LogSensfunc) elif mode.lower() == 'spline': spl = UnivariateSpline(obj_wave_ds, LogSensfunc, ext=0, k=2, s=0.0025) sensfunc2 = spl(obj_wave.value) elif mode.lower() == 'poly': fit = np.polyfit(obj_wave_ds, LogSensfunc, polydeg) sensfunc2 = np.polyval(fit, obj_wave.value) sensfunc_out = (10 ** sensfunc2) * standard['flux'].unit / obj_flux.unit 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) # plt.scatter(standard['wave'], std_flux, color='C1', alpha=0.75, label="stdstar") plt.scatter(obj_wave_ds, std_flux_ds, color='C1', alpha=0.75) plt.xlabel("Wavelength") plt.ylabel("Flux") plt.xlim(np.nanmin(obj_wave.value), np.nanmax(obj_wave.value)) plt.ylim(np.nanmin(obj_flux.value * sensfunc_out.value) * 0.98, np.nanmax(obj_flux.value * sensfunc_out.value) * 1.02) # plt.legend() plt.show() return sensfunc_spec def apply_sensfunc(self, sensfunc): """ Apply the derived sensitivity function, converts observed units (e.g. ADU/s) to physical units (e.g. erg/s/cm2/A). Sensitivity function is first linearly interpolated onto the wavelength scale of the observed data, and then directly multiplied. Parameters ---------- sensfunc : astropy.table.Table The output of ``standard_sensfunc``, table has columns ('wave', 'S'). Returns ------- fluxcal_spec: specutils.Spectrum1D The sensfunc corrected ``Spectrum1D`` object. """ spec = self.mag2flux() obj_wave, obj_flux = spec.spectral_axis, spec.flux # Sort, in case the sensfunc wavelength axis is backwards ss = np.argsort(obj_wave.value) # Interpolate the sensfunc onto the observed wavelength axis sensfunc2 = np.interp(obj_wave.value, sensfunc['wave'][ss], sensfunc['S'][ss]) object_spectrum = obj_flux * (sensfunc2 * sensfunc['S'].unit) fluxcal_spec = Spectrum1D(spectral_axis=obj_wave, flux=object_spectrum) return fluxcal_spec ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/line_matching.py0000644000175100001660000001062714764317313020557 0ustar00runnerdockerfrom typing import Sequence import warnings import numpy as np import astropy.units as u from astropy.stats import gaussian_fwhm_to_sigma, gaussian_sigma_to_fwhm from astropy.modeling import models from astropy.table import QTable from astropy.wcs import WCS as astropy_WCS from gwcs.wcs import WCS as gWCS from specutils import Spectrum1D from specutils.fitting import find_lines_threshold, fit_lines __all__ = [ "find_arc_lines", "match_lines_wcs" ] def find_arc_lines( spectrum: Spectrum1D, fwhm: float | u.Quantity = 5.0 * u.pix, window: float = 3.0, noise_factor: float = 5.0 ) -> QTable: """ Find arc lines in a spectrum using `~specutils.fitting.find_lines_threshold` and then perform gaussian fits to each detected line to refine position and FWHM. Parameters ---------- spectrum : The extracted arc spectrum to search for lines. It should be background-subtracted and must have an "uncertainty" attribute. fwhm : Estimated full-width half-maximum of the lines in pixels. window : The window size in units of fwhm to use for the gaussian fits. noise_factor : The factor to multiply the uncertainty by to determine the noise threshold in the `~specutils.fitting.find_lines_threshold` routine. Returns ------- QTable A table of detected arc lines and their properties: centroid, fwhm, and amplitude. """ # If fwhm is a float, convert it to a Quantity with the same unit as the spectral axis # of the input spectrum. if not isinstance(fwhm, u.Quantity): fwhm *= spectrum.spectral_axis.unit if fwhm.unit != spectrum.spectral_axis.unit: raise ValueError("fwhm must have the same units as spectrum.spectral_axis.") detected_lines = find_lines_threshold(spectrum, noise_factor=noise_factor) detected_lines = detected_lines[detected_lines['line_type'] == 'emission'] centroids = [] widths = [] amplitudes = [] for r in detected_lines: g_init = models.Gaussian1D( amplitude=spectrum.flux[r['line_center_index']], mean=r['line_center'], stddev=fwhm * gaussian_fwhm_to_sigma ) g_fit = fit_lines(spectrum, g_init, window=window * fwhm) centroids.append(g_fit.mean.value * g_fit.mean.unit) widths.append(g_fit.stddev * gaussian_sigma_to_fwhm) amplitudes.append(g_fit.amplitude.value * g_fit.amplitude.unit) line_table = QTable() line_table['centroid'] = centroids line_table['fwhm'] = widths line_table['amplitude'] = amplitudes return line_table def match_lines_wcs( pixel_positions: Sequence[float], catalog_wavelengths: Sequence[float], spectral_wcs: gWCS | astropy_WCS, tolerance: float = 5.0, ) -> QTable: """ Use an input spectral WCS to match lines in an extracted spectrum to a catalog of known lines. Create matched table of pixel/wavelength positions for lines within a given tolerance of their WCS-predicted positions. Parameters ---------- pixel_positions : The pixel positions of the lines in the calibration spectrum. catalog_wavelengths : The wavelengths of the lines in the catalog. spectral_wcs : The spectral WCS of the calibration spectrum. tolerance : The matching tolerance in pixels Returns ------- QTable A table of the matched lines and their pixel/wavelength positions. """ # This routine uses numpy broadcasting which doesn't always behave with Quantity objects. # Pull out the np.ndarray values to avoid those issues. if isinstance(pixel_positions, u.Quantity): pixel_positions = pixel_positions.value # Extra sanity handling to make sure the input Sequence can be converted to an np.array try: pixel_positions = np.array(pixel_positions, dtype=float) except ValueError as e: raise ValueError(f"pixel_positions must be convertable to np.array with dtype=float: {e}") catalog_pixels = spectral_wcs.world_to_pixel(catalog_wavelengths) separations = pixel_positions[:, np.newaxis] - catalog_pixels matched_loc = np.where(np.abs(separations) < tolerance) matched_table = QTable() matched_table["pixel_center"] = pixel_positions[matched_loc[0]] * u.pix matched_table["wavelength"] = catalog_wavelengths[matched_loc[1]] if len(matched_table) == 0: warnings.warn("No lines matched within the given tolerance.") return matched_table ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/table_utils.py0000644000175100001660000000320114764317313020253 0ustar00runnerdocker"""Utility functions to parse main NIST table.""" import numpy as np from astropy.table import Table, vstack __all__ = [] def sort_table_by_element(table, elem_list): """Build table based on list of elements Parameters ---------- table: astropy table Table to sort elem_list: list list of strings to sort table by Returns ------- element_filtered_table: astropytable Filtered table based on inputs """ filtered_table_list = [table[np.where(table['Element'] == elem)] for elem in elem_list] element_filtered_table = vstack(filtered_table_list) return element_filtered_table def sort_table_by_wavelength(table, min_wave, max_wave): """Build table off of wavelength ranges Parameters ---------- min_wave: float Lower bound wavelength to filter on max_wave: float Upper bound wavelength to filter on Returns ------- wave_filtered_table: astropytable Filtered table based on inputs """ assert min_wave < max_wave, "Minimum wavelength greater than maximum wavelength." wave_filtered_table = table[ np.where( (table['Wavelength'] >= min_wave) & (table['Wavelength'] <= max_wave) ) ] return wave_filtered_table def main(): """A little example. """ t = Table.read('data/line_lists/NIST/NIST_combined.csv', format='csv') elements = ['He I', 'Ne I', 'Ar I'] sorted_by_elem = sort_table_by_element(t, elements) sorted_by_wave = sort_table_by_wavelength(t, 2000, 3000) print(sorted_by_wave) print(sorted_by_elem) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.727497 specreduce-1.5.1/specreduce/tests/0000755000175100001660000000000014764317326016544 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/__init__.py0000644000175100001660000000017114764317313020650 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This packages contains affiliated package tests. """ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.727497 specreduce-1.5.1/specreduce/tests/data/0000755000175100001660000000000014764317326017455 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits0000644000175100001660000021450014764317313031126 0ustar00runnerdockerSIMPLE = T / conforms to FITS standard BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 387 NAXIS2 = 44 END ACV^߿V^j꿯ypyt\w0BlA?BrB6G.A*h8§_;$E[B5M>B0=U¾­XB]A%N*nBA@: `86'‡CCnյ8,nTi2 )¦ºrºyB njˆ?b·yBm/IBdҿ>xUhz‚X ]\ŽÄq9T 3(iҕ:L°—zlV])}t”ƒY)B~n@AxZGUxɪCڌopLBNb@€A?TQAUeL>hšcLdQJ-DlYK/_3A˗7B[~5a@E/ BA@O@֓jV@"0HÞ /0@eDV?D:G|'U}7A_A|0Ě??`eD) v$†k7ҍY/o@js#A&DI2F6dy}RlLBL@axAsA0@d /+~[Jz@5'tsDpX b>*oCV@֦z@rx?g߁{0_|A@M@p?G|0h/'/c [)7y/@!F\?4w@д 7.@Ǣ?>;@5T @s)< ߀`"W4h!w]É H jÉ]»$*BP%:On:±=Mv³p,๭ÓS=ڊ‡?!…D[oL|\3Fq@*oª5V)¾^ FQBBrďq—A+¬ZpW_gxYz±Œr醙AN F§:YGV‰WXX6B݃C? haL#yN3zA Iz ;`2l?iF d—%B4/{D)1CDBah]@Twb‹)?:WG *@3I46/@A=ȂA-)A^gEAGm۵ډi?ʊ..|9d;KmAǣ`hk "A;^ \bc,>}A[A-A/SثR}P; EfVL@FA%.9>/&#eg?2@=@-8 >vhB;vm@DFOY&@U@ӿyr>?8`%`tEAٔC?žA @Ĩ2@&ei1Y@Ʋ7oF?t@.+9';@s@A=*A}[܁\p2Vbߏ>R.&^@g"@?@C>Q@-@ /p@@@=EG.V̬e+6j#>@+S5UOfs7r²Ð%mUµYXA}9BMz²^m-?@!A~WB KnÀ$8©A@rCQ¾VºfAP B%I8ٹ YH¿K9wXPAJ B;h)JsG`'_kBQ8~¡cڷSAKٺB$!pA*5@5BA]!)>B5Ad f֎2Z‚n€ƷA1B>BA")xPA>@BpM#”BB@Ur K/=AA62Yg@hac#?iG#~B2WA #1m !cek@ AKRn [% &eJYb܆A[]uAyW\Abw%vxvADX}")?dA I@|s@ulvA%UA@rD[aADAЕmFAcy[wAX/AAKcln@r/"iG }AH4-r@ K? 1@X@[BU.>@3@Q+@@,@qAٿ@ӹ_AAֻ&q!YO]BA=wIMG>") @ܿBAw@v[6?E賗>NNf#0> @a ~2A >^AcIu1˿?8MZ?1? >ddK?f-;#>2Q?j?y?@'WPA1!Al3-[cW B ,3QgI+C CC«94Cž,@SuUB#[0¾EžGj]w5vž¦ B=%A̫¹d\c ֳc&@9դDg4APBH BD:yfKp,=QA@dwA"#$B|1mQDB_ zBXPAE>AqO0Ae2ר~@lr{@jA"Tۇۿ4ڽWI )~. A ́@ E@ᅬ[_@jlg׫N?G?A9) +AlWA֯$ĀzX@;@ A@vA5I '$ba.U.;@= @(@ѡ{AwARB~d.@Rs>BQAŇ dO@I@A.Aɱ ;v}x/8+XBOo@X8dYMX@>e=6w@}PUfmC@GM 4@si@؅g?0䫭K'EQ?[A*?lbiZw{?'@k=䠙SR?Y9 @NSBN‚J#kBԏv<4C~ OA/k´C#BŸ3{Mªe|C\]@½i]ԮRlݶ"@OBA7w™y۵ w?PR2xBTn &0 iB goj/' { ¥ȅ¦?µ0 FŒB0q&@nkAQ!A[µdQEA>}LFHAQg _Œ™=B%A1B7Z`_`:A,M8OU=S4h@>BdPF~}J&ZG;P3J?-g#P;AQ͵?(٥@Pf&v^^ \r_#s~Al;6>2|Cm`ULbAz8/19*[d,L?Mե78æ*U@H|r@-thZ b*|V&@!Bvqp0܀&%: 0bu@A&AY!qA?ږA?ZV> w(j@ihep2U@ JlAAQG6Aj(P^$]l")?85AAۥSA1i@M=tXH&?ujhE:@ {ӂ\@jR@2@e?̵=9c ۶.B?]c<͵»&GúF#~Ðy-èg;7U|½]5)kD82*“j2&A2e¦S\`AMB1"’O߭– ?9B~ë@7vzvnP_@Br8X!c@C OAAA}BCBFO@A>r‚Ii…}B*O2B>@N KVCXoUs6z™6yS@׎4A?M3 WЩA„BR-ÍBDA|88  |@£J-AӦ\@{6T䋲_΀Ok45s6Y@0+ʖ[HTAEB4dA-PAIbWBl1/A6@pb } s}@#K@ BtfA?ɷ]O B.e˖AyAĚbSd@6j[`^a7A>AJ3φĆ@\@ӈa@3(V@ m:k iXmb@|@m}@@21,$p7wATF@rт67Q} L3~ )\"2A.C_@I9@ў MvU?&@0@E@0+ A=iǿ|`gH'aAjB~ In('濖*?i/?[@'@G* (˩@KƼ4ߎ@U?X_uI?JM;@Z@wA(T͓`y/VۇIFE :?-@B*AmÒn,]ÍnU3u :/"?!'B3]wxB%D|̻¬`AHqAwp^Õ&AfIx",0CXmA3A2J[œ]sºBvAt<b%4B&$6p¿(.rBB4 )U7A=5'„,raKvA:ghÊ'e ¡T1ŸPCĜȓ M‰A@a:£KpA"R3 BA |"LBdB#'SFBqA@AyA&".4G43@kk=CT]BA }WB3Ao]B KAeBe^UBW[l0A C#OA >@U0@9kJ%1S@@zA?xFUHu@?BCI@NȅA4zOz]?Sz*j@ 9ATHKQ@XAgaJApA ?) ~R)}>ABA@6o*@eJ?A,uA9Z07 (zYƖp"6?}AA}@@F +c?3r@I9AT5A]'^,>虠r/?X?&E2JPݺǿA|_DA[1ËA|]l^0Bx DP<%žВ~BC]dCL|Mih/}A6CbCmC4#BgBѠ@Ÿ@›EB …`>%+B' \\Q >&A= BX ŸLG8fM–B\?[Ef*A@p!BSB;6A?A1Bu}AGd…Ac:B,+8TX{>}B yMA]?ڟA>4@W#=A{H@th+o!Yh)X #@~1BRDe!?€Th‡0vEB+ F6s-@=[@MX t RA?@]f`LAAF}O~^ӫM?{ɪBH@_gB)̾yMuGb`?@A A@BxK B<\?"xpԌVL"LcAdr!@LiZ@Sx?AF=khYn ?2Ig$ج>R5nݽ!7@m/P+O헀')ț @}&Ðƨw¬o&CnCl/*Ð@km&#UwTv TrB]DU=’.D:Bi&"VrfAP'"BN$yU¢8&f@·#EPn"0.nA´: (*'C0gBXSCAoZ8?doC5AȂ A§\A*hZ­PWLz#԰p¹kM}n8‹ t[k7B^~ozMOA~c)A JJT1S3CU [iW!Q,e[mÃA($B›71ucBGI3>*c j+"\SKSoW8bp9D@K!Gʮ|-@0Uj(j@tAP!A¬)3FG@ \˾AXAx.?n@KTk2Vǰ0A/AMByr8A#բA"=n@q8k+@:Nsmؔׯj?cGwgp¿A3-~8mC @'hbZ&tmB%m^@l,# \'a= HTE)BQA&{~€?"eF=,Bp€)C^)AGA`Tm||RU+; 緿%~@'θ4<.5yOZEA:fA!Kn(+3*fbԿ" wь{!<@b҇@̘FFh@A0??%&nL?+Bl|u.)?6kgoë?=A;6Aڮ.~AM#=cpBA;]j?xAG[QT)U4 o1?yNB@@?|Gt> _?N6@g@QAm3? ,x?gA2 J˹JAW]"ȝOh>AlSAz8eZn_@nL6ʕh$$kuJ@4Aǘ1?ӓ9@ %8&'ը#U}AR?c?S#߁ ‡&C3ZD §wӦ1 9BaXȢ?UBB4=@BG>B?8 Gf:M¥@ 0BN 1vŒov.B-AxQ?4o A6 B-[uHb JZ’tBY^B7zAt@-XB+0aXB :]A}RBBv ܖA}Ab…B8F2^?²B¤qϾNAUo~uBBtWA:z(`AAḆKB@o?@I|“rʝBD%AH)yj\3bA@z6Z>-lWxT3BJAm A'@jp3A$$Baj nB*p`Aҏ\Aga_:Hdӎ+^A0B+~ @ƤAAZAPNvA@俽΋@8dp=xR@5qALA>iWKJ_*`dc k AWBv%AZQ@H@y[3d$ |۵9FA+^yA a@Jin|V@9-@BAW#@@9f@ˏ@)@2ο#^8S'U@@A !TQ=@#r8@6翩@ʵ@W@H=ʣA/VcAžo͔jn#5|eJJ`yL BgÎcE!NP9 C:l~Kѩ/BԼCBDwFC BÖ|pB՟AA“{N kC-kRJrhl8B5P RA'! qtB0q{@;Ac9$4‹ySAB8Bi5h1#A 7@AgzA7'®)DžAI5nSCPyeUkwA 02BAp1OY"pxA+AAx͇ ciH(w)Enzw@%AAYw*6g*ªpªB #m1^}A2z@ }Z-t+B?2D@Hp4ͤ">y>A]P)xA]uBCָjBA\|~9FAA qm5A>F䬲xY |r&%A@y~k ;b,7@eݿ?.A|AB5@_5?u.?QAKq}A 6Lh?i@p`aASǜ@ТB\*@@}AT lc?6}&:2qAy=^s>'"}XuVF\ =z]@+pNAf@t2]T>ܬA nu."@w_ &K!+@ ş@RиNp#k-@Gd>ygD:λ?@$M;%5b! (YCAB 2A?4 T@7 @G0A@ JE9M8Az?_?L`3x/ku?1U9ègbVAX7ê]]ps:RIr,7`wdÄrA ENB­0>p=µwY~UBBQP’RB±¢kA8&OŠ&![qL ¶†lA-c[āyzB0BQgr§#A•‹`P(BB:@6"kŽl]pdBJ"B@8{hH=8\+{z1?.AuNw^KQ HZi ?PAD"C\5wA›hH .D B ZVU&C.T=CSÂBiܛB!r»4OSB²„iq -1>YhBRt6hXBq0Rx²&—ԘPK?Ϙh#¯£ 2A|AJpBAB:UB?jy@aR\-¼ A/V BJ™8An $MN!HA‚-d1@W+@Cu&6TIÜ@]A\i?,7.@GXB B(@U̇"aS @pFA,iq@P#9 J@A^?@Hl@AJj= @@uB?n:?@.@6@bA[=H6@ں,{l`r<_FN@I˧@`?AA(c3 +sUXR}?d>@ZAHzۥ@@ƈuC~Cɂ?}kBv-C6™7?BlBxB{k«:Loušj`1¡<’^%C7K|YY9["^GDkwaA(A*,?^BCSˆ gBB%v _%y#AޱA+ڤ£[rA@BLABBf|||1/AA @~Bn9A@„doBdrBNl@wA< AVWz9XB'z&h A(A 4;@+`?i2Ar‘(LA"=QBJw##B^[9GA{V3AD@zf[]A)B`@AYB.WW4F7Tr@UοuAyP$%[B9AA4@QWѡAIɿ$`@A@ATA?q( ޻0KvwA(h@*&DiA'Mi@k@1uZ8^?K ?Q.)\k$@>㽺Sw?C@ɗwAKU ?M+Eٚ?@IA9HA9f[?`A UA@ r0zms@T2xW{cz3h?d?Fg6@$,-?.@ִ?_8N ?FH@ƣl"z>#@`;A@M* '@-Ŀ.Rle)/ÐkZ{HBi ;jB drG)""`5BHC' åd@d%nvJZ_s:^=˼°b¯A S!CeZpB[[BB`-A$%Š8B&lfB'NF2A#&B-C( §&~ɟpBB.¢Œ?KAvS"©]dIT_?+`BAjќ„!]d2^VBzt+:;A4J$[BD@D+AU›=h>"qo, iAMAq/ˆg'7)AIE\_1A&u@ :pV›BtS=A-Aj5셿^y{A AF%R#x@DA+(@_!NA~w/7er@{J$A|ZVj@ϲA?Q{)CAAqfY@7 iGۨ@GӘ7@FUwQ#"AFՄG@̯0Ad7UH@@DN@M~F0<<?)Ax@jBj@pA@- q?'72x>ڿ6@ @G?BZ)4Bô-06ѮAc}»B$p)ª&fB<~BPI_WAf"OzGT([G\]BeO?^DFAw]QB7H{AGÁXF"9“Aa¤­O]--Nc96.@cÿ[ 4@SA"{Aq²ž^AAy\B¥S@CpB3 YBbBE1BB|:e‹PAmB*B6 LB%AdA5B%B3,`( B=BkAJѷBE ?gA'B4h)L‡.HA{ZZA0NW.^g@r!A!M* =WkAfк\?2@@6TTtǾAAƿȤ:a /!@ 77T@B @Ǚ{4>wp}ֶB^Z{p@@/黳A͟@ %Z@B+8t@A?IGBJ=@Ȓ?:,scAJ@\ziA*\E|O3C/hwZ*c?aAY,aTGÄWii\*@BAЕA|;?5A?}!@@AKu)A@1J n4@.UAN<@fAKA5|Ay[@Lz@(W@0E@E+ 3@G@;EXz-@j?ܔAS?CxBqGDBˮAo@g8|AaPFAsi¸I>_]@tVAA=g:]lv1Bp= B@!Ajk(A u( rU[ zB*eNBkAQj)G+AkfAAAک=zQiAQ5 YxB1@_@o`$y|:&8@]AA#]Kn4]1Avo#AO@:AA% 9w+b]@JrA2R] u4BAAv˝@3Bo5،F?yLv^? 1AC?D1u|т#AG<@0bAs$VB=@5A'odkAb1d@i8@Xf>Ȓo|DAw0vfP,:ۮAB }@-H0_ Žmg>uA v!0ţMi2N̒R@&QpqF@IAK1Nɮ!i.?M?K&ANG?e\A -@¿1GVRx&9)A* ,@!A_hq4@heD@>?#¿y'e {WG?r$F~>o@U/k>P@$@YHRºDX%S`GABsQCRMoBAN"ߍF£<L,Z/@/5F),`B^3Ŵ¹_CXA@F'+&*2OC~4YCPAoÐB>B:DRB֢d*,'p̍=3&B0& pZ@/gqvROvwe¯GGT¥]@=<`l`ʢ%vBvL+?pS=xvx>[Aw;mք<02IiA;5vy=/f6‡d"VI¸ĭA@iZ .OCi3O,PNBm" BdAr\=Q @}V.HSqk@q?'YO?/hMAAD?t" 4J?n`-T?@[Z@-`(z7.L"S@$9RO@[?A'u{<ȁ&w@ʀX1B,EAuÄ/z\‘BTpX;jW^Ȼ®yÚ5}Yoǽ¾ >BB: L+VjALÌ}^|C7ٵ5BBx…9[¨s<7AG©ߑ2Q _Th)cBkU|B$;f@W\€s'†0:hBs'/A U*Rv .A2:A9+$¡8ѣB1@sGj|AAwh #|Qٽއ$X0Bא jS)hACQ//1I`@>yAefӏ>X@?`@ B"{(@^}{ZRrAS @\(#A7@(=YAA0Bz?,6+C'+7YLA*X?X@\l'bω4GhAZ,b*R@s2z)a;{P/v }m~A\?xU,/^?왡>%!Y?SMc2A`A^)]?P4\BAV( VjR{C?-x<@'@Hy#@UԼ'?N55b@_pY 6]?y@ ƹ- !AFQXdFZ)>E<3= @ m?/q߾m^i@U@Ȥ:dΨB+&vsqAbhfI·uAT1Bl3oPcBAB&80B'LÃBc#qB?RA0&b¿s?X@2>^"2užSBYA,v-AA~j.FT€ЪAB/'‹/qtWBM?5lA'mWcA@+#Lm'^ƒ_)A"QFAw_A.eWE>U9WnܿFAZ)A@HA:`4nARq,AG[g@ (n AWI@(E@fAW7RMuApA2f?.f@T@DA~uAAg@A['@d,2p@.EAY?zUHA?k@A6B{Aun3K*&AMB?wL" ?xS?˶@U/AA!'zUXS@DARN<ؑi7{^@@Ev'BA'AxAŒ)f@k8]D@0,[E@s3> A@A[۬A$T>H@vAA&}IjnsAT)0 h@@0^,@nWB}b+W@]) ސ?A{7c$A4Jyϡ?!@NvO@^7?@sq@ >K̩?_;A@ջCâCC~kf( ‚&× Ѿho@;Sº&TA%6›8B_BB ABAŒ`k;oA=Ba3^kA Bhy3~m fGZA/NAAdQBAZJ!UB;AsAE>@%A Zvv=S/A ^̌RAUZt%"A39B€-h@NB8]C!M5|\J}hQAn @YAI;@S@c8kAA& -i@b$>Wظ@Qb@Ɠ#A|A@74A-z#@?fA@`>sA$@<|xAEkÌ&HK;XCS4G®pxsi?fMCC9†uPCC7B@ܡgAVƒ!y(zD;GAfѾ$Gpm:c?j—'A65B@yWAwB_N!AV4aıEªŸfA[}@@-Aj=rdt?&oHྀ>eA$?=ɛ@F@mCA\?ό?&RA?h@[ At@MJO?:ArjJ@A@(?A]AGA AErAϘANKAoAS zA'A]xAmA4B bAB9 B28BhBVYfByBqB*B垵CC g_ClQCbC2qCBBgٽ# gXB(.B!N¹-A]BZa·¥Qs’B%#BB7DmB>Z?JUil"FFCŠnNƒ[oŸDº€BfGRuheB4ZAA#BAG tQ-]BīXdBX F曞gKJٜO|r¤";ql,+CȱšCY1+sSV!2A˜z]A G3C8oxIB;>{?cAi@rÆAC xxy@Je@0@]|A@?9&A("٤gQYH2v_&Q@1B  h@&Au"m?=W}BAJ Ao;ZYdhB r e{E ]'A,]ӷeA$5Z@aEGc-@A\8@Av9Kپ@~UoىA=OB5cA@/A+A$@/A,NAkA1AALA`gnQAy(FA@NABK@ظx@Rz#A)AtAB BA bAA/tAABF,A! APAV/AwAOiABcBxB,BHB"BBPB̍BqBB@BCyCCC.C'MC.CCEPC6XCGC9[XCYICH>CnpC|CCoC^IC8C۸2"B{%ZB>l[C[BIlB̩C)Ž/Cee@.ŠgB,B'JG%Bbn8B/S\AʉKB4B}BZCSPC WC|BC9*DM~µ`cŠC!rBBPBT6BdB͊.B%BB-'!B/A!MDC0[BD"B1! hB9NOb@P;BW&A%B>BBmBH)BvZAtBCB>yBv!BSBSiQm@sA6xBABd8BB"DBI,BPBBHB`oBWBKtBEDBBBBCB8M~B B!BC+WC%5C%C bC33CGC5,CCK CdPD=jdDPDVJ]DeDuDfDoDD]DLDـDDDDďDÝD8dE IE9EGE!BE6E>E[EhfEjEIE)EE"EEIEgkCr@]C*B DCźdB9CCCAaDCCelCCC C)xCkhCC[9CCz Cy?C MCC}CCC C%CnC*CQ$C9C4C6C7 CDzCw^Ca~CV CIC4CDC]]CCC{3CC9CCdC(3C! CAC&C6CC0?CD1C8COD0D D1D^bD ,PD [D5D oD :pD DD ЅDRDpDKD"D2D'5D(KD%D'D!D)ʥD.D+@D0kD-ÔD3D6SD9vD7AD@JDASDF WDHרDOUDPDHDMDZDYDcgD`6D\DjhDb1D6DfDXaDD DP:D,CD2q D~$HDQ8DSDI.DfD MD8D6TDXDڿDaD\GDSRDK/DAOGDFduDRDPlDS 1Dx|Dn)DfB~Dy\D\DDDpDnDEavDisDxD^NDEdDy)DJ.D@Dn2CD)DDaD~DՏDMDk"DvDtD&DRDdDxcDDEDeDDdDsDkDrDDpD}DMDDz7D~DPMDDxDD3DuDJDjDD|D{JD\DIDDmD5DsSDʨDЭD_D+gDDԒDDD3DۂDagDDbDDED{2D DdD"DpDD3DDoEDWEfEE?EqE]yE aEEE E\E:EDExE1EGE!#EE(%E%E.bE.JZE.jE3gE4E:/E9E;qEBkEAEI2EFAEGoERbETEXhE]E[HE^E`FEg{EimElErEoEpjEuVEz@E{E-E\EVEEpEo#Eb#EX;EEwEqEEyWEk:EEEBeE EtnEE`+EEsEڲEYE?E\EFѷFBCF HF IF FFNbFnF[eF$F&F)אF/F4BPF:QFL}$FTեFTFXF]cFm5ZFrq"FwFzFFNFFFՔFbF}tFFȪF9(F-F G^ G ,UG 0)G&vGPgGG=G7SE:BY[DDD)SDDRDD3DxDD\DD8DDкDnD*DʹD~D"|DfDDD_,D[D߉vDD7D+DwDDDD D};DsJD E<DE$jDWEDDE LENEE6IE .E ǻEENEbEn EDEOEIEjuE' E"w#E"(E(E!rEE&KE!0E+-E"xE'SE)p.E%^E-ŸE)`E4;E=5E7E44E8GE9E9fE>E=FEAEE<3EFEH:EHQEOzEPEPuEXVETEZER?E]E[)Ea-EfEiE^EmEetEruEq ^EvF EsE~CE9EPE%nEWEsE6aEEELEEE-E8E E1EhEUE|E/EEחEqEoEKE7E E EmNEbjEdEESEEEłEņ1EHEˉE7E7=E E$EC-E|E5VE E4EZEtE.ExEEoE^ESFcFYFSF ~lFMFFF#|F%zkF$BF%F)F+4F.gF2IF5gF7EkF;JF>GFC?FJFNUFOFScnFXF[ڮFaFhjFpFy F~HFF#'F.FYFlFF+!FFmdF"FoFSFFFcFF FuF֝BFbF"FFvFbTG6G G)GGG"G%˂G+G1xG9!GPoGGYGY&$G_PGfX4GaGi GEwEBDDΚDDDLDq;DލDXDŮDvDcDD(EDDRDrDE FE E&DE EŁEYE GEEBEtEEEEE\EEcEE9E-E 5E*E&}E'E;TPE-+[E5 E*Y(E2E:9bE@E:E? 6EAEACE?!EBEOEP"xEVETEUEY/EX%lEXäERFEZE^E[{E^Ej"EgQEfEdEue!Es]EwfErɉEqLEpEzKEv EyEwѤEqEEEsEE"ELEE:nEEْEOLE“Ey3EEE%EEEE6E(E3EExEmEE[EnEE)EZOE`pE+1ELEeEETE£EmEEǶ(EiE̊rEiGEҙEӶEׁEEEIE₌EE*PEEOE&E$EGEEFFF,FbFF +FFyF6FFghF|F$F8IF0;FF F F"dAF%iF'F,*F3JF;*FD*+FL]FMoFL FMFSX*FUrwFWwF]F_އFb}Fg,8kE=lE@E<{EB8lEEmE@E@&EJEH'ELEKEOELߦES\EXo0EXFE]E]E\E[EeŀEe4KEaREnNEg8QEsEqZEoOEtE{ECErEE[EEEyEyE\E9E4|EEEEEUNEVEE\EiZEEE8E{mEEEEE4EE`EEME|UEsELEE!TE EyE͌EF EOEWEpTE޽EEGE E,2EpkEgE}E"XEEpE2CF2FFyxF1FaxF|FFF,F(F FIF"1F&+F'BF)F-J.F1fF3F;_F>!F>hF@jFEenFJvFOlFU_F[F`?FbFdFiUFm{FpfFq_F|YF FEFbFFRFaF+FJFlFF?F3KFpFFgFFLF(F?FFjFgF-G.GG?G Gn G#G)G)4JG/zG:G=G?#G:EBϯD0D`"qDPC9D@.D+DL̑DZ DK|D7D7uxD3 Df,dD^EDamD&TDQDgDQ9xDD DVUD\(DW=DTD[DcDXD"DD|Dx\4DcyDaDZDDVDDvD-Dz#D_DbrDPDDz(D#DiLDr 6DD DDZ DKDDn,EDDaDDDDkD&D0D̓D D-DDȠDDDn DMDD?DDDZDcDD̵DوD8DD~DD%DDrXDD'D/GDWDD .D=DƞDv#D_D,DьDvDRDDجDoDчD(DDDmD1DDD|DD |EiEvlEE(E S{E E E thEEJEEdE\EGElEYEEE &E#E$oE'E'E,E/E0E2E4E6cE:E>lEB6|PCDAC%Ce0E0KC#sCxCCUCCN,C9C#Cs2lC̀Cg_B$C5CCCxoC[CC 5C]dCCd_CC CACCxCUrCIiC `CCMC3CC]CUCBCC[^CKCy>CPCRCC KC=ClCaC$CNC{̘CCCPCCC@CCYCzCeCXC9DBCvCTC uC9CCCd]CCywC*C~ CCCZCCC%4CC]CC4CCCC%LCGDWCqC#CfCDDDJD wD DD$#D dD DD DD"GD DDGDFD|DD' DBD+/D(rD)gD1D"D4D7D9_D0ND3rD;fD;4D; DAЮDODJ4DSBDLDKKDMDTҮDQBDX˾D\~0Dh Dc`Dc>qDicDo3@DnDdDwDzDtDwP`Dp*DD9D#D\2DDwDWDDDD^lD#DTDD Dx@D1SDDtDVDDƋD DtMD~fD֗D23DˆDD2DEUD(EdEFELE6EEEE";E'9E36E5E:GE7E<}EKENEWGETEZlEgEEOEEhZEEDEEeEWEEpEgEœEF-FcEE[D1VASA%}C37n@4 ^ClCOYo;/$BS{Bs CbBTBSC3K—AB|ƒs]BxBB`G2sA&?&BB;BzWgÐ?•HB &BؠC 4;B?<:@ZAB)AyKAA>B LnSo_BAo^BՓB5[BZB6'e/wB4JvBBbBJL B$5BA A=B+AKaBBnQAB7;BBO{RB@A8iBc;@ϪBBoAWB4B<5AJB-@-XB iBh,B`B>YBlyBq`BtAzBKBڸB8B/BB}tKBB_ABBqBB)BTBBbF0B9m"B-B( BBB{BBBeBpBCKGBB BJBBΩBtJBKCByBBϛBnCBCC2CDbCC C ;0$z( ~A1A|R A#پ^WgϞAXAȑyB$Q @ A{;hAPAAyKP=c?A@!V=s\?>%A&AAF:C@wq@sIAp'`A8ADKATh0@Kiп,ȫu@EAďYAo?RZo@"gA9Q@i/3A[Ar/D\3@UBp AJ@vAfƾ-sRb. AA\?]A垯A|?=o@"AJA5ApAC?LB@AjAYAArAAԬAgB BZB0BH'BqBBjiBcA.AݾnB'ANB:ݑBGBUBF+ByBT 2BBMfBBwBi?B/BBTBĴeB@B+B BA{C CC|C*OC)CL-cCNCvFCHCqCCwCCC}CwCLAz>B9žgBb$aHCB\CV^Ú@D ‘:"C-בA BpC#B\ »6DB-C;!tBO@ܫ) \H/&# kVBF%Jz6\]`J7RuAqVḼVY_ɖA8P Ibn*y?ŏY?o(4@{cnd"A .޸6VA'&@օA^k5As#K?¾yEE2?)]W9@t#\#@п;]@7]rҖ-;A^u=j@74?ղ)˽yI@\!>S)@Y@JAAM@-@ב@@E#@m'@gAg@cA|AAvA{Ax*AA1AB B RAB%A/A7l5`=Ϫr6~FI),k-gC\B;BF 9 Âm4!CѾCX':AWR¤J†@<=f9>¼ [«6†85C(Aؗ´duHOAuA݉ZC'F ݶ‘ nAsy{VBe˜@$Y]g§˜9?}PR@ Y£Z@ѱ*8C{0[B޶6ȏ[9zA}5A ¯]CEvBf\N-5%r8ڑB^:eAstAkB@(B4ࣚ}@p5GD@@cA j;?B2za?DԚA?ClC@1KABeDq\ҎlCIyAo0}]YzAF W_I[.̲}`IAIB&\Ͽ$u@!,HA<& E}?!ru@T@"?#޽=޺@E<Č15`jcA@ ;M>OYA`A`)bl۾Ź _p6+s?iRA )XA~?HFA@=C\@ @.>@@QW*] {d@ GZ-7ؕ'GA,~> I=U=e0H>|}vQ@]A5`D?r"@7?Ak,Y@O?_I߁0l=YB`aۘ3 S@A4A+C Y)B4 B!"A60zBrBCF*8=B+B8 =W% $BCv"8A1N-54>šQQA?{ۼB6CC;AcÙP8 E}Sa.A"{Au"ï%BZcpBB\5AGh%9An7' ŸeDm9J¦4AA.2(ZœZAQSF{A}#A@$AD&BC]ߝAmonB<}g,S,¢AA1A*Bڴ/tbBC<fAhK\NzG@+@;iG=/r@9p T@42?7fO[j-ECAx-BQTDW…®c-;S¨ABjABu:p¸ A;o-«7ޠ>\²^‹AS£;|[Y^…e A,n2C-BX%o8[ABA1š&yBU$#$?wr M<5{:?PB:¾4„UZ ^l;Bt2f^`'2B :@M,WFKo b)BfkB$&Aw2Bt%QAO?WGngA~XJrA(r&-mA PA.:zA€[cQtA+@Z%PCLNAk?Ƚ g^@WA^AtsrˆdQ2?%fwwdEl#bifXWA6nA9LAB{Am)Dh4~0AL'AiU⼍SKAZAHCZb2A(M+Y8*A?hbJkno=>??drnA\.#$ @_XTAv2˅WhBZt\@/Ay$Q5d;V AޞD:.@6_7LAqt =dͽg8Sz"]S?:̬$Q@[:~,t̜ Ateb\#߄])@@\UDvZ ;@?TF<:.DB. [~2*B*;BE7;BU}v5A kLB{-ì\tvRjE)d@¤$§-åtš”f[-5aTH?`Cs|:¶9!{nœB)m{B )%B<Œ ¡jB#´hϋ¸6ApNA  “q™=CBB}BϫBy m— AOY– ,$BJaIsV++lD~B%‹mBV*}ov(B@S'U“+>o@LA:Af>p*yBA:@WA\b@#?//*X B=v?鿂B[^GBAG@(&h>uj)8L//A4w>}VA(2=3AvhO*=UA]@d(nPYX@Nӡ]hf.?@HA&@}l ~A^f1P뿺FB@}_Y!?z?kˇ6 u4Sθ@>@@(3[?1 @nWM `;'?!#+A96@9A8m#!E('W>}"A@RoVCŚ‮n@sg~'\Ax6ߙlqDu>@b5)T:Kw왢i-ӿAM.տ`Vq`B|CDf´ÆBª|+HaLuDIaHBiÂ[S(ª5|㵝i6\ JNA*;Bn0LSX+zãC8BCƶBսu* (WC440b7Biw^}Š?%b?AA-=y½~AT"TAZB4@qG µ*šxxt½_/%Bh$•mBT˜gQbA_ ‹9Y‡1B5I9Q@FΘyBB:.[w_p:95zR?7W]gA͇J‚MHND@6ݕgrA9oA5VG$,@A*?QA"S 1??/+o@I@A$G\BAU?P \1@Auk]>=”1Ac@8^nAhd@!>A3&@ʵͿV`@᣿.5A{]~"@ @$)bHFI?ШfA+vɘ"RmxB94]37O>=358|"HA` 0@1j>cnCz#d{ó,B 1@0M A .pwB9&~TœCU(<§H7“d/AI2H0@œ¹|B-TABmB (-@m?0B|a$6(BpBNmRN0~:c/i …A76CA.c}AB&mgB@>A tBQ¥,BXy†+q JZAS6YBqyB6IIZ>ރ§kA.@H2$LtC8AbX?V|EAtN¦mX ~A&Cn<"93M80p xU>y!yBA‚ fIBEl\@ 6!ۊTz/Jjlr=14`AB;Z AHAy~YB2BO~B (۸cBMB At8A.;,kΎ@^FAAj8Aɦ \A2BA?F.;JB*,4i@Q>25@?$E{7w—׭dAZE*uAA^A5zAO!f  A[AoyB8@ A6ANh|@@3j!?,v|?ٍzp>@OL V@A?Ҹ_A%2|BG@£@+\8&BafAd&4C 2BEJUl+ǚB\f=SA³&CµB]bBoARr@:d2@ڍCLqr(F俹?BėzBsE^';<#חJ[”~A$BA10 B"BXCA'aBPoLVB3At././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_background.py0000644000175100001660000003454014764317313022276 0ustar00runnerdockerfrom astropy.nddata import NDData import astropy.units as u import numpy as np from numpy.testing import assert_allclose import pytest from specutils import Spectrum1D from specreduce.background import Background from specreduce.tracing import FlatTrace, ArrayTrace 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. # trace_pos = 15 trace = FlatTrace(image, trace_pos) bkg_sep = 5 bkg_width = 2 # all the following should be equivalent, whether image's spectral axis # is in pixels or physical units: bg1 = Background(image, [trace - bkg_sep, trace + bkg_sep], width=bkg_width) bg2 = Background.two_sided(image, trace, bkg_sep, width=bkg_width) bg3 = Background.two_sided(image, trace_pos, bkg_sep, width=bkg_width) assert np.allclose(bg1.bkg_image().flux, bg2.bkg_image().flux) assert np.allclose(bg1.bkg_image().flux, bg3.bkg_image().flux) bg4 = Background(image_um, [trace - bkg_sep, trace + bkg_sep], width=bkg_width) bg5 = Background.two_sided(image_um, trace, bkg_sep, width=bkg_width) bg6 = Background.two_sided(image_um, trace_pos, bkg_sep, width=bkg_width) assert np.allclose(bg1.bkg_image().flux, bg4.bkg_image().flux) assert np.allclose(bg1.bkg_image().flux, bg5.bkg_image().flux) assert np.allclose(bg1.bkg_image().flux, bg6.bkg_image().flux) # test that creating a one_sided background works Background.one_sided(image, trace, bkg_sep, width=bkg_width) # test that passing a single trace works bg = Background(image, trace, width=bkg_width) # test that image subtraction works sub1 = image - bg1 sub2 = bg1.sub_image(image) sub3 = bg1.sub_image() assert np.allclose(sub1.flux, sub2.flux) assert np.allclose(sub2.flux, sub3.flux) sub4 = image_um - bg4 sub5 = bg4.sub_image(image_um) sub6 = bg4.sub_image() assert np.allclose(sub1.flux, sub4.flux) assert np.allclose(sub4.flux, sub5.flux) assert np.allclose(sub5.flux, sub6.flux) bkg_spec = bg1.bkg_spectrum() assert isinstance(bkg_spec, Spectrum1D) sub_spec = bg1.sub_spectrum() assert isinstance(sub_spec, Spectrum1D) # test that width==0 results in no background bg = Background.two_sided(image, trace, bkg_sep, width=0) assert np.all(bg.bkg_image().flux == 0) # test that any NaNs in input image (whether in or outside the window) don't # propagate to _bkg_array (which affects bkg_image and sub_image methods) or # the final 1D spectra. img[0, 0] = np.nan # out of window img[trace_pos, 0] = np.nan # in window stats = ["average", "median"] for st in stats: bg = Background(img, trace - bkg_sep, width=bkg_width, statistic=st) assert np.isnan(bg.image.flux).sum() == 2 assert np.isnan(bg._bkg_array).sum() == 0 assert np.isnan(bg.bkg_spectrum().flux).sum() == 0 assert np.isnan(bg.sub_spectrum().flux).sum() == 0 bkg_spec_avg = bg1.bkg_spectrum(bkg_statistic="average") assert_allclose(bkg_spec_avg.mean().value, 14.5, rtol=0.5) bkg_spec_median = bg1.bkg_spectrum(bkg_statistic="median") assert_allclose(bkg_spec_median.mean().value, 14.5, rtol=0.5) with pytest.raises( ValueError, match="Background statistic max is not supported. " "Please choose from: average, median, or sum.", ): bg1.bkg_spectrum(bkg_statistic="max") 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) # bottom of top window near/on top-edge of image (these should warn, but not fail) with pytest.warns(match="background window extends beyond image boundaries"): Background.two_sided(image, 25, 8, width=5) with pytest.warns(match="background window extends beyond image boundaries"): Background.two_sided(image, 25, 8, width=6) with pytest.warns(match="background window extends beyond image boundaries"): Background.two_sided(image, 25, 8, width=7) with pytest.warns(match="background window extends beyond image boundaries"): Background.two_sided(image, 7, 5, width=6) trace = ArrayTrace(image, trace=np.arange(10) + 20) # from 20 to 29 with pytest.warns(match="background window extends beyond image boundaries"): with pytest.raises( ValueError, match="background window does not remain in bounds across entire dispersion axis", ): # noqa # 20 + 10 - 3 = 27 (lower edge of window on-image at right side of trace) # 29 + 10 - 3 = 36 (lower edge of window off-image at right side of trace) Background.one_sided(image, trace, 10, width=3) with pytest.raises(ValueError, match="width must be positive"): Background.two_sided(image, 25, 2, width=-1) def test_trace_inputs(mk_test_img_raw): """ Tests for the input argument 'traces' to `Background`. This should accept a list of or a single Trace object, or a list of or a single (positive) number to define a FlatTrace. """ image = mk_test_img_raw # When `Background` object is created with no Trace object passed in it should # create a FlatTrace in the middle of the image (according to disp. axis) background = Background(image, width=5) assert np.all(background.traces[0].trace.data == image.shape[1] / 2.0) # FlatTrace(s) should be created if number or list of numbers is passed in for `traces` background = Background(image, 10.0, width=5) assert isinstance(background.traces[0], FlatTrace) assert background.traces[0].trace_pos == 10.0 traces = [10.0, 15] background = Background(image, traces, width=5) for i, trace_pos in enumerate(traces): assert background.traces[i].trace_pos == trace_pos # make sure error is raised if input for `traces` is invalid match_str = ( "objects, a number or list of numbers to define FlatTraces, " + "or None to use a FlatTrace in the middle of the image." ) with pytest.raises(ValueError, match=match_str): Background(image, "non_valid_trace_pos") class TestMasksBackground: """ Various test functions to test how masked and non-finite data is handled in `Background. """ def mk_img(self, nrows=4, ncols=5, nan_slices=None): """ Make a simple gradient image to test masking in Background. Optionally add NaNs to data with `nan_slices`. Returned array is in u.DN. """ img = np.tile((np.arange(1.0, ncols + 1)), (nrows, 1)) if nan_slices: # add nans in data for s in nan_slices: img[s] = np.nan return img * u.DN @pytest.mark.parametrize("mask", ["apply", "propagate", "zero_fill"]) def test_fully_masked_column(self, mask): """ Test background with some fully-masked columns (not fully masked image). In this case, the background value for that fully-masked column should be 0.0, with no error or warning raised. """ img = self.mk_img(nrows=10, ncols=10) img[:, 0:1] = np.nan bkg = Background(img, traces=FlatTrace(img, 6), mask_treatment=mask) assert np.all(bkg.bkg_image().data[:, 0:1] == 0.0) @pytest.mark.parametrize("mask", ["apply", "propagate"]) def test_fully_masked_image(self, mask): """ Test that the appropriate error is raised by `Background` when image is fully masked/NaN. """ with pytest.raises(ValueError, match="Image is fully masked."): # fully NaN image img = self.mk_img() * np.nan Background(img, traces=FlatTrace(self.mk_img(), 2), mask_treatment=mask) with pytest.raises(ValueError, match="Image is fully masked."): # fully masked image (should be equivalent) img = NDData(np.ones((4, 5)), mask=np.ones((4, 5), dtype=bool)) Background(img, traces=FlatTrace(self.mk_img(), 2), mask_treatment=mask) # Now test that an image that isn't fully masked, but is fully masked # within the window determined by `width`, produces the correct result. msg = "Image is fully masked within background window determined by `width`." with pytest.raises(ValueError, match=msg): img = self.mk_img(nrows=12, ncols=12, nan_slices=[np.s_[3:10, :]]) Background(img, traces=FlatTrace(img, 6), width=7) @pytest.mark.filterwarnings("ignore:background window extends beyond image boundaries") @pytest.mark.parametrize( "method,expected", [ ("apply", np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0])), ( "propagate", np.array([0.0, 2.0, 3.0, 0.0, 5.0, 6.0, 7.0, 0.0, 9.0, 10.0, 11.0, 12.0]), ), ( "zero_fill", np.array( [ 0.58333333, 2.0, 3.0, 2.33333333, 5.0, 6.0, 7.0, 7.33333333, 9.0, 10.0, 11.0, 12.0, ] ), ), ], ) def test_mask_treatment_bkg_img_spectrum(self, method, expected): """ This test function tests `Background.bkg_image` and `Background.bkg_spectrum` when there is masked data. It also tests background subtracting the image, and returning the spectrum of the background subtracted image. This test is parameterized over all currently implemented mask handling methods to test that they work as intended. The window size is set to use the entire image array, so warning about background window is ignored.""" img_size = 12 # square 12 x 12 image # make image, set some value to nan, which will be masked in the function image1 = self.mk_img( nrows=img_size, ncols=img_size, nan_slices=[np.s_[5:10, 0], np.s_[7:12, 3], np.s_[2, 7]] ) # also make an image that doesn't have nonf data values, but has # masked values at the same locations, to make sure they give the same # results mask = ~np.isfinite(image1) dat = self.mk_img(nrows=img_size, ncols=img_size) image2 = NDData(dat, mask=mask) for image in [image1, image2]: # construct a flat trace in center of image trace = FlatTrace(image, img_size / 2) # create 'Background' object with `mask_treatment` set # 'width' should be > size of image to use all pix (but warning will # be raised, which we ignore.) background = Background(image, mask_treatment=method, traces=trace, width=img_size + 1) # test background image matches 'expected' bk_img = background.bkg_image() # change this and following assertions to assert_quantity_allclose once # issue #213 is fixed np.testing.assert_allclose(bk_img.flux.value, np.tile(expected, (img_size, 1))) # test background spectrum matches 'expected' times the number of rows # in cross disp axis, since this is a sum and all values in a col are # the same. bk_spec = background.bkg_spectrum() np.testing.assert_allclose(bk_spec.flux.value, expected * img_size) def test_sub_bkg_image(self): """ Test that masked and non-finite data is handled correctly when subtracting background from image, for all currently implemented masking options. """ # make image, set some value to nan, which will be masked in the function image = self.mk_img( nrows=12, ncols=12, nan_slices=[np.s_[5:10, 0], np.s_[7:12, 3], np.s_[2, 7]] ) # Calculate a background value using mask_treatment = 'apply'. # For 'apply', the flag applies to how masked values are handled during # calculation of background for each column, but nonfinite data will # remain in input data array background_apply = Background( image, mask_treatment="apply", traces=FlatTrace(image, 6), width=2 ) subtracted_img_apply = background_apply.sub_image() assert np.all(np.isfinite(subtracted_img_apply.data) == np.isfinite(image.data)) # Calculate a background value using mask_treatment = 'propagate'. The input # 2d mask is reduced to a 1d mask to mask out full columns in the # presence of any nans - this means that (as tested above in # `test_mask_treatment_bkg_img_spectrum`) those columns will have 0.0 # background. In this case, image.mask is expanded to mask full # columns - the image itself will not have full columns set to np.nan, # so there are still valid background subtracted data values in this # case, but the corresponding mask for that entire column will be masked. background_propagate = Background( image, mask_treatment="propagate", traces=FlatTrace(image, 6), width=2 ) subtracted_img_propagate = background_propagate.sub_image() assert np.all(np.isfinite(subtracted_img_propagate.data) == np.isfinite(image.data)) # Calculate a background value using mask_treatment = 'zero_fill'. Data # values at masked locations are set to 0 in the image array, and the # background value calculated for that column will be subtracted # resulting in a negative value. The resulting background subtracted # image should be fully finite and the mask should be zero everywhere # (all unmasked) background_zero_fill = Background( image, mask_treatment="zero_fill", traces=FlatTrace(image, 6), width=2 ) subtracted_img_zero_fill = background_zero_fill.sub_image() assert np.all(np.isfinite(subtracted_img_zero_fill.data)) assert np.all(subtracted_img_zero_fill.mask == 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_core.py0000644000175100001660000000105114764317313021076 0ustar00runnerdockerimport pytest from specreduce.core import SpecreduceOperation def test_sro_call(): sro = SpecreduceOperation() with pytest.raises(NotImplementedError): sro() def test_sro_as_function(): class TestSRO(SpecreduceOperation): def __call__(self, x): return x**2 assert TestSRO.as_function(6) == 36 class TestSRO(SpecreduceOperation): def __call__(self, *nargs): return 5 with pytest.raises(NotImplementedError, match="There is not a way"): TestSRO.as_function(6) == 36 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_extinction.py0000644000175100001660000000506014764317313022336 0ustar00runnerdockerimport numpy as np import pytest from astropy import units as u from astropy.utils.exceptions import AstropyUserWarning from specreduce.calibration_data import ( AtmosphericExtinction, AtmosphericTransmission, SUPPORTED_EXTINCTION_MODELS ) @pytest.mark.remote_data def test_supported_models(): """ Test loading of supported models """ for model in SUPPORTED_EXTINCTION_MODELS: ext = AtmosphericExtinction(model=model) assert len(ext.extinction_mag) > 0 assert len(ext.transmission) > 0 @pytest.mark.remote_data def test_custom_mag_model(): """ Test creation of custom model from Quantity arrays """ wave = np.linspace(0.3, 2.0, 50) extinction = u.Magnitude(1. / wave, u.MagUnit(u.dimensionless_unscaled)) ext = AtmosphericExtinction(extinction=extinction, spectral_axis=wave * u.um) assert len(ext.extinction_mag) > 0 assert len(ext.transmission) > 0 @pytest.mark.remote_data def test_custom_raw_mag_model(): """ Test creation of custom model from Quantity arrays """ wave = np.linspace(0.3, 2.0, 50) extinction = 1. / wave * u.mag ext = AtmosphericExtinction(extinction=extinction, spectral_axis=wave * u.um) assert len(ext.extinction_mag) > 0 assert len(ext.transmission) > 0 @pytest.mark.remote_data def test_custom_linear_model(): """ Test creation of custom model from Quantity arrays """ wave = np.linspace(0.3, 2.0, 50) extinction = 1. / wave * u.dimensionless_unscaled ext = AtmosphericExtinction(extinction=extinction, spectral_axis=wave * u.um) assert len(ext.extinction_mag) > 0 assert len(ext.transmission) > 0 @pytest.mark.remote_data def test_unsupported_model(): """ Test loading of a nonexistent model """ with pytest.raises(ValueError, match='Requested extinction model,'): AtmosphericExtinction(model='bad_model') @pytest.mark.remote_data def test_missing_extinction_unit(): """ Test creation of custom model from Quantity arrays """ wave = np.linspace(0.3, 2.0, 50) extinction = 1. / wave with pytest.warns(AstropyUserWarning): ext = AtmosphericExtinction(extinction=extinction, spectral_axis=wave * u.um) assert len(ext.extinction_mag) > 0 assert len(ext.transmission) > 0 @pytest.mark.remote_data def test_transmission_model(): """ Test creating of default atmospheric transmission model """ ext = AtmosphericTransmission() assert len(ext.transmission) > 0 with pytest.warns(RuntimeWarning): assert len(ext.extinction_mag) > 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_extract.py0000644000175100001660000004114014764317313021623 0ustar00runnerdockerimport numpy as np import pytest from astropy import units as u from astropy.modeling import models from astropy.nddata import NDData, VarianceUncertainty, UnknownUncertainty, StdDevUncertainty from astropy.tests.helper import assert_quantity_allclose from specutils import Spectrum1D from specreduce.background import Background from specreduce.extract import BoxcarExtract, HorneExtract, OptimalExtract, _align_along_trace from specreduce.tracing import FitTrace, FlatTrace, ArrayTrace def add_gaussian_source(image, amps=2, stddevs=2, means=None): """Modify `image.data` to add a horizontal spectrum across the image. Each column can have a different amplitude, stddev or mean position if these are arrays (otherwise, constant across image). """ nrows, ncols = image.shape if means is None: means = nrows // 2 if not isinstance(means, np.ndarray): means = np.ones(ncols) * means if not isinstance(amps, np.ndarray): amps = np.ones(ncols) * amps if not isinstance(stddevs, np.ndarray): stddevs = np.ones(ncols) * stddevs for i, col in enumerate(range(ncols)): mod = models.Gaussian1D(amplitude=amps[i], mean=means[i], stddev=stddevs[i]) image.data[:, i] = mod(np.arange(nrows)) def test_boxcar_extraction(mk_test_img): # Try combinations of extraction center, and even/odd extraction aperture sizes. image = mk_test_img trace = FlatTrace(image, 15.0) boxcar = BoxcarExtract(image, trace) spectrum = boxcar.spectrum assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 75.0)) assert spectrum.unit is not None and spectrum.unit == u.Jy trace.set_position(14.5) spectrum = boxcar() assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 72.5)) trace.set_position(14.7) spectrum = boxcar() assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 73.5)) trace.set_position(15.0) boxcar.width = 6 spectrum = boxcar() assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 90.0)) trace.set_position(14.5) spectrum = boxcar(width=6) assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 87.0)) trace.set_position(15.0) spectrum = boxcar(width=4.5) assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 67.5)) trace.set_position(15.0) spectrum = boxcar(width=4.7) assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 70.5)) trace.set_position(14.3) spectrum = boxcar(width=4.7) assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 67.15)) def test_boxcar_nonfinite_handling(mk_test_img): image = mk_test_img image.data[14, 2] = np.nan image.data[14, 4] = np.inf trace = FlatTrace(image, 15.0) spectrum = BoxcarExtract(image, trace, width=6, mask_treatment="ignore")() target = np.full_like(spectrum.flux.value, 90.0) target[2] = np.nan target[4] = np.inf np.testing.assert_equal(spectrum.flux.value, target) spectrum = BoxcarExtract(image, trace, width=6, mask_treatment="apply")() target[[2, 4]] = 91.2 np.testing.assert_allclose(spectrum.flux.value, target, rtol=1e-8) def test_boxcar_outside_image_condition(mk_test_img): # Trace is such that extraction aperture lays partially outside the image image = mk_test_img trace = FlatTrace(image, 3.0) boxcar = BoxcarExtract(image, trace, mask_treatment="apply") spectrum = boxcar(width=10.0) assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 32.0)) boxcar = BoxcarExtract(image, trace, mask_treatment="ignore") spectrum = boxcar(width=10.0) assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 32.0)) def test_boxcar_array_trace(mk_test_img): image = mk_test_img trace_array = np.ones_like(image[1]) * 15.0 trace = ArrayTrace(image, trace_array) boxcar = BoxcarExtract(image, trace) spectrum = boxcar() assert np.allclose(spectrum.flux.value, np.full_like(spectrum.flux.value, 75.0)) def test_horne_image_validation(mk_test_img): # # Test HorneExtract scenarios specific to its use with an image of # type `~numpy.ndarray` (instead of the default `~astropy.nddata.NDData`). # image = mk_test_img trace = FlatTrace(image, 15.0) extract = OptimalExtract(image.data, trace) # equivalent to HorneExtract # an array-type image must come with a variance argument with pytest.raises(ValueError, match=r".*variance must be specified.*"): ext = extract() # an NDData-type image can't have an empty uncertainty attribute with pytest.raises(ValueError, match=r".*NDData object lacks uncertainty"): ext = extract(image=image) # a warning should be raised if uncertainty is StdDevUncertainty with pytest.warns(UserWarning, match="image NDData object's uncertainty"): err = StdDevUncertainty(np.ones_like(image)) image.uncertainty = err ext = extract(image=image) # an NDData-type image's uncertainty must be of type VarianceUncertainty # or type StdDevUncertainty with pytest.raises(ValueError, match=r".*unexpected uncertainty type.*"): err = UnknownUncertainty(np.ones_like(image)) image.uncertainty = err ext = extract(image=image) # an array-type image must have the same dimensions as its variance argument with pytest.raises(ValueError, match=r".*shapes must match.*"): # remember variance, mask, and unit args are only checked if image # object doesn't have those attributes (e.g., numpy and Quantity arrays) err = np.ones_like(image[0]) ext = extract(image=image.data, variance=err) # an array-type image must have the same dimensions as its mask argument with pytest.raises(ValueError, match=r".*shapes must match.*"): err = np.ones_like(image) mask = np.zeros_like(image[0]) ext = extract(image=image.data, variance=err, mask=mask) # an array-type image given without mask and unit arguments is fine # and produces an extraction with flux in DN and spectral axis in pixels err = np.ones_like(image) ext = extract(image=image.data, variance=err, mask=None, unit=None) assert ext.unit == u.Unit("DN") assert np.all(ext.spectral_axis == np.arange(image.shape[extract.disp_axis]) * u.pix) # ignore Astropy warning for extractions that aren't best fit with a Gaussian: @pytest.mark.filterwarnings("ignore:The fit may be unsuccessful") def test_horne_variance_errors(mk_test_img): image = mk_test_img trace = FlatTrace(image, 3.0) # all zeros are treated as non-weighted (i.e., as non-zero fluxes) image.uncertainty = VarianceUncertainty(np.zeros_like(image)) image.mask = np.zeros_like(image) extract = HorneExtract(image, trace) ext = extract.spectrum assert not np.all(ext == 0) # single zero value adjusts mask and does not raise error err = np.ones_like(image) err[0][0] = 0 image.uncertainty = VarianceUncertainty(err) ext = extract(image) assert not np.all(ext == 1) # single negative value raises error err = image.uncertainty.array err[0][0] = -1 with pytest.raises(ValueError, match="variance must be fully positive"): # remember variance, mask, and unit args are only checked if image # object doesn't have those attributes (e.g., numpy and Quantity arrays) ext = extract(image=image.data, variance=err, mask=image.mask, unit=u.Jy) @pytest.mark.filterwarnings("ignore:The fit may be unsuccessful") def test_horne_non_flat_trace(): # create a synthetic "2D spectrum" and its non-flat trace n_rows, n_cols = (10, 50) original = np.zeros((n_rows, n_cols)) original[n_rows // 2] = 1 # create small offsets along each column to specify a non-flat trace trace_offset = np.polyval([2e-3, -0.01, 0], np.arange(n_cols)).astype(int) exact_trace = n_rows // 2 - trace_offset # re-index the array with the offsets applied to the trace (make it non-flat): rows = np.broadcast_to(np.arange(n_rows)[:, None], original.shape) cols = np.broadcast_to(np.arange(n_cols), original.shape) roll_rows = np.mod(rows + trace_offset[None, :], n_rows) rolled = original[roll_rows, cols] # all zeros are treated as non-weighted (give non-zero fluxes) err = 0.1 * np.ones_like(rolled) mask = np.zeros_like(rolled).astype(bool) # unroll the trace using the Horne extract utility function for alignment: unrolled = _align_along_trace(rolled, n_rows // 2 - trace_offset) # ensure that mask is correctly unrolled back to its original alignment: np.testing.assert_allclose(unrolled, original) # Extract the spectrum from the non-flat image+trace extract_non_flat = HorneExtract( rolled, ArrayTrace(rolled, exact_trace), variance=err, mask=mask, unit=u.Jy )() # Also extract the spectrum from the image after alignment with a flat trace extract_flat = HorneExtract( unrolled, FlatTrace(unrolled, n_rows // 2), variance=err, mask=mask, unit=u.Jy )() # ensure both extractions are equivalent: assert_quantity_allclose(extract_non_flat.flux, extract_flat.flux) def test_horne_bad_profile(mk_test_img): image = mk_test_img trace = FlatTrace(image, 3.0) extract = HorneExtract( image.data, trace, spatial_profile="bad_profile_name", variance=np.ones(image.data.shape) ) with pytest.raises(ValueError, match="spatial_profile must be one of"): extract.spectrum extract = HorneExtract( image.data, trace, spatial_profile=models.Polynomial1D(2), variance=np.ones(image.data.shape), ) with pytest.raises(ValueError, match="spatial_profile must be a"): extract.spectrum def test_horne_nonfinite_column(mk_test_img): image = mk_test_img image.data[:, 4] = np.nan trace = FlatTrace(image, 3.0) extract = HorneExtract( image.data, trace, spatial_profile="gaussian", variance=np.ones(image.data.shape) ) sp = extract.spectrum assert np.isnan(sp.flux.value[4]) assert np.all(np.isfinite(sp.flux.value[:4])) assert np.all(np.isfinite(sp.flux.value[5:])) def test_horne_no_bkgrnd(mk_test_img): # Test HorneExtract when using bkgrd_prof=None image = mk_test_img trace = FlatTrace(image, 3.0) extract = HorneExtract(image.data, trace, bkgrd_prof=None, variance=np.ones(image.data.shape)) assert len(extract.spectrum.flux) == 10 def test_horne_interpolated_profile(mk_test_img): # basic test for HorneExtract using the spatial_profile == `interpolated_profile` # option add a perfectly gaussian source and make sure gaussian extraction # and self-profile extraction agree (since self=gaussian in this case) image = mk_test_img add_gaussian_source(image) # add source across image, flat trace trace = FlatTrace(image, image.shape[0] // 2) # horne extraction using spatial_profile=='Gaussian' horne_extract_gauss = HorneExtract( image.data, trace, spatial_profile="gaussian", bkgrd_prof=None, variance=np.ones(image.data.shape), ) # horne extraction with spatial_profile=='interpolated_profile' horne_extract_self = HorneExtract( image.data, trace, spatial_profile={"name": "interpolated_profile", "n_bins_interpolated_profile": 3}, bkgrd_prof=None, variance=np.ones(image.data.shape), ) assert_quantity_allclose(horne_extract_gauss.spectrum.flux, horne_extract_self.spectrum.flux) horne_extract_self = HorneExtract( image.data, trace, spatial_profile={"name": "interpolated_profile", "n_bins_interpolated_profile": 3}, variance=np.ones(image.data.shape), ) def test_horne_interpolated_profile_norm(mk_test_img): # ensure that when using spatial_profile == `interpolated_profile`, the fit profile # represents the shape as a funciton of wavelength, and correctly accounts # for variations in trace position and flux. image = mk_test_img nrows, ncols = image.shape # create sawtooth pattern trace. right now, the _align_along_trace function # will rectify the trace to the integer-pixel level, so in this test # case the specturm will be totally straightened out. trace_shape = np.ones(ncols) * nrows // 2 trace_shape[::2] += 4 trace = ArrayTrace(image, trace_shape) # add gaussian source to image with increasing amplitude and that follows # along the trace. looks like a sawtooth pattern of increasing brightness add_gaussian_source(image, amps=np.linspace(1, 5, image.shape[1]), means=trace_shape) # horne extraction with spatial_profile=='interpolated_profile' # also tests that non-default parameters in the input dictionary format work ex = HorneExtract( image.data, trace, spatial_profile={ "name": "interpolated_profile", "n_bins_interpolated_profile": 3, "interp_degree_interpolated_profile": (1, 1), }, bkgrd_prof=None, variance=np.ones(image.data.shape), ) # need to run produce extract.spectrum to access _interp_spatial_prof ex.spectrum # evaulate interpolated profile on entire grid interp_prof = ex._interp_spatial_prof(np.arange(ncols), np.arange(nrows)).T # the shifting position and amplitude should be accounted for, so the fit # spatial profile should just represent the shape as a function of # wavelength in this case, that is a gaussian with the area normalized at # each wavelength and a constant mean position # make sure that the fit spatial prof is normalized correctly assert_quantity_allclose(np.sum(interp_prof, axis=0), 1.0) # and that shifts in trace position are accounted for (to integer level) assert np.all(np.argmax(interp_prof, axis=0) == nrows // 2) def test_horne_interpolated_nbins_fails(mk_test_img): # make sure that HorneProfile spatial_profile='interpolated_profile' correctly # fails when the number of samples is greater than the size of the image image = mk_test_img trace = FlatTrace(image, 5) with pytest.raises(ValueError): ex = HorneExtract( image.data, trace, spatial_profile={"name": "interpolated_profile", "n_bins_interpolated_profile": 100}, ) ex.spectrum class TestMasksExtract: def mk_flat_gauss_img(self, nrows=200, ncols=160, nan_slices=None, add_noise=True): """ Makes a flat gaussian image for testing, with optional added gaussian nosie and optional data values set to NaN. Variance is included, which is required by HorneExtract. Returns a Spectrum1D with flux, spectral axis, and uncertainty. """ sigma_pix = 4 col_model = models.Gaussian1D(amplitude=1, mean=nrows / 2, stddev=sigma_pix) spec2dvar = np.ones((nrows, ncols)) noise = 0 if add_noise: np.random.seed(7) sigma_noise = 1 noise = np.random.normal(scale=sigma_noise, size=(nrows, ncols)) index_arr = np.tile(np.arange(nrows), (ncols, 1)) img = col_model(index_arr.T) + noise if nan_slices: # add nans in data for s in nan_slices: img[s] = np.nan wave = np.arange(0, img.shape[1], 1) objectspec = Spectrum1D( spectral_axis=wave * u.m, flux=img * u.Jy, uncertainty=VarianceUncertainty(spec2dvar * u.Jy * u.Jy), ) return objectspec def test_boxcar_fully_masked(self): """ Test that the appropriate error is raised by `BoxcarExtract` when image is fully masked/NaN. """ return img = self.mk_flat_gauss_img() trace = FitTrace(img) with pytest.raises(ValueError, match="Image is fully masked."): # fully NaN image img = np.zeros((4, 5)) * np.nan Background(img, traces=trace, width=2) with pytest.raises(ValueError, match="Image is fully masked."): # fully masked image (should be equivalent) img = NDData(np.ones((4, 5)), mask=np.ones((4, 5))) Background(img, traces=trace, width=2) # Now test that an image that isn't fully masked, but is fully masked # within the window determined by `width`, produces the correct result msg = "Image is fully masked within background window determined by `width`." with pytest.raises(ValueError, match=msg): img = self.mk_img(nrows=12, ncols=12, nan_slices=[np.s_[3:10, :]]) Background(img, traces=FlatTrace(img, 6), width=7) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_image_parsing.py0000644000175100001660000000760014764317313022761 0ustar00runnerdockerimport numpy as np from astropy import units as u from specutils import Spectrum1D from specreduce.core import _ImageParser from specreduce.extract import HorneExtract from specreduce.tracing import FlatTrace # (for use inside tests) 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") # do key's fluxes match its comparison's fluxes? assert np.allclose(collection[key].data, collection[compare].data), (f"images '{key}' and " f"'{compare}' have unequal " "flux values") # if the image came with a spectral axis, was it kept? if not, was the # default spectral axis in pixels applied? sax_provided = hasattr(all_images[key], 'spectral_axis') assert np.allclose(collection[key].spectral_axis, (all_images[key].spectral_axis if sax_provided else sax_def)), (f"spectral axis of image '{key}' does " f"not match {'input' if sax_provided else 'default'}") # if the image came with an uncertainty, was it kept? if not, was the # default uncertainty created? unc_provided = hasattr(all_images[key], 'uncertainty') assert np.allclose(collection[key].uncertainty.array, (all_images[key].uncertainty.array if unc_provided else unc_def)), (f"uncertainty of image '{key}' does " f"not match {'input' if unc_provided else 'default'}") # were masks created despite none being given? (all indices should be False) assert (getattr(collection[key], 'mask', None) is not None), f"no mask was created for image '{key}'" assert np.all(collection[key].mask == 0), ("mask not all False " f"for image '{key}'") # test consistency of general image parser results def test_parse_general(all_images): all_images_parsed = {k: _ImageParser()._parse_image(im) for k, im in all_images.items()} for key in all_images_parsed.keys(): compare_images(all_images, key, all_images_parsed) # use verified general image parser results to check HorneExtract's image parser 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()} for key, col in images_collection.items(): img = all_images[key] col['general'] = _ImageParser()._parse_image(img) if hasattr(all_images[key], 'uncertainty'): defaults = {} else: # save default values of attributes used in general parser when # they are not available from the image object. HorneExtract always # requires a variance, so it's chosen here to be on equal footing # with the general case defaults = {'variance': unc_def, 'mask': ~np.isfinite(img), 'unit': getattr(img, 'unit', u.DN)} col[key] = HorneExtract(img, FlatTrace(img, 2))._parse_image(img, **defaults) compare_images(all_images, key, col, compare='general') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_line_matching.py0000644000175100001660000000764014764317313022761 0ustar00runnerdockerimport pytest import numpy as np from astropy.wcs import WCS from astropy.modeling import models from astropy.nddata import StdDevUncertainty from specutils import Spectrum1D from specutils.fitting import fit_generic_continuum from specreduce.extract import BoxcarExtract from specreduce.utils.synth_data import make_2d_arc_image from specreduce.tracing import FlatTrace from specreduce.calibration_data import load_pypeit_calibration_lines from specreduce.line_matching import match_lines_wcs, find_arc_lines @pytest.fixture def mk_test_data(): """ Create test data for the line matching routines. """ non_linear_header = { 'CTYPE1': 'AWAV-GRA', # Grating dispersion function with air wavelengths 'CUNIT1': 'Angstrom', # Dispersion units 'CRPIX1': 519.8, # Reference pixel [pix] 'CRVAL1': 7245.2, # Reference value [Angstrom] 'CDELT1': 2.956, # Linear dispersion [Angstrom/pix] 'PV1_0': 4.5e5, # Grating density [1/m] 'PV1_1': 1, # Diffraction order 'PV1_2': 27.0, # Incident angle [deg] 'PV1_3': 1.765, # Reference refraction 'PV1_4': -1.077e6, # Refraction derivative [1/m] 'CTYPE2': 'PIXEL', # Spatial detector coordinates 'CUNIT2': 'pix', # Spatial units 'CRPIX2': 1, # Reference pixel 'CRVAL2': 0, # Reference value 'CDELT2': 1 # Spatial units per pixel } linear_header = { 'CTYPE1': 'AWAV', # Grating dispersion function with air wavelengths 'CUNIT1': 'Angstrom', # Dispersion units 'CRPIX1': 519.8, # Reference pixel [pix] 'CRVAL1': 7245.2, # Reference value [Angstrom] 'CDELT1': 2.956, # Linear dispersion [Angstrom/pix] 'CTYPE2': 'PIXEL', # Spatial detector coordinates 'CUNIT2': 'pix', # Spatial units 'CRPIX2': 1, # Reference pixel 'CRVAL2': 0, # Reference value 'CDELT2': 1 # Spatial units per pixel } non_linear_wcs = WCS(header=non_linear_header) linear_wcs = WCS(header=linear_header) tilt_mod = models.Legendre1D(degree=2, c0=50, c1=0, c2=100) match_im = make_2d_arc_image( nx=1400, ny=1024, linelists=['HeI', 'NeI'], wcs=linear_wcs, line_fwhm=5, tilt_func=tilt_mod, amplitude_scale=5e-4 ) arclist = load_pypeit_calibration_lines(['HeI', 'NeI'])['wavelength'] trace = FlatTrace(match_im, 512) arc_sp = BoxcarExtract(match_im, trace, width=5).spectrum arc_sp.uncertainty = StdDevUncertainty(np.sqrt(arc_sp.flux).value) continuum = fit_generic_continuum(arc_sp, median_window=51) arc_sub = Spectrum1D( spectral_axis=arc_sp.spectral_axis, flux=arc_sp.flux - continuum(arc_sp.spectral_axis) ) arc_sub.uncertainty = arc_sp.uncertainty return linear_wcs, non_linear_wcs, arclist, arc_sub @pytest.mark.remote_data @pytest.mark.filterwarnings("ignore:No observer defined on WCS") @pytest.mark.filterwarnings("ignore:Model is linear in parameters") def test_find_arc_lines(mk_test_data): """ Test the find_arc_lines routine. """ _, _, _, arc_sub = mk_test_data lines = find_arc_lines(arc_sub, fwhm=5, window=5, noise_factor=5) assert len(lines) > 1 @pytest.mark.remote_data @pytest.mark.filterwarnings("ignore:No observer defined on WCS") @pytest.mark.filterwarnings("ignore:Model is linear in parameters") def test_match_lines_wcs(mk_test_data): """ Test the match_lines_wcs routine. """ linear_wcs, _, arclist, arc_sub = mk_test_data lines = find_arc_lines(arc_sub, fwhm=5, window=5, noise_factor=5) matched_lines = match_lines_wcs( pixel_positions=lines['centroid'], catalog_wavelengths=arclist, spectral_wcs=linear_wcs.spectral, tolerance=5 ) assert len(matched_lines) > 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_linelists.py0000644000175100001660000000523414764317313022163 0ustar00runnerdockerimport pytest from specreduce.calibration_data import load_pypeit_calibration_lines @pytest.mark.remote_data def test_pypeit_single(): """ Test to load a single linelist from ``pypeit`` by passing a string. """ line_tab = load_pypeit_calibration_lines('HeI', cache=True, show_progress=False) assert line_tab is not None assert "HeI" in line_tab['ion'] assert sorted(list(line_tab.columns)) == [ 'Instr', 'NIST', 'Source', 'amplitude', 'ion', 'wavelength' ] @pytest.mark.remote_data def test_pypeit_list(): """ Test to load and combine a set of linelists from ``pypeit`` by passing a list. """ line_tab = load_pypeit_calibration_lines(['HeI', 'NeI'], cache=True, show_progress=False) assert line_tab is not None assert "HeI" in line_tab['ion'] assert "NeI" in line_tab['ion'] @pytest.mark.remote_data def test_pypeit_comma_list(): """ Test to load and combine a set of linelists from ``pypeit`` by passing a comma-separated list. """ line_tab = load_pypeit_calibration_lines("HeI, NeI", cache=True, show_progress=False) assert line_tab is not None assert "HeI" in line_tab['ion'] assert "NeI" in line_tab['ion'] @pytest.mark.remote_data def test_pypeit_nonexisting_lamp(): """ Test to make sure a warning is raised if the lamp list includes a bad lamp name. """ with pytest.warns(UserWarning, match='NeJ not in the list'): load_pypeit_calibration_lines(["HeI", "NeJ"], cache=True, show_progress=False) @pytest.mark.remote_data def test_pypeit_empty(): """ Test to make sure None is returned if an empty list is passed. """ with pytest.warns(UserWarning, match='No calibration lines'): line_tab = load_pypeit_calibration_lines([], cache=True, show_progress=False) assert line_tab is None @pytest.mark.remote_data def test_pypeit_none(): """ Test to make sure None is returned if calibration lamp list is None. """ line_tab = load_pypeit_calibration_lines(None, cache=True, show_progress=False) assert line_tab is None @pytest.mark.remote_data def test_pypeit_input_validation(): """ Check that bad inputs for ``pypeit`` linelists raise the appropriate warnings and exceptions """ 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.") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_mask_treatment.py0000644000175100001660000001353014764317313023171 0ustar00runnerdockerfrom copy import deepcopy import pytest import numpy as np from astropy.units import DN from astropy.nddata import NDData from specreduce.core import _ImageParser def mk_image(): image = np.ones((3, 5)) mask = np.zeros(image.shape, dtype=bool) image[1, 3] = np.nan mask[[0, 1], [1, 0]] = 1 return NDData(image * DN, mask=mask) def test_bad_option(): image = mk_image() with pytest.raises(ValueError, match="'mask_treatment' must be one of"): _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="bad") def test_bad_mask_type(): image = mk_image() image.mask = image.mask.astype(float) with pytest.raises(ValueError, match="'mask' must be a boolean or integer array."): _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="apply") def test_apply(): image = mk_image() parsed_image = _ImageParser()._parse_image(deepcopy(image), disp_axis=1, mask_treatment="apply") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[[0, 1, 1], [1, 0, 3]] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(deepcopy(image), disp_axis=1, mask_treatment="apply") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[1, 3] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_ignore(): image = mk_image() parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="ignore") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="ignore") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_propagate(): image = mk_image() parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="propagate") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[:, [0, 1, 3]] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="propagate") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[:, 3] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_zero_fill(): image = mk_image() parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="zero_fill") image_true = np.ones(image.data.shape) image_true[[0, 1, 1], [1, 0, 3]] = 0 np.testing.assert_array_equal(parsed_image.data, image_true) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="zero_fill") image_true = np.ones(image.data.shape) image_true[1, 3] = 0 np.testing.assert_array_equal(parsed_image.data, image_true) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_nan_fill(): image = mk_image() parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="nan_fill") image_true = np.ones(image.data.shape) image_true[[0, 1, 1], [1, 0, 3]] = np.nan np.testing.assert_array_equal(parsed_image.data, image_true) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="nan_fill") image_true = np.ones(image.data.shape) image_true[1, 3] = np.nan np.testing.assert_array_equal(parsed_image.data, image_true) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_apply_nan_only(): image = mk_image() parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="apply_nan_only") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[1, 3] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="apply_nan_only") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[1, 3] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_apply_mask_only(): image = mk_image() parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="apply_mask_only") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) mask_true[[0, 1], [1, 0]] = 1 np.testing.assert_array_equal(parsed_image.mask, mask_true) image.mask = None parsed_image = _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="apply_mask_only") np.testing.assert_array_equal(parsed_image.data, image.data) mask_true = np.zeros(image.data.shape, dtype=bool) np.testing.assert_array_equal(parsed_image.mask, mask_true) def test_fully_masked(): image = mk_image() image.mask[:] = 1 with pytest.raises(ValueError, match="Image is fully masked."): _ImageParser()._parse_image(image, disp_axis=1, mask_treatment="apply") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_specphot_stds.py0000644000175100001660000000207614764317313023040 0ustar00runnerdockerimport pytest from astropy.utils.exceptions import AstropyUserWarning from specreduce.calibration_data import load_MAST_calspec, load_onedstds @pytest.mark.remote_data def test_load_MAST(): sp = load_MAST_calspec("g191b2b_005.fits", show_progress=False) assert sp is not None assert len(sp.spectral_axis) > 0 @pytest.mark.remote_data def test_load_MAST_bad_filename(): with pytest.warns(AstropyUserWarning, match="Downloading of"): sp = load_MAST_calspec("j191b2b_005.fits", show_progress=False) assert sp is None @pytest.mark.remote_data def test_load_onedstds(): sp = load_onedstds() assert sp is not None assert len(sp.spectral_axis) > 0 @pytest.mark.remote_data def test_load_onedstds_bad_dataset(): with pytest.warns(AstropyUserWarning, match="Specfied dataset,"): sp = load_onedstds("snffactory") assert sp is None @pytest.mark.remote_data def test_load_onedstds_bad_specfile(): with pytest.warns(AstropyUserWarning, match="Can't load"): sp = load_onedstds(specfile="FG131.dat") assert sp is None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_synth_data.py0000644000175100001660000001226314764317313022313 0ustar00runnerdockerimport pytest from astropy import units as u from astropy.modeling import models from astropy.nddata import CCDData from astropy.wcs import WCS from specreduce.utils.synth_data import make_2d_trace_image, make_2d_arc_image, make_2d_spec_image def test_make_2d_trace_image(): ccdim = make_2d_trace_image( nx=3000, ny=1000, background=5, trace_center=None, trace_order=3, trace_coeffs={'c0': 0, 'c1': 50, 'c2': 100}, profile=models.Gaussian1D(amplitude=100, stddev=10) ) assert ccdim.data.shape == (1000, 3000) assert isinstance(ccdim, CCDData) @pytest.mark.remote_data @pytest.mark.filterwarnings("ignore:No observer defined on WCS") def test_make_2d_arc_image_defaults(): ccdim = make_2d_arc_image() assert isinstance(ccdim, CCDData) @pytest.mark.remote_data @pytest.mark.filterwarnings("ignore:No observer defined on WCS") def test_make_2d_arc_pass_wcs(): nx = 3000 ny = 1000 wave_unit = u.Angstrom extent = [3000, 6000] # test passing a valid WCS with dispersion along X wcs = WCS(naxis=2) wcs.wcs.ctype[0] = 'WAVE' wcs.wcs.ctype[1] = 'PIXEL' wcs.wcs.cunit[0] = wave_unit wcs.wcs.cunit[1] = u.pixel wcs.wcs.crval[0] = extent[0] wcs.wcs.cdelt[0] = (extent[1] - extent[0]) / nx wcs.wcs.crval[1] = 0 wcs.wcs.cdelt[1] = 1 ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=None, wave_unit=None, wcs=wcs ) assert ccdim.data.shape == (1000, 3000) assert isinstance(ccdim, CCDData) # test passing a tilt model tilt_model = models.Chebyshev1D(degree=2, c0=50, c1=0, c2=100) ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=None, wave_unit=None, wcs=wcs, tilt_func=tilt_model ) assert ccdim.data.shape == (1000, 3000) assert isinstance(ccdim, CCDData) # make sure WCS without spectral axis gets rejected wcs.wcs.ctype[0] = 'PIXEL' assert wcs.spectral.naxis == 0 with pytest.raises(ValueError, match='Provided WCS must have a spectral axis'): ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=None, wave_unit=None, wcs=wcs ) # test passing valid WCS with dispersion along Y while using air wavelengths wcs = WCS(naxis=2) wcs.wcs.ctype[1] = 'AWAV' wcs.wcs.ctype[0] = 'PIXEL' wcs.wcs.cunit[1] = wave_unit wcs.wcs.cunit[0] = u.pixel wcs.wcs.crval[1] = extent[0] wcs.wcs.cdelt[1] = (extent[1] - extent[0]) / nx wcs.wcs.crval[0] = 0 wcs.wcs.cdelt[0] = 1 ccdim = make_2d_arc_image( nx=ny, ny=nx, extent=None, wave_unit=None, wave_air=True, wcs=wcs, tilt_func=tilt_model ) assert ccdim.data.shape == (3000, 1000) assert isinstance(ccdim, CCDData) # make sure no WCS and no extent gets rejected with pytest.raises(ValueError, match='Must specify either a wavelength extent or a WCS'): ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=None, wave_unit=None, wcs=None ) # make sure if extent is provided, it has the right length with pytest.raises(ValueError, match='Wavelength extent must be of length 2'): ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=[1, 2, 3], wave_unit=None, wcs=None ) # make sure a 1D WCS gets rejected wcs = WCS(naxis=1) wcs.wcs.ctype[0] = 'WAVE' wcs.wcs.cunit[0] = wave_unit wcs.wcs.crval[0] = extent[0] wcs.wcs.cdelt[0] = (extent[1] - extent[0]) / nx with pytest.raises(ValueError, match='WCS must have NAXIS=2 for a 2D image'): ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=None, wave_unit=None, wcs=wcs ) # make sure a WCS with no spectral axis gets rejected wcs = WCS(naxis=2) wcs.wcs.ctype[1] = 'PIXEL' wcs.wcs.ctype[0] = 'PIXEL' wcs.wcs.cunit[1] = u.pixel wcs.wcs.cunit[0] = u.pixel wcs.wcs.crval[1] = extent[0] wcs.wcs.cdelt[1] = (extent[1] - extent[0]) / nx wcs.wcs.crval[0] = 0 wcs.wcs.cdelt[0] = 1 with pytest.raises(ValueError, match='Provided WCS must have a spectral axis'): ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=None, wave_unit=None, wcs=wcs ) # make sure invalid wave_unit is caught with pytest.raises(ValueError, match='Wavelength unit must be a length unit'): ccdim = make_2d_arc_image( nx=nx, ny=ny, extent=[100, 300], wave_unit=u.pixel ) # make sure a non-polynomial tilt_func gets rejected with pytest.raises( ValueError, match='The only tilt functions currently supported are 1D polynomials' ): ccdim = make_2d_arc_image( tilt_func=models.Gaussian1D ) @pytest.mark.remote_data @pytest.mark.filterwarnings("ignore:No observer defined on WCS") def test_make_2d_spec_image_defaults(): ccdim = make_2d_spec_image() assert isinstance(ccdim, CCDData) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_tracing.py0000644000175100001660000004702014764317313021603 0ustar00runnerdockerimport numpy as np import pytest from astropy.modeling import fitting, models from astropy.nddata import NDData import astropy.units as u from specreduce.utils.synth_data import make_2d_trace_image from specreduce.tracing import Trace, FlatTrace, ArrayTrace, FitTrace IM = make_2d_trace_image() def mk_img(nrows=200, ncols=160, nan_slices=None, add_noise=True): """ Makes a gaussian image for testing, with optional added gaussian nosie and optional data values set to NaN. """ # NOTE: Will move this to a fixture at some point. sigma_pix = 4 col_model = models.Gaussian1D(amplitude=1, mean=nrows / 2, stddev=sigma_pix) noise = 0 if add_noise: np.random.seed(7) sigma_noise = 1 noise = np.random.normal(scale=sigma_noise, size=(nrows, ncols)) index_arr = np.tile(np.arange(nrows), (ncols, 1)) img = col_model(index_arr.T) + noise if nan_slices: # add nans in data for s in nan_slices: img[s] = np.nan return img * u.DN # test basic trace class def test_basic_trace(): t_pos = IM.shape[0] / 2 t = Trace(IM) assert t[0] == t_pos assert t[0] == t[-1] assert t.shape[0] == IM.shape[1] t.shift(100) assert t[0] == 600.0 t.shift(-1000) assert np.ma.is_masked(t[0]) # test flat traces def test_flat_trace(): t = FlatTrace(IM, 550.0) assert t.trace_pos == 550 assert t[0] == 550.0 assert t[0] == t[-1] t.set_position(400.0) assert t[0] == 400.0 def test_negative_flat_trace_err(): # make sure correct error is raised when trying to create FlatTrace with # negative trace_pos with pytest.raises(ValueError, match="must be positive."): FlatTrace(IM, trace_pos=-1) with pytest.raises(ValueError, match="must be positive."): FlatTrace(IM, trace_pos=0) # test array traces def test_array_trace(): arr = np.ones_like(IM[0]) * 550.0 t = ArrayTrace(IM, arr) assert t[0] == 550.0 assert t[0] == t[-1] t.shift(100) assert t[0] == 650.0 t.shift(-1000) assert np.ma.is_masked(t[0]) arr_long = np.ones(5000) * 550.0 t_long = ArrayTrace(IM, arr_long) assert t_long.shape[0] == IM.shape[1] arr_short = np.ones(50) * 550.0 t_short = ArrayTrace(IM, arr_short) assert t_short[0] == 550.0 assert np.ma.is_masked(t_short[-1]) assert t_short.shape[0] == IM.shape[1] # make sure nonfinite data in input `trace` is masked arr[0:5] = np.nan t = ArrayTrace(IM, arr) assert np.all(t.trace.mask[0:5]) assert np.all(t.trace.mask[5:] == 0) # test fitted traces @pytest.mark.filterwarnings("ignore:Model is linear in parameters") def test_fit_trace(): # create image (process adapted from compare_extractions.ipynb) np.random.seed(7) nrows = 200 ncols = 160 sigma_pix = 4 sigma_noise = 1 col_model = models.Gaussian1D(amplitude=1, mean=nrows / 2, stddev=sigma_pix) noise = np.random.normal(scale=sigma_noise, size=(nrows, ncols)) index_arr = np.tile(np.arange(nrows), (ncols, 1)) img = col_model(index_arr.T) + noise # calculate trace on normal image t = FitTrace(img, bins=20) # test shifting shift_up = int(-img.shape[0] / 4) t_shift_up = t.trace + shift_up shift_out = img.shape[0] t.shift(shift_up) assert np.sum(t.trace == t_shift_up) == t.trace.size, "valid shift failed" t.shift(shift_out) assert t.trace.mask.all(), "invalid values not masked" # test peak_method and trace_model options tg = FitTrace(img, bins=20, peak_method="gaussian", trace_model=models.Legendre1D(3)) tc = FitTrace(img, bins=20, peak_method="centroid", trace_model=models.Chebyshev1D(2)) tm = FitTrace(img, bins=20, peak_method="max", trace_model=models.Spline1D(degree=3)) # traces should all be close to 100 # (values may need to be updated on changes to seed, noise, etc.) assert np.max(abs(tg.trace - 100)) < sigma_pix assert np.max(abs(tc.trace - 100)) < 3 * sigma_pix assert np.max(abs(tm.trace - 100)) < 6 * sigma_pix with pytest.raises(ValueError): t = FitTrace(img, peak_method="invalid") window = 10 guess = int(nrows / 2) img_win_nans = img.copy() img_win_nans[guess - window : guess + window] = np.nan # ensure float bin values trigger a warning but no issues otherwise with pytest.warns(UserWarning, match="TRACE: Converting bins to int"): FitTrace(img, bins=20.0, trace_model=models.Polynomial1D(2)) # ensure non-equipped models are rejected with pytest.raises(ValueError, match=r"trace_model must be one of"): FitTrace(img, trace_model=models.Hermite1D(3)) # ensure a bin number below 4 is rejected with pytest.raises(ValueError, match="bins must be >= 4"): FitTrace(img, bins=3) # ensure a bin number below degree of trace model is rejected with pytest.raises(ValueError, match="bins must be > "): FitTrace(img, bins=4, trace_model=models.Chebyshev1D(5)) # ensure number of bins greater than number of dispersion pixels is rejected with pytest.raises(ValueError, match=r"bins must be <"): FitTrace(img, bins=ncols + 1) @pytest.mark.filterwarnings("ignore:The fit may be unsuccessful") @pytest.mark.filterwarnings("ignore:Model is linear in parameters") class TestMasksTracing: """ There are three currently implemented options for masking in FitTrace: filter, omit, and zero_fill. Trace, FlatTrace, and ArrayTrace do not have `mask_treatment` options as input because masked/nonfinite values in the data are not relevant for those trace types as they are not affected by masked input data. The tests in this class test masking options for FitTrace, as well as some basic tests (errors, etc) for the other trace types. """ def test_flat_and_basic_trace_mask(self): """ Mask handling is not relevant for basic and flat trace - nans or masked values in the input image will not impact the trace value. The attribute should be initialized though, and be one of the valid options ([None] in this case), for consistancy with all other Specreduce operations. Note that unlike FitTrace, a fully-masked image should NOT result in an error raised because the trace does not depend on the data. """ img = mk_img(nrows=5, ncols=5) basic_trace = Trace(img) assert basic_trace.mask_treatment is None flat_trace = FlatTrace(img, trace_pos=2) assert flat_trace.mask_treatment is None arr = [1, 2, np.nan, 3, 4] array_trace = ArrayTrace(img, arr) assert array_trace.mask_treatment is None def test_array_trace_masking(self): """ The `trace` input to ArrayTrace can be a masked array, or an array containing nonfinite data which will be converted to a masked array. Additionally, if any padding needs to be added, the returned trace will be a masked array. Otherwise, it should be a regular array. Even though an ArrayTrace may have nans or masked values in the input 1D array for the trace, `mask_treatment_method` refers to how masked values in the input data should be treated. Nans / masked values passed in the array trace should be considered intentional, so also test that `mask_treatment` is initialized to None. """ img = mk_img(nrows=10, ncols=10) # non-finite data in input trace should be masked out trace_arr = np.array((1, 2, np.nan, 4, 5)) array_trace = ArrayTrace(img, trace_arr) assert array_trace.trace.mask[2] assert isinstance(array_trace.trace, np.ma.MaskedArray) # and combined with any input masked data trace_arr = np.ma.MaskedArray([1, 2, np.nan, 4, 5], mask=[1, 0, 0, 0, 0]) array_trace = ArrayTrace(img, trace_arr) assert array_trace.trace.mask[0] assert array_trace.trace.mask[2] assert isinstance(array_trace.trace, np.ma.MaskedArray) # check that mask_treatment is None as there are no valid choices assert array_trace.mask_treatment is None # check that if array is fully finite and not masked, that the returned # trace is a normal array, not a masked array trace = ArrayTrace(img, np.ones(100)) assert isinstance(trace.trace, np.ndarray) assert not isinstance(trace.trace, np.ma.MaskedArray) # ensure correct warning is raised when entire trace is masked. trace_arr = np.ma.MaskedArray([1, 2, np.nan, 4, 5], mask=[1, 1, 0, 1, 1]) with pytest.warns(UserWarning, match=r"Entire trace array is masked."): array_trace = ArrayTrace(img, trace_arr) def test_fit_trace_fully_masked_image(self): """ Test that the correct warning is raised when a fully masked image is encountered. Also test that when a non-fully masked image is provided, but `window` is set and the image is fully masked within that window, that the correct error is raised. """ # make simple gaussian image. img = mk_img() # create same-shaped variations of image with nans in data array # which will be masked within FitTrace. nrows = 200 ncols = 160 img_all_nans = np.tile(np.nan, (nrows, ncols)) # error on trace of all-nan image with pytest.raises(ValueError, match=r"Image is fully masked. Check for invalid values."): FitTrace(img_all_nans) window = 10 guess = int(nrows / 2) img_win_nans = img.copy() img_win_nans[guess - window : guess + window] = np.nan # error on trace of otherwise valid image with all-nan window around guess with pytest.raises(ValueError, match="pixels in window region are masked"): FitTrace(img_win_nans, guess=guess, window=window) def test_fit_trace_fully_masked_columns_warn_msg(self): """ Test that the correct warning message is raised when fully masked columns (in a not-fully-masked image) are encountered in FitTrace. These columns will be set to NaN and filtered from the final all-bin fit (as tested in test_fit_trace_fully_masked_cols), but a warning message is raised. """ img = mk_img() # test that warning (dependent on choice of `peak_method`) is raised when a # few bins are masked, and that theyre listed individually mask = np.zeros(img.shape, dtype=bool) mask[:, 100] = 1 mask[:, 20] = 1 mask[:, 30] = 1 nddat = NDData(data=img, mask=mask, unit=u.DN) match_str = "All pixels in bins 20, 30, 100 are fully masked. Setting bin peaks to NaN." with pytest.warns(UserWarning, match=match_str): FitTrace(nddat, peak_method="max") with pytest.warns(UserWarning, match=match_str): FitTrace(nddat, peak_method="centroid") with pytest.warns(UserWarning, match=match_str): FitTrace(nddat, peak_method="gaussian") # and when many bins are masked, that the message is consolidated mask = np.zeros(img.shape, dtype=bool) mask[:, 0:21] = 1 nddat = NDData(data=img, mask=mask, unit=u.DN) with pytest.warns( UserWarning, match="All pixels in bins " "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ..., 20 are " "fully masked. Setting bin peaks to NaN.", ): FitTrace(nddat) @pytest.mark.filterwarnings("ignore:All pixels in bins") @pytest.mark.parametrize("mask_treatment", ["apply", "propagate", "apply_nan_only"]) def test_fit_trace_fully_masked_cols(self, mask_treatment): """ Create a test image with some fully-nan/masked columns, and test that when the final fit to all bin peaks is done for the trace, that these fully-masked columns are set to NaN and filtered during the final all-bin fit. Ignore the warning that is produced when this case is encountered (that is tested in `test_fit_trace_fully_masked_cols_warn_msg`.) """ img = mk_img(nrows=10, ncols=11) # set some columns fully to nan, which will be masked out img[:, 7] = np.nan img[:, 4] = np.nan img[:, 0] = np.nan # also create an image that doesn't have nans in the data, but # is masked in the same locations, to make sure that is equivilant. # test peak_method = 'max' truth = [ 1.6346154, 2.2371795, 2.8397436, 3.4423077, 4.0448718, 4.6474359, 5.25, 5.8525641, 6.4551282, 7.0576923, 7.6602564, ] max_trace = FitTrace(img, peak_method="max", mask_treatment=mask_treatment) np.testing.assert_allclose(truth, max_trace.trace, atol=0.1) # peak_method = 'gaussian' truth = [ 1.947455, 2.383634, 2.8198131, 3.2559921, 3.6921712, 4.1283502, 4.5645293, 5.0007083, 5.4368874, 5.8730665, 6.3092455, ] max_trace = FitTrace(img, peak_method="gaussian", mask_treatment=mask_treatment) np.testing.assert_allclose(truth, max_trace.trace, atol=0.1) # peak_method = 'centroid' truth = [ 2.5318835, 2.782069, 3.0322546, 3.2824402, 3.5326257, 3.7828113, 4.0329969, 4.2831824, 4.533368, 4.7835536, 5.0337391, ] max_trace = FitTrace(img, peak_method="centroid", mask_treatment=mask_treatment) np.testing.assert_allclose(truth, max_trace.trace, atol=0.1) @pytest.mark.filterwarnings("ignore:All pixels in bins") @pytest.mark.parametrize( "peak_method,expected", [ ("max", [5.0, 3.0, 5.0, 5.0, 7.0, 5.0, np.nan, 5.0, 5.0, 5.0, 2.0, 5.0]), ("gaussian", [5.0, 5.0, 5.0, 5.0, 5.0, 5.0, np.nan, 5.0, 5.0, 5.0, 1.576, 5.0]), ( "centroid", [ 4.27108332, 2.24060342, 4.27108332, 4.27108332, 6.66827608, 4.27108332, np.nan, 4.27108332, 4.27108332, 4.27108332, 1.19673467, 4.27108332, ], ), ], ) def test_mask_treatment_apply(self, peak_method, expected): """ Test for mask_treatment=apply for FitTrace. With this masking option, masked and non-finite data should be filtered when determining bin/column peak. Fully masked bins should be omitted from the final all-bin-peak fit for the Trace. Parametrized over different `peak_method` options. """ # Make an image with some non-finite values. image1 = mk_img( nan_slices=[np.s_[4:8, 1:2], np.s_[2:7, 4:5], np.s_[:, 6:7], np.s_[3:9, 10:11]], nrows=10, ncols=12, add_noise=False, ) # Also make an image that doesn't have nonf data values, but has masked # values at the same locations, to make sure they give the same results. mask = ~np.isfinite(image1) dat = mk_img(nrows=10, ncols=12, add_noise=False) image2 = NDData(dat, mask=mask) for imgg in [image1, image2]: # run FitTrace, with the testing-only flag _save_bin_peaks_testing set # to True to return the bin peak values before fitting the trace trace = FitTrace(imgg, peak_method=peak_method, _save_bin_peaks_testing=True) x_bins, y_bins = trace._bin_peaks_testing np.testing.assert_allclose(y_bins, expected, atol=0.1) # check that final fit to all bins, accouting for fully-masked bins, # matches the trace fitter = fitting.LMLSQFitter() mask = np.isfinite(y_bins) all_bin_fit = fitter(trace.trace_model, x_bins[mask], y_bins[mask]) all_bin_fit = all_bin_fit((np.arange(12))) np.testing.assert_allclose(trace.trace, all_bin_fit) @pytest.mark.filterwarnings("ignore:All pixels in bins") @pytest.mark.parametrize("peak_method", ["max", "gaussian", "centroid"]) def test_mask_treatment_unsupported(self, peak_method): """ Test to ensure the unsupported mask treatment methods for FitTrace raise a `ValueError`. Parametrized over different `peak_method` options. """ image = mk_img( nan_slices=[np.s_[4:8, 1:2], np.s_[2:7, 4:5], np.s_[:, 6:7], np.s_[3:9, 10:11]], nrows=10, ncols=12, add_noise=False, ) for method in "ignore", "zero_fill", "nan_fill", "apply_mask_only": with pytest.raises(ValueError): FitTrace(image, peak_method=peak_method, mask_treatment=method) @pytest.mark.filterwarnings("ignore:All pixels in bins") @pytest.mark.parametrize( "peak_method,expected", [ ("max", [5.0, np.nan, 5.0, 5.0, np.nan, 5.0, np.nan, 5.0, 5.0, 5.0, np.nan, 5.0]), ("gaussian", [5.0, np.nan, 5.0, 5.0, np.nan, 5.0, np.nan, 5.0, 5.0, 5.0, np.nan, 5.0]), ( "centroid", [ 4.27108332, np.nan, 4.27108332, 4.27108332, np.nan, 4.27108332, np.nan, 4.27108332, 4.27108332, 4.27108332, np.nan, 4.27108332, ], ), ], ) def test_mask_treatment_propagate(self, peak_method, expected): """ Test for mask_treatment=`propagate` for FitTrace. Columns (assuming disp_axis==1) with any masked data values will be fully masked and therefore not contribute to the bin peaks. Parametrized over different `peak_method` options. """ # Make an image with some non-finite values. image1 = mk_img( nan_slices=[np.s_[4:8, 1:2], np.s_[2:7, 4:5], np.s_[:, 6:7], np.s_[3:9, 10:11]], nrows=10, ncols=12, add_noise=False, ) # Also make an image that doesn't have non-finite data values, but has masked # values at the same locations, to make sure those cases are equivalent mask = ~np.isfinite(image1) dat = mk_img(nrows=10, ncols=12, add_noise=False) image2 = NDData(dat, mask=mask) for imgg in [image1, image2]: # run FitTrace, with the testing-only flag _save_bin_peaks_testing set # to True to return the bin peak values before fitting the trace trace = FitTrace( imgg, peak_method=peak_method, mask_treatment="propagate", _save_bin_peaks_testing=True, ) x_bins, y_bins = trace._bin_peaks_testing np.testing.assert_allclose(y_bins, expected) # check that final fit to all bins, accouting for fully-masked bins, # matches the trace fitter = fitting.LevMarLSQFitter() mask = np.isfinite(y_bins) all_bin_fit = fitter(trace.trace_model, x_bins[mask], y_bins[mask]) all_bin_fit = all_bin_fit((np.arange(12))) np.testing.assert_allclose(trace.trace, all_bin_fit) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_utils.py0000644000175100001660000001751314764317313021320 0ustar00runnerdockerimport numpy as np import pytest from astropy.modeling import fitting, models from specreduce.tracing import FitTrace from specreduce.utils.utils import measure_cross_dispersion_profile from specutils import Spectrum1D from astropy.nddata import NDData import astropy.units as u def mk_gaussian_img(nrows=20, ncols=16, mean=10, stddev=4): """ Makes a simple horizontal gaussian image.""" # note: this should become a fixture eventually, since other tests use # similar functions to generate test data. np.random.seed(7) col_model = models.Gaussian1D(amplitude=1, mean=mean, stddev=stddev) index_arr = np.tile(np.arange(nrows), (ncols, 1)) return col_model(index_arr.T) def mk_img_non_flat_trace(nrows=40, ncols=100, amp=10, stddev=2): """ Makes an image with a gaussian source that has a non-flat trace dispersed along the x axis. """ spec2d = np.zeros((nrows, ncols)) for ii in range(spec2d.shape[1]): mgaus = models.Gaussian1D(amplitude=amp, mean=(9.+(20/spec2d.shape[1])*ii), stddev=stddev) rg = np.arange(0, spec2d.shape[0], 1) gaus = mgaus(rg) spec2d[:, ii] = gaus return spec2d class TestMeasureCrossDispersionProfile(): @pytest.mark.parametrize('pixel', [None, 1, [1, 2, 3]]) @pytest.mark.parametrize('width', [10, 9]) def test_measure_cross_dispersion_profile(self, pixel, width): """ Basic test for `measure_cross_dispersion_profile`. Parametrized over different options for `pixel` to test using all wavelengths, a single wavelength, and a set of wavelengths, as well as different input types (plain array, quantity, Spectrum1D, and NDData), as well as `width` to use a window of all rows and a smaller window. """ # test a few input formats images = [] mean = 5.0 stddev = 4.0 dat = mk_gaussian_img(nrows=10, ncols=10, mean=mean, stddev=stddev) images.append(dat) # test unitless images.append(dat * u.DN) images.append(NDData(dat * u.DN)) images.append(Spectrum1D(flux=dat * u.DN)) for img in images: # use a flat trace at trace_pos=10, a window of width 10 around the trace # and use all 20 columns in image to create an average (median) # cross dispersion profile cdp = measure_cross_dispersion_profile(img, width=width, pixel=pixel) # make sure that if we fit a gaussian to the measured average profile, # that we get out the same profile that was used to create the image. # this should be exact since theres no noise in the image fitter = fitting.LevMarLSQFitter() mod = models.Gaussian1D() fit_model = fitter(mod, np.arange(width), cdp) assert fit_model.mean.value == np.where(cdp == max(cdp))[0][0] assert fit_model.stddev.value == stddev # test passing in a FlatTrace, and check the profile cdp = measure_cross_dispersion_profile(img, width=width, pixel=pixel) fit_model = fitter(mod, np.arange(width), cdp) assert fit_model.mean.value == np.where(cdp == max(cdp))[0][0] np.testing.assert_allclose(fit_model.stddev.value, stddev) @pytest.mark.filterwarnings("ignore:Model is linear in parameters") def test_cross_dispersion_profile_non_flat_trace(self): """ Test measure_cross_dispersion_profile with a non-flat trace. Tests with 'align_along_trace' set to both True and False, to account for the changing center of the trace and measure the true profile shape, or to 'blur' the profile, respectivley. """ image = mk_img_non_flat_trace() # fit the trace trace_fit = FitTrace(image) # when not aligning along trace and using the entire image # rows for the window, the center of the profile should follow # the shape of the trace peak_locs = [9, 10, 12, 13, 15, 16, 17, 19, 20, 22, 23, 24, 26, 27, 29] for i, pixel in enumerate(range(0, image.shape[1], 7)): profile = measure_cross_dispersion_profile(image, trace=trace_fit, width=None, pixel=pixel, align_along_trace=False, statistic='mean') peak_loc = (np.where(profile == max(profile))[0][0]) assert peak_loc == peak_locs[i] # when align_along_trace = True, the shape of the profile should # not change since (there is some wiggling around though due to the # fact that the trace is rolled to the nearest integer value. this can # be smoothed with an interpolation option later on, but it is 'rough' # for now). In this test case, the peak positions will all either # be at pixel 20 or 21. for i, pixel in enumerate(range(0, image.shape[1], 7)): profile = measure_cross_dispersion_profile(image, trace=trace_fit, width=None, pixel=pixel, align_along_trace=True, statistic='mean') peak_loc = (np.where(profile == max(profile))[0][0]) assert peak_loc in [20, 21] def test_errors_warnings(self): img = mk_gaussian_img(nrows=10, ncols=10) with pytest.raises(ValueError, match='`crossdisp_axis` must be 0 or 1'): measure_cross_dispersion_profile(img, crossdisp_axis=2) with pytest.raises(ValueError, match='`trace` must be Trace object, ' 'number to specify the location ' 'of a FlatTrace, or None to use ' 'center of image.'): measure_cross_dispersion_profile(img, trace='not a trace or a number') with pytest.raises(ValueError, match="`statistic` must be 'median' " "or 'mean'."): measure_cross_dispersion_profile(img, statistic='n/a') with pytest.raises(ValueError, match='Both `pixel` and `pixel_range` ' 'can not be set simultaneously.'): measure_cross_dispersion_profile(img, pixel=2, pixel_range=(2, 3)) with pytest.raises(ValueError, match='`pixels` must be an integer, ' 'or list of integers to specify ' 'where the crossdisperion profile ' 'should be measured.'): measure_cross_dispersion_profile(img, pixel='str') with pytest.raises(ValueError, match='`pixel_range` must be a tuple ' 'of integers.'): measure_cross_dispersion_profile(img, pixel_range=(2, 3, 5)) with pytest.raises(ValueError, match='Pixels chosen to measure cross ' 'dispersion profile are out of ' 'image bounds.'): measure_cross_dispersion_profile(img, pixel_range=(2, 12)) with pytest.raises(ValueError, match='`width` must be an integer, ' 'or None to use all ' 'cross-dispersion pixels.'): measure_cross_dispersion_profile(img, width='.') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tests/test_wavelength_calibration.py0000644000175100001660000001264114764317313024670 0ustar00runnerdockerimport numpy as np import pytest 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 def test_linear_from_list(spec1d): centers = [0, 10, 20, 30] w = [5000, 5100, 5198, 5305]*u.AA test = WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w) spec2 = test.apply_to_spectrum(spec1d) assert_quantity_allclose(spec2.spectral_axis[0], 4998.8*u.AA) assert_quantity_allclose(spec2.spectral_axis[-1], 5495.169999*u.AA) def test_wavelength_from_table(spec1d): centers = [0, 10, 20, 30] w = [5000, 5100, 5198, 5305]*u.AA table = QTable([w], names=["wavelength"]) WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=table) def test_linear_from_table(spec1d): centers = [0, 10, 20, 30] w = [5000, 5100, 5198, 5305]*u.AA table = QTable([centers, w], names=["pixel_center", "wavelength"]) test = WavelengthCalibration1D(spec1d, matched_line_list=table) spec2 = test.apply_to_spectrum(spec1d) assert_quantity_allclose(spec2.spectral_axis[0], 4998.8*u.AA) assert_quantity_allclose(spec2.spectral_axis[-1], 5495.169999*u.AA) def test_poly_from_table(spec1d): # This test is mostly to prove that you can use other models centers = [0, 10, 20, 30, 40] w = [5005, 5110, 5214, 5330, 5438]*u.AA table = QTable([centers, w], names=["pixel_center", "wavelength"]) test = WavelengthCalibration1D(spec1d, matched_line_list=table, input_model=Polynomial1D(2), fitter=LinearLSQFitter()) test.apply_to_spectrum(spec1d) assert_allclose(test.fitted_model.parameters, [5.00477143e+03, 1.03457143e+01, 1.28571429e-02]) def test_replace_spectrum(spec1d, spec1d_with_emission_line): centers = [0, 10, 20, 30]*u.pix w = [5000, 5100, 5198, 5305]*u.AA test = WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w) # Accessing this property causes fits the model and caches the resulting WCS test.wcs assert "wcs" in test.__dict__ # Replace the input spectrum, which should clear the cached properties test.input_spectrum = spec1d_with_emission_line assert "wcs" not in test.__dict__ def test_expected_errors(spec1d): centers = [0, 10, 20, 30, 40] w = [5005, 5110, 5214, 5330, 5438]*u.AA table = QTable([centers, w], names=["pixel_center", "wavelength"]) with pytest.raises(ValueError, match="Cannot specify line_wavelengths separately"): WavelengthCalibration1D(spec1d, matched_line_list=table, line_wavelengths=w) with pytest.raises(ValueError, match="must have the same length"): w2 = [5005, 5110, 5214, 5330, 5438, 5500]*u.AA WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w2) with pytest.raises(ValueError, match="astropy.units.Quantity array or" " as an astropy.table.QTable"): w2 = [5005, 5110, 5214, 5330, 5438] WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w2) with pytest.raises(ValueError, match="specify at least one"): WavelengthCalibration1D(spec1d, line_pixels=centers) def test_fit_residuals(spec1d): # test that fit residuals are all 0 when input is perfectly linear and model # is a linear model centers = np.array([0, 10, 20, 30]) w = (0.5 * centers + 2) * u.AA test = WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w) test.apply_to_spectrum(spec1d) # have to apply for residuals to be computed assert_quantity_allclose(test.residuals, 0.*u.AA, atol=1e-07*u.AA) def test_fit_residuals_access(spec1d): # make sure that accessing residuals can be called before wcs/apply_to_spectrum centers = np.array([0, 10, 20, 30]) w = (0.5 * centers + 2) * u.AA test = WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w) test.residuals test.wcs def test_unsorted_pixels_wavelengths(spec1d): # make sure an error is raised if input matched pixels/wavelengths are # not strictly increasing or decreasing. centers = np.array([0, 10, 5, 30]) w = (0.5 * centers + 2) * u.AA with pytest.raises(ValueError, match='Pixels must be strictly increasing or decreasing.'): WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w) # now test that it fails when wavelengths are unsorted centers = np.array([0, 10, 20, 30]) w = np.array([2, 5, 6, 1]) * u.AA with pytest.raises(ValueError, match='Wavelengths must be strictly increasing or decreasing.'): WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=w) # and same if those wavelengths are provided in a table table = QTable([w], names=["wavelength"]) with pytest.raises(ValueError, match='Wavelengths must be strictly increasing or decreasing.'): WavelengthCalibration1D(spec1d, line_pixels=centers, line_wavelengths=table) # and again with decreasing pixels but unsorted wavelengths with pytest.raises(ValueError, match='Wavelengths must be strictly increasing or decreasing.'): WavelengthCalibration1D(spec1d, line_pixels=centers[::-1], line_wavelengths=w) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/tracing.py0000644000175100001660000004612414764317313017406 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see LICENSE.rst import warnings from copy import deepcopy from dataclasses import dataclass, field from typing import Literal 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 from specreduce.core import _ImageParser __all__ = ["Trace", "FlatTrace", "ArrayTrace", "FitTrace"] @dataclass class Trace: """ Basic tracing class that by default traces the middle of the image. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, required Image to be traced Attributes ---------- shape : tuple Shape of the array describing the trace """ image: NDData def __post_init__(self): self.trace_pos = self.image.shape[0] / 2 self.trace = np.ones_like(self.image[0]) * self.trace_pos # masking options not relevant for basic Trace self._mask_treatment = None self._valid_mask_treatment_methods = [None] # eventually move this to common SpecreduceOperation base class self.validate_masking_options() def __getitem__(self, i): return self.trace[i] @property def shape(self): return self.trace.shape def validate_masking_options(self): if self.mask_treatment not in self.valid_mask_treatment_methods: raise ValueError( f"`mask_treatment` {self.mask_treatment} not one " f"of {self.valid_mask_treatment_methods}" ) # noqa def shift(self, delta): """ Shift the trace by delta pixels perpendicular to the axis being traced Parameters ---------- delta : float Shift to be applied to the trace """ # act on self.trace.data to ignore the mask and then re-mask when calling _bound_trace self.trace = np.asarray(self.trace.data) + delta self._bound_trace() def _bound_trace(self): """ Mask trace positions that are outside the upper/lower bounds of the image. """ ny = self.image.shape[0] self.trace = np.ma.masked_outside(self.trace, 0, ny - 1) def __add__(self, delta): """ Return a copy of the trace shifted "forward" by delta pixels perpendicular to the axis being traced """ copy = deepcopy(self) copy.shift(delta) return copy def __sub__(self, delta): """ Return a copy of the trace shifted "backward" by delta pixels perpendicular to the axis being traced """ return self.__add__(-delta) @property def mask_treatment(self): return self._mask_treatment @property def valid_mask_treatment_methods(self): return self._valid_mask_treatment_methods @dataclass class FlatTrace(Trace, _ImageParser): """ Trace that is constant along the axis being traced. Example: :: trace = FlatTrace(image, trace_pos) Parameters ---------- trace_pos : float Position of the trace """ trace_pos: float def __post_init__(self): self.image = self._parse_image(self.image) self.set_position(self.trace_pos) # masking options not relevant for basic Trace self._mask_treatment = None self._valid_mask_treatment_methods = [None] def set_position(self, trace_pos): """ Set the trace position within the image Parameters ---------- trace_pos : float Position of the trace """ if trace_pos < 1: raise ValueError("`trace_pos` must be positive.") self.trace_pos = trace_pos self.trace = np.ones_like(self.image.data[0]) * self.trace_pos self._bound_trace() @dataclass class ArrayTrace(Trace, _ImageParser): """ Define a trace given an array of trace positions. Parameters ---------- trace : `numpy.ndarray` or `numpy.ma.MaskedArray` Array containing trace positions. """ trace: np.ndarray def __post_init__(self): # masking options not relevant for ArrayTrace. any non-finite or masked # data in `image` will not affect array trace self._mask_treatment = None self._valid_mask_treatment_methods = [None] # masked array will have a .data, regular array will not. trace_data = getattr(self.trace, "data", self.trace) # but we do need to mask uncaught non-finite values in input trace array # which should also be combined with any existing mask in the input `trace` if hasattr(self.trace, "mask"): total_mask = np.logical_or(self.trace.mask, ~np.isfinite(trace_data)) else: total_mask = ~np.isfinite(trace_data) # always work with masked array, even if there is no masked # or nonfinite data, in case padding is needed. if not, mask will be # dropped at the end and a regular array will be returned. self.trace = np.ma.MaskedArray(trace_data, total_mask) self.image = self._parse_image(self.image) nx = self.image.shape[1] nt = len(self.trace) if nt != nx: if nt > nx: # truncate trace to fit image self.trace = self.trace[0:nx] else: # assume trace starts at beginning of image and pad out trace to fit. # padding will be the last value of the trace, but will be masked out. padding = np.ma.MaskedArray(np.ones(nx - nt) * self.trace[-1], mask=True) self.trace = np.ma.hstack([self.trace, padding]) self._bound_trace() # warn if entire trace is masked if np.all(self.trace.mask): warnings.warn("Entire trace array is masked.") # and return plain array if nothing is masked if not np.any(self.trace.mask): self.trace = self.trace.data @dataclass class FitTrace(Trace, _ImageParser): """ Trace the spectrum aperture in an image. Bins along the image's dispersion (wavelength) direction, finds each bin's peak cross-dispersion (spatial) pixel, and uses a model to interpolate the function fitted to the peaks as a final trace. The number of bins, peak finding algorithm, and model used for fitting are customizable by the user. Example: :: trace = FitTrace(image, peak_method='gaussian', guess=trace_pos) Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, required The image over which to run the trace. Assumes cross-dispersion (spatial) direction is axis 0 and dispersion (wavelength) direction is axis 1. bins : int, optional The number of bins in the dispersion (wavelength) direction into which to divide the image. If not set, defaults to one bin per dispersion (wavelength) pixel in the given image. If set, requires at least 4 or N bins for a degree N ``trace_model``, whichever is greater. [default: None] guess : int, optional A guess at the trace's location in the cross-dispersion (spatial) direction. If set, overrides the normal max peak finder. Good for tracing a fainter source if multiple traces are present. [default: None] window : int, optional Fit the trace to a region with size ``window * 2`` around the guess position. Useful for tracing faint sources if multiple traces are present, but potentially bad if the trace is substantially bent or warped. [default: None] trace_model : one of `~astropy.modeling.polynomial.Chebyshev1D`,\ `~astropy.modeling.polynomial.Legendre1D`,\ `~astropy.modeling.polynomial.Polynomial1D`,\ or `~astropy.modeling.spline.Spline1D`, optional The 1-D polynomial model used to fit the trace to the bins' peak pixels. Spline1D models are fit with Astropy's 'SplineSmoothingFitter', generic linear models are fit with the 'LinearLSQFitter', while the other models are fit with the 'LMLSQFitter'. [default: ``models.Polynomial1D(degree=1)``] peak_method : string, optional One of ``gaussian``, ``centroid``, or ``max``. ``gaussian``: Fits a gaussian to the window within each bin and adopts the central value as the peak. May work best with fewer bins on faint targets. (Based on the "kosmos" algorithm from James Davenport's same-named repository.) ``centroid``: Takes the centroid of the window within in bin. ``max``: Saves the position with the maximum flux in each bin. [default: ``max``] mask_treatment Specifies how to handle masked or non-finite values in the input image. The fit cannot handle non-finite values, so only the ``apply``, ``propagate``, ``apply_nan_only`` options are supported. The ``apply`` option combines the existing mask with the mask derived from non-finite values, ``propagate`` expands the mask along the cross-dispersion axis (that is, a masked pixel results in the whole cross-dispersion slice being masked), and ``apply_nan_only`` drops the existing mask and replaces it with a mask derived from non-finite values. """ bins: int | None = None guess: float | None = None window: int | None = None trace_model: Model = field(default=models.Polynomial1D(degree=1)) peak_method: Literal["gaussian", "centroid", "max"] = "max" _crossdisp_axis: int = 0 _disp_axis: int = 1 mask_treatment: Literal["apply", "propagate", "apply_nan_only"] = "apply" _valid_mask_treatment_methods = ("apply", "propagate", "apply_nan_only") # for testing purposes only, save bin peaks if requested _save_bin_peaks_testing: bool = False def __post_init__(self): # Parse image, including masked/nonfinite data handling based on # choice of `mask_treatment`. returns a Spectrum1D if self.mask_treatment not in self._valid_mask_treatment_methods: raise ValueError( "`mask_treatment` must be one of " f"{self._valid_mask_treatment_methods}" ) self.image = self._parse_image( self.image, disp_axis=self._disp_axis, mask_treatment=self.mask_treatment ) # _parse_image returns a Spectrum1D. convert this to a masked array # for ease of calculations here (even if there is no masked data). # Note: uncertainties are dropped, this should also be addressed at # some point probably across the package. img = np.ma.masked_array(self.image.data, self.image.mask) self._mask_temp = self.image.mask # validate input arguments valid_peak_methods = ("gaussian", "centroid", "max") if self.peak_method not in valid_peak_methods: raise ValueError(f"peak_method must be one of {valid_peak_methods}") if self._crossdisp_axis != 0: raise ValueError("cross-dispersion axis must equal 0") if self._disp_axis != 1: raise ValueError("dispersion axis must equal 1") valid_models = (models.Spline1D, models.Legendre1D, models.Chebyshev1D, models.Polynomial1D) if not isinstance(self.trace_model, valid_models): raise ValueError( "trace_model must be one of " f"{', '.join([m.name for m in valid_models])}." ) cols = img.shape[self._disp_axis] model_deg = self.trace_model.degree if self.bins is None: self.bins = cols elif self.bins < 4: # many of the Astropy model fitters require four points at minimum raise ValueError("bins must be >= 4") elif self.bins <= model_deg: raise ValueError(f"bins must be > {model_deg} for " f"a degree {model_deg} model.") elif self.bins > cols: raise ValueError( f"bins must be <= {cols}, the length of the " "image's spatial direction" ) if not isinstance(self.bins, int): warnings.warn("TRACE: Converting bins to int") self.bins = int(self.bins) if self.window is not None and ( self.window > img.shape[self._disp_axis] or self.window < 1 ): raise ValueError( f"window must be >= 2 and less than {cols}, the " "length of the image's spatial direction" ) elif self.window is not None and not isinstance(self.window, int): warnings.warn("TRACE: Converting window to int") self.window = int(self.window) # fit the trace self._fit_trace(img) def _fit_trace(self, img): yy = np.arange(img.shape[self._crossdisp_axis]) # set max peak location by user choice or wavelength with max avg flux ztot = img.mean(axis=self._disp_axis) peak_y = self.guess if self.guess is not None else ztot.argmax() # NOTE: peak finder can be bad if multiple objects are on slit if self.peak_method == "gaussian": # guess the peak width as the FWHM, roughly converted to gaussian sigma yy_above_half_max = np.sum(ztot > (ztot.max() / 2)) width_guess = yy_above_half_max / gaussian_sigma_to_fwhm # enforce some (maybe sensible?) rules about trace peak width width_guess = 2 if width_guess < 2 else 25 if width_guess > 25 else width_guess # fit a Gaussian to peak for fall-back answer, but don't use yet g1d_init = models.Gaussian1D(amplitude=ztot.max(), mean=peak_y, stddev=width_guess) offset_init = models.Const1D(np.ma.median(ztot)) profile = g1d_init + offset_init fitter = fitting.DogBoxLSQFitter() popt_tot = fitter(profile, yy, ztot) # restrict fit to window (if one exists) ilum2 = ( yy if self.window is None else yy[np.arange(peak_y - self.window, peak_y + self.window, dtype=int)] ) # check if everything in window region is masked if img[ilum2].mask.all(): raise ValueError( "All pixels in window region are masked. Check " "for invalid values or use a larger window value." ) x_bins = np.linspace(0, img.shape[self._disp_axis], self.bins + 1, dtype=int) y_bins = np.tile(np.nan, self.bins) warn_bins = [] for i in range(self.bins): # binned columns, averaged along disp. axis. # or just a single, unbinned column if no bins z_i = img[ilum2, x_bins[i] : x_bins[i + 1]].mean(axis=self._disp_axis) # if this bin is fully masked, set bin peak to NaN so it can be # filtered in the final fit to all bin peaks for the trace if z_i.mask.all() or (~np.isfinite(z_i)).all(): warn_bins.append(i) y_bins[i] = np.nan continue if self.peak_method == "gaussian": peak_y_i = ilum2[z_i.argmax()] yy_i_above_half_max = np.sum(z_i > (z_i.max() / 2)) width_guess_i = yy_i_above_half_max / gaussian_sigma_to_fwhm # NOTE: original KOSMOS code mandated width be greater than 2 # (to avoid cosmic rays) and less than 25 (to avoid fitting noise). # we should extract values from img to derive similar limits # width_guess_i = (2 if width_guess_i < 2 # else 25 if width_guess_i > 25 # else width_guess_i) g1d_init_i = models.Gaussian1D( amplitude=z_i.max(), mean=peak_y_i, stddev=width_guess_i ) offset_init_i = models.Const1D(np.ma.median(z_i)) profile_i = g1d_init_i + offset_init_i popt_i = fitter(profile_i, ilum2[~z_i.mask], z_i.data[~z_i.mask]) # if gaussian fits off chip, then fall back to previous answer if not ilum2.min() <= popt_i.mean_0 <= ilum2.max(): y_bins[i] = popt_tot.mean_0.value else: y_bins[i] = popt_i.mean_0.value popt_tot = popt_i elif self.peak_method == "centroid": z_i_cumsum = np.cumsum(z_i) # find the interpolated index where the cumulative array reaches # half the total cumulative values y_bins[i] = np.interp(z_i_cumsum[-1] / 2.0, z_i_cumsum, ilum2) # NOTE this reflects current behavior, should eventually be changed # to set to nan by default (or zero fill / interpoate option once # available) elif self.peak_method == "max": # TODO: implement smoothing with provided width y_bins[i] = ilum2[z_i.argmax()] # NOTE: a fully masked should eventually be changed to set to # nan by default (or zero fill / interpoate option once available) # warn about fully-masked bins if len(warn_bins) > 0: # if there are a ton of bins, we don't want to print them all out if len(warn_bins) > 20: warn_bins = warn_bins[0:10] + ["..."] + [warn_bins[-1]] warnings.warn( f"All pixels in {'bins' if len(warn_bins) else 'bin'} " f"{', '.join([str(x) for x in warn_bins])}" " are fully masked. Setting bin" f" peak{'s' if len(warn_bins) else ''} to NaN." ) # recenter bin positions x_bins = (x_bins[:-1] + x_bins[1:]) / 2 # interpolate the fitted trace over the entire wavelength axis # for testing purposes only, save bin peaks if requested if self._save_bin_peaks_testing: self._bin_peaks_testing = (x_bins, y_bins) # filter non-finite bin peaks before filtering to all bin peaks y_finite = np.where(np.isfinite(y_bins))[0] if y_finite.size > 0: x_bins = x_bins[y_finite] y_bins = y_bins[y_finite] # use given model to bin y-values; interpolate over all wavelengths if isinstance(self.trace_model, models.Spline1D): fitter = fitting.SplineSmoothingFitter() elif self.trace_model.linear: fitter = fitting.LinearLSQFitter() else: fitter = fitting.LMLSQFitter() self._y_bins = y_bins self.trace_model_fit = fitter(self.trace_model, x_bins, y_bins) trace_x = np.arange(img.shape[self._disp_axis]) trace_y = self.trace_model_fit(trace_x) else: warnings.warn("TRACE ERROR: No valid points found in trace") trace_y = np.tile(np.nan, img.shape[self._disp_axis]) self.trace = np.ma.masked_invalid(trace_y) @deprecated("1.3", alternative="FitTrace") @dataclass class KosmosTrace(FitTrace): """ This class is pending deprecation. Please use `FitTrace` instead. """ __doc__ += FitTrace.__doc__ pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.728497 specreduce-1.5.1/specreduce/utils/0000755000175100001660000000000014764317326016542 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/utils/__init__.py0000644000175100001660000000011714764317313020646 0ustar00runnerdocker""" General purpose utilities for specreduce """ from .utils import * # noqa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/utils/synth_data.py0000644000175100001660000003411614764317313021253 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see ../../licenses/LICENSE.rst import warnings from typing import Sequence import numpy as np from astropy import units as u from astropy.modeling import models, Model from astropy.nddata import CCDData from astropy.stats import gaussian_fwhm_to_sigma from astropy.wcs import WCS from specreduce.calibration_data import load_pypeit_calibration_lines __all__ = [ 'make_2d_trace_image', 'make_2d_arc_image', 'make_2d_spec_image' ] def make_2d_trace_image( nx: int = 3000, ny: int = 1000, background: int | float = 5, trace_center: int | float | None = None, trace_order: int = 3, trace_coeffs: dict[str, int | float] = {'c0': 0, 'c1': 50, 'c2': 100}, profile: Model = models.Moffat1D(amplitude=10, alpha=0.1), add_noise: bool = True ) -> CCDData: """ Create synthetic 2D spectroscopic image with a single source. The spatial (y-axis) position of the source along the dispersion (x-axis) direction is modeled using a Chebyshev polynomial. The flux units are counts and the noise is modeled as Poisson. Parameters ---------- nx : Size of image in X axis which is assumed to be the dispersion axis ny : Size of image in Y axis which is assumed to be the spatial axis background : Level of constant background in counts trace_center : Zeropoint of the trace. If None, then use center of Y (spatial) axis. trace_order : Order of the Chebyshev polynomial used to model the source's trace trace_coeffs : Dict containing the Chebyshev polynomial coefficients to use in the trace model profile : Model to use for the source's spatial profile add_noise : If True, add Poisson noise to the image Returns ------- ccd_im : CCDData instance containing synthetic 2D spectroscopic image """ x = np.arange(nx) y = np.arange(ny) xx, yy = np.meshgrid(x, y) if trace_center is None: trace_center = ny / 2 trace_mod = models.Chebyshev1D(degree=trace_order, **trace_coeffs) trace = yy - trace_center + trace_mod(xx/nx) z = background + profile(trace) if add_noise: from photutils.datasets import apply_poisson_noise trace_image = apply_poisson_noise(z) else: trace_image = z ccd_im = CCDData(trace_image, unit=u.count) return ccd_im def make_2d_arc_image( nx: int = 3000, ny: int = 1000, wcs: WCS | None = None, extent: Sequence[int | float] = (3500, 7000), wave_unit: u.Unit = u.Angstrom, wave_air: bool = False, background: int | float = 5, line_fwhm: float = 5., linelists: list[str] = ['HeI'], amplitude_scale: float = 1., tilt_func: Model = models.Legendre1D(degree=0), add_noise: bool = True ) -> CCDData: """ Create synthetic 2D spectroscopic image of reference emission lines, e.g. a calibration arc lamp. Currently, linelists from ``pypeit`` are supported and are selected by string or list of strings that is passed to `~specreduce.calibration_data.load_pypeit_calibration_lines`. If a ``wcs`` is not provided, one is created using ``extent`` and ``wave_unit`` with dispersion along the X axis. Parameters ---------- nx : Size of image in X axis which is assumed to be the dispersion axis ny : Size of image in Y axis which is assumed to be the spatial axis wcs : 2D WCS to apply to the image. Must have a spectral axis defined along with appropriate spectral wavelength units. extent : If ``wcs`` is not provided, this defines the beginning and end wavelengths of the dispersion axis. wave_unit : If ``wcs`` is not provided, this defines the wavelength units of the dispersion axis. wave_air : If True, convert the vacuum wavelengths used by ``pypeit`` to air wavelengths. background : Level of constant background in counts line_fwhm : Gaussian FWHM of the emission lines in pixels linelists : Specification for linelists to load from ``pypeit`` amplitude_scale : Scale factor to apply to amplitudes provided in the linelists tilt_func : The tilt function to apply along the cross-dispersion axis to simulate tilted or curved emission lines. add_noise : If True, add Poisson noise to the image; requires ``photutils`` to be installed. Returns ------- ccd_im : CCDData instance containing synthetic 2D spectroscopic image Examples -------- This is an example of modeling a spectrograph whose output is curved in the cross-dispersion direction: .. plot:: :include-source: import matplotlib.pyplot as plt import numpy as np from astropy.modeling import models import astropy.units as u from specreduce.utils.synth_data import make_2d_arc_image model_deg2 = models.Legendre1D(degree=2, c0=50, c1=0, c2=100) im = make_2d_arc_image( linelists=['HeI', 'ArI', 'ArII'], line_fwhm=3, tilt_func=model_deg2 ) fig = plt.figure(figsize=(10, 6)) plt.imshow(im) The FITS WCS standard implements ideal world coordinate functions based on the physics of simple dispersers. This is described in detail by Paper III, https://www.aanda.org/articles/aa/pdf/2006/05/aa3818-05.pdf. This can be used to model a non-linear dispersion relation based on the properties of a spectrograph. This example recreates Figure 5 in that paper using a spectrograph with a 450 lines/mm volume phase holographic grism. Standard gratings only use the first three ``PV`` terms: .. plot:: :include-source: import numpy as np import matplotlib.pyplot as plt from astropy.wcs import WCS import astropy.units as u from specreduce.utils.synth_data import make_2d_arc_image non_linear_header = { 'CTYPE1': 'AWAV-GRA', # Grating dispersion function with air wavelengths 'CUNIT1': 'Angstrom', # Dispersion units 'CRPIX1': 719.8, # Reference pixel [pix] 'CRVAL1': 7245.2, # Reference value [Angstrom] 'CDELT1': 2.956, # Linear dispersion [Angstrom/pix] 'PV1_0': 4.5e5, # Grating density [1/m] 'PV1_1': 1, # Diffraction order 'PV1_2': 27.0, # Incident angle [deg] 'PV1_3': 1.765, # Reference refraction 'PV1_4': -1.077e6, # Refraction derivative [1/m] 'CTYPE2': 'PIXEL', # Spatial detector coordinates 'CUNIT2': 'pix', # Spatial units 'CRPIX2': 1, # Reference pixel 'CRVAL2': 0, # Reference value 'CDELT2': 1 # Spatial units per pixel } linear_header = { 'CTYPE1': 'AWAV', # Grating dispersion function with air wavelengths 'CUNIT1': 'Angstrom', # Dispersion units 'CRPIX1': 719.8, # Reference pixel [pix] 'CRVAL1': 7245.2, # Reference value [Angstrom] 'CDELT1': 2.956, # Linear dispersion [Angstrom/pix] 'CTYPE2': 'PIXEL', # Spatial detector coordinates 'CUNIT2': 'pix', # Spatial units 'CRPIX2': 1, # Reference pixel 'CRVAL2': 0, # Reference value 'CDELT2': 1 # Spatial units per pixel } non_linear_wcs = WCS(non_linear_header) linear_wcs = WCS(linear_header) # this re-creates Paper III, Figure 5 pix_array = 200 + np.arange(1400) nlin = non_linear_wcs.spectral.pixel_to_world(pix_array) lin = linear_wcs.spectral.pixel_to_world(pix_array) resid = (nlin - lin).to(u.Angstrom) plt.plot(pix_array, resid) plt.xlabel("Pixel") plt.ylabel("Correction (Angstrom)") plt.show() nlin_im = make_2d_arc_image( nx=600, ny=512, linelists=['HeI', 'NeI'], line_fwhm=3, wave_air=True, wcs=non_linear_wcs ) lin_im = make_2d_arc_image( nx=600, ny=512, linelists=['HeI', 'NeI'], line_fwhm=3, wave_air=True, wcs=linear_wcs ) # subtracting the linear simulation from the non-linear one shows how the # positions of lines diverge between the two cases plt.imshow(nlin_im.data - lin_im.data) plt.show() """ if wcs is None: if extent is None: raise ValueError("Must specify either a wavelength extent or a WCS.") if len(extent) != 2: raise ValueError("Wavelength extent must be of length 2.") if u.get_physical_type(wave_unit) != 'length': raise ValueError("Wavelength unit must be a length unit.") wcs = WCS(naxis=2) wcs.wcs.ctype[0] = 'WAVE' wcs.wcs.ctype[1] = 'PIXEL' wcs.wcs.cunit[0] = wave_unit wcs.wcs.cunit[1] = u.pixel wcs.wcs.crval[0] = extent[0] wcs.wcs.cdelt[0] = (extent[1] - extent[0]) / nx wcs.wcs.crval[1] = 0 wcs.wcs.cdelt[1] = 1 else: if wcs.spectral.naxis != 1: raise ValueError("Provided WCS must have a spectral axis.") if wcs.naxis != 2: raise ValueError("WCS must have NAXIS=2 for a 2D image.") x = np.arange(nx) y = np.arange(ny) xx, yy = np.meshgrid(x, y) is_spectral = [a['coordinate_type'] == "spectral" for a in wcs.get_axis_types()] if is_spectral[0]: disp_axis = 0 else: disp_axis = 1 if tilt_func is not None: if not isinstance( tilt_func, (models.Legendre1D, models.Chebyshev1D, models.Polynomial1D, models.Hermite1D) ): raise ValueError( "The only tilt functions currently supported are 1D polynomials " "from astropy.models." ) if disp_axis == 0: xx = xx + tilt_func((yy - ny/2)/ny) else: yy = yy + tilt_func((xx - nx/2)/nx) z = background + np.zeros((ny, nx)) linelist = load_pypeit_calibration_lines(linelists, wave_air=wave_air) if linelist is not None: with warnings.catch_warnings(): warnings.filterwarnings("ignore", message="No observer defined on WCS.*") line_disp_positions = wcs.spectral.world_to_pixel(linelist['wavelength']) line_sigma = gaussian_fwhm_to_sigma * line_fwhm for line_pos, ampl in zip(line_disp_positions, linelist['amplitude']): line_mod = models.Gaussian1D( amplitude=ampl * amplitude_scale, mean=line_pos, stddev=line_sigma ) if disp_axis == 0: z += line_mod(xx) else: z += line_mod(yy) if add_noise: from photutils.datasets import apply_poisson_noise arc_image = apply_poisson_noise(z) else: arc_image = z ccd_im = CCDData(arc_image, unit=u.count, wcs=wcs) return ccd_im def make_2d_spec_image( nx: int = 3000, ny: int = 1000, wcs: WCS | None = None, extent: Sequence[int | float] = (6500, 9500), wave_unit: u.Unit = u.Angstrom, wave_air: bool = False, background: int | float = 5, line_fwhm: float = 5., linelists: list[str] = ['OH_GMOS'], amplitude_scale: float = 1., tilt_func: Model = models.Legendre1D(degree=0), trace_center: int | float | None = None, trace_order: int = 3, trace_coeffs: dict[str, int | float] = {'c0': 0, 'c1': 50, 'c2': 100}, source_profile: Model = models.Moffat1D(amplitude=10, alpha=0.1), add_noise: bool = True ) -> CCDData: """ Make a synthetic 2D spectrum image containing both emission lines and a trace of a continuum source. Parameters ---------- nx : Number of pixels in the dispersion direction. ny : Number of pixels in the spatial direction. wcs : 2D WCS to apply to the image. Must have a spectral axis defined along with appropriate spectral wavelength units. extent : If ``wcs`` is not provided, this defines the beginning and end wavelengths of the dispersion axis. wave_unit : If ``wcs`` is not provided, this defines the wavelength units of the dispersion axis. wave_air : If True, convert the vacuum wavelengths used by ``pypeit`` to air wavelengths. background : Constant background level in counts. line_fwhm : Gaussian FWHM of the emission lines in pixels linelists : Specification for linelists to load from ``pypeit`` amplitude_scale : Scale factor to apply to amplitudes provided in the linelists tilt_func : The tilt function to apply along the cross-dispersion axis to simulate tilted or curved emission lines. trace_center : Zeropoint of the trace. If None, then use center of Y (spatial) axis. trace_order : Order of the Chebyshev polynomial used to model the source's trace trace_coeffs : Dict containing the Chebyshev polynomial coefficients to use in the trace model source_profile : Model to use for the source's spatial profile add_noise : If True, add Poisson noise to the image; requires ``photutils`` to be installed. Returns ------- ccd_im : CCDData instance containing synthetic 2D spectroscopic image """ arc_image = make_2d_arc_image( nx=nx, ny=ny, wcs=wcs, extent=extent, wave_unit=wave_unit, wave_air=wave_air, background=0, line_fwhm=line_fwhm, linelists=linelists, amplitude_scale=amplitude_scale, tilt_func=tilt_func, add_noise=False ) trace_image = make_2d_trace_image( nx=nx, ny=ny, background=0, trace_center=trace_center, trace_order=trace_order, trace_coeffs=trace_coeffs, profile=source_profile, add_noise=False ) 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) return ccd_im ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/utils/utils.py0000644000175100001660000002365714764317313020265 0ustar00runnerdockerimport numpy as np from specreduce.core import _ImageParser from specreduce.tracing import Trace, FlatTrace from specreduce.extract import _ap_weight_image, _align_along_trace __all__ = ['measure_cross_dispersion_profile', '_align_along_trace'] def measure_cross_dispersion_profile(image, trace=None, crossdisp_axis=0, width=None, pixel=None, pixel_range=None, statistic='median', align_along_trace=True): """ Return a 1D (quantity) array of the cross-dispersion profile measured at a specified pixel value ('wavelength', but potentially before calibration), or the average profile across several pixel values (or range of pixel values) along the dispersion axis. If a single number is specified for `pixel`, then the profile at that pixel (i.e wavelength) will be returned. If several pixels are specified in a list or array, then they will be averaged (median or mean, set by `statistic` which defaults to median). Alternatively, `pixel_range` can be specified as a tuple of integers specifying the minimum and maximum pixel range to average the profile. `pixel` and `pixel_range` cannot be set simultaneously, and the default is `pixel_range`=(min_image, max_image) to return an average profile across the entire wavelength range of the image. The window in the cross dispersion axis for measuring the profile at the pixel(s) specified is determined by `width` and `trace`. If a trace is provided (either as a Trace object, or as a number specifying the location on the cross-dispersion axis of a FlatTrace object) that will determine the center of the profile on the cross-dispersion axis. Otherwise, if `trace` is None, the center of the image will be the center of the returned profile (i.e., the center row assuming a horizontal trace). If `width` is none, a window size of half the image in the cross-dispersion axis will be used to measure the cross dispersion profile. Otherwise, an integer value can be set for `width` which will determine the size of the window around the trace used to measure the profile (this window moves with the trace if trace is not flat). By default, if a non-flat trace is used the image will be aligned along the trace. This can be controlled with the 'align_along_trace' parameter. Parameters ---------- image : `~astropy.nddata.NDData`-like or array-like, required 2D image to measure cross-dispersion profile. trace : Trace object, int, float, or None A Trace object, a number to specify a FlatTrace, or None to use the middle of the image. This is the position that defines the center of the cross-dispersion profile. [default: None] crossdisp_axis : int, optional The index of the image's cross-dispersion axis. [default: 0] width : tuple of int or None Width around 'trace' to calculate profile. If None, then all rows in the cross-dispersion axis will be used. [default: None] pixel: int, list of int, or None Pixel value(s) along the dispersion axis to return cross-dispersion profile. If several specified in list, then the average (method set by `statistic`) profile will be calculated. If None, and `pixel_range` is set, then `pixel_range` will be used. [default: None] pixel_range: tuple of int or None Tuple of (min, max) defining the pixel range (along dispersion axis) to calculate average cross-dispersion profile, up to and not inclusive of max. If None, and `pixel` is not None, `pixel` will be used. If None and `pixel` is also None, this will be interpreted as using the entire dispersion axis to generate an average profile for the whole image. [default: None] statistic: 'median' or 'mean' If `pixel` specifies multiple pixels, or `pixel_range` is specified, an average profile will be returned. This can be either `median` (default) or `mean`. This is ignored if only one pixel is specified. [default: median] align_along_trace: bool Relevant only for non-flat traces. If True, "roll" each column so that the trace sits in the central row before calculating average profile. This will prevent any 'blurring' from averaging a non-flat trace at different pixel/wavelengths. [default: True] """ if crossdisp_axis != 0 and crossdisp_axis != 1: raise ValueError('`crossdisp_axis` must be 0 or 1.') crossdisp_axis = int(crossdisp_axis) disp_axis = 1 if crossdisp_axis == 0 else 0 unit = getattr(image, 'unit', None) # parse image, which will return a spectrum1D (note: this is not ideal, # but will be addressed at some point) parser = _ImageParser() image = parser._parse_image(image, disp_axis=disp_axis) # which we then need to make back into a masked array # again this way of parsing the image is not ideal but # thats just how it is for now. image = np.ma.MaskedArray(image.data, mask=image.mask) # transpose if disp_axis = 0 just for simplicity of calculations # image is already copied so this won't modify input if disp_axis == 0: image = image.T nrows = image.shape[crossdisp_axis] ncols = image.shape[disp_axis] if not isinstance(trace, Trace): # `trace` can be a trace obj if trace is None: # if None, make a FlatTrace in the center of image trace_pos = nrows / 2 trace = FlatTrace(image, trace_pos) elif isinstance(trace, (float, int)): # if float/int make a FlatTrace trace = FlatTrace(image, trace) else: raise ValueError('`trace` must be Trace object, number to specify ' 'the location of a FlatTrace, or None to use center' ' of image.') if statistic not in ['median', 'mean']: raise ValueError("`statistic` must be 'median' or 'mean'.") # determine if there is one pixel/wavelength selected or many as either a # list or a tuple to specify a range if pixel is not None: if pixel_range is not None: raise ValueError('Both `pixel` and `pixel_range` can not be set' ' simultaneously.') if isinstance(pixel, (int, float)): pixels = np.array([int(pixel)]) elif np.all([isinstance(x, (int, float)) for x in pixel]): pixels = np.array([int(x) for x in pixel]) else: raise ValueError('`pixels` must be an integer, or list of integers ' 'to specify where the crossdisperion profile should ' 'be measured.') else: # range is specified if pixel_range is None: pixels = np.arange(0, ncols) else: # if not None, it should be a lower and upper bound if len(pixel_range) != 2: raise ValueError('`pixel_range` must be a tuple of integers.') pixels = np.arange(min(pixel_range), max(pixel_range)) # now that we have all pixels that should be included in the profile, make # sure that they are within image bounds. # note: Should just warn instead and clip out out-of-bounds pixels, and only # warn if there are none left? if np.any(pixels < 0) or np.any(pixels > ncols - 1): raise ValueError('Pixels chosen to measure cross dispersion profile are' ' out of image bounds.') # now that we know which pixel(s) on the disp. axis we want to include # figure out the range/window of pixels along the crossdisp axis to measure # the profile if width is None: # if None, use all rows width = nrows elif isinstance(width, (float, int)): width = int(width) else: raise ValueError('`width` must be an integer, or None to use all ' 'cross-dispersion pixels.') width = int(width) # rectify trace, if _align_along_trace is True and trace is not flat aligned_trace = None if align_along_trace: if not isinstance(trace, FlatTrace): # note: img was transposed according to `crossdisp_axis`: disp_axis will always be 1 aligned_trace = _align_along_trace(image, trace.trace, disp_axis=1, crossdisp_axis=0) # new trace will be a flat trace at the center of the image trace_pos = nrows / 2 trace = FlatTrace(aligned_trace, trace_pos) # create a weight image based on the trace and 'width' to mask around trace if width == nrows: wimg = np.zeros(image.shape) else: wimg = _ap_weight_image(trace, width, disp_axis, crossdisp_axis, image.shape) # invert mask to include, not exclude, pixels around trace wimg = (1 - wimg).astype(int) # now that we have figured out the mask for the window in cross-disp. axis, # select only the pixel(s) we want to include in measuring the avg. profile pixel_mask = np.ones((image.shape)) pixel_mask[:, pixels] = 0 # combine these masks to isolate the rows and cols used to measure profile combined_mask = np.logical_or(pixel_mask, wimg) if aligned_trace is not None: masked_arr = np.ma.MaskedArray(aligned_trace, combined_mask) else: masked_arr = np.ma.MaskedArray(image.data, combined_mask) # and measure the cross dispersion profile. if multiple pixels/wavelengths, # this will be an average. we already transposed data based on disp_axis so # axis is always 1 for this calculation if statistic == 'mean': avg_prof = np.ma.mean(masked_arr, axis=1) else: # must be median, we already checked. avg_prof = np.ma.median(masked_arr, axis=1) # and get profile avg_prof = avg_prof.data[~avg_prof.mask] # and re-apply original unit, if there was one if unit is not None: avg_prof *= unit return avg_prof ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790933.0 specreduce-1.5.1/specreduce/version.py0000644000175100001660000000077714764317325017453 0ustar00runnerdocker# file generated by setuptools-scm # don't change, don't track in version control __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] TYPE_CHECKING = False if TYPE_CHECKING: from typing import Tuple from typing import Union VERSION_TUPLE = Tuple[Union[int, str], ...] else: VERSION_TUPLE = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE __version__ = version = '1.5.1' __version_tuple__ = version_tuple = (1, 5, 1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/specreduce/wavelength_calibration.py0000644000175100001660000002547614764317313022501 0ustar00runnerdockerfrom 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 from gwcs import coordinate_frames as cf from gwcs import wcs from specutils import Spectrum1D __all__ = [ 'WavelengthCalibration1D' ] def _check_arr_monotonic(arr): # returns True if ``arr`` is either strictly increasing or strictly # decreasing, otherwise returns False. sorted_increasing = np.all(arr[1:] >= arr[:-1]) sorted_decreasing = np.all(arr[1:] <= arr[:-1]) return sorted_increasing or sorted_decreasing class WavelengthCalibration1D(): def __init__(self, input_spectrum, matched_line_list=None, line_pixels=None, line_wavelengths=None, catalog=None, input_model=Linear1D(), fitter=None): """ input_spectrum: `~specutils.Spectrum1D` A one-dimensional Spectrum1D calibration spectrum from an arc lamp or similar. matched_line_list: `~astropy.table.QTable`, optional An `~astropy.table.QTable` table with (minimally) columns named "pixel_center" and "wavelength" with known corresponding line pixel centers and wavelengths populated. line_pixels: list, array, `~astropy.table.QTable`, optional List or array of line pixel locations to anchor the wavelength solution fit. Can also be input as an `~astropy.table.QTable` table with (minimally) a column named "pixel_center". line_wavelengths: `~astropy.units.Quantity`, `~astropy.table.QTable`, optional `astropy.units.Quantity` array of line wavelength values corresponding to the line pixels defined in ``line_list``, assumed to be in the same order. Can also be input as an `~astropy.table.QTable` with (minimally) a "wavelength" column. catalog: list, str, `~astropy.table.QTable`, optional The name of a catalog of line wavelengths to load and use in automated and template-matching line matching. NOTE: This option is currently not implemented. input_model: `~astropy.modeling.Model` The model to fit for the wavelength solution. Defaults to a linear model. fitter: `~astropy.modeling.fitting.Fitter`, optional The fitter to use in optimizing the model fit. Defaults to `~astropy.modeling.fitting.LinearLSQFitter` if the model to fit is linear or `~astropy.modeling.fitting.LMLSQFitter` if the model to fit is non-linear. Note that either ``matched_line_list`` or ``line_pixels`` must be specified, and if ``matched_line_list`` is not input, at least one of ``line_wavelengths`` or ``catalog`` must be specified. """ self._input_spectrum = input_spectrum self._input_model = input_model self._cached_properties = ['fitted_model', 'residuals', 'wcs'] self.fitter = fitter self._potential_wavelengths = None self._catalog = catalog if not isinstance(input_spectrum, Spectrum1D): raise ValueError('Input spectrum must be Spectrum1D.') # We use either line_pixels or matched_line_list to create self._matched_line_list, # and check that various requirements are fulfilled by the input args. if matched_line_list is not None: pixel_arg = "matched_line_list" if not isinstance(matched_line_list, QTable): raise ValueError("matched_line_list must be an astropy.table.QTable.") self._matched_line_list = matched_line_list elif line_pixels is not None: pixel_arg = "line_pixels" if isinstance(line_pixels, (list, np.ndarray)): self._matched_line_list = QTable([line_pixels], names=["pixel_center"]) elif isinstance(line_pixels, QTable): self._matched_line_list = line_pixels else: raise ValueError("Either matched_line_list or line_pixels must be specified.") if "pixel_center" not in self._matched_line_list.columns: raise ValueError(f"{pixel_arg} must have a 'pixel_center' column.") if self._matched_line_list["pixel_center"].unit is None: self._matched_line_list["pixel_center"].unit = u.pix # check that pixels are monotonic if not _check_arr_monotonic(self._matched_line_list["pixel_center"]): raise ValueError('Pixels must be strictly increasing or decreasing.') # now that pixels have been determined from input, figure out wavelengths. if (line_wavelengths is None and catalog is None and "wavelength" not in self._matched_line_list.columns): raise ValueError("You must specify at least one of line_wavelengths, " "catalog, or 'wavelength' column in matched_line_list.") # Sanity checks on line_wavelengths value if line_wavelengths is not None: if (isinstance(self._matched_line_list, QTable) and "wavelength" in self._matched_line_list.columns): raise ValueError("Cannot specify line_wavelengths separately if there is" " a 'wavelength' column in matched_line_list.") if len(line_wavelengths) != len(self._matched_line_list): raise ValueError("If line_wavelengths is specified, it must have the same " f"length as {pixel_arg}") if not isinstance(line_wavelengths, (u.Quantity, QTable)): raise ValueError("line_wavelengths must be specified as an astropy.units.Quantity" " array or as an astropy.table.QTable") # make sure wavelengths (or freq) are monotonic and add wavelengths # to _matched_line_list if isinstance(line_wavelengths, u.Quantity): if not _check_arr_monotonic(line_wavelengths): if str(line_wavelengths.unit.physical_type) == "frequency": raise ValueError('Frequencies must be strictly increasing or decreasing.') raise ValueError('Wavelengths must be strictly increasing or decreasing.') self._matched_line_list["wavelength"] = line_wavelengths elif isinstance(line_wavelengths, QTable): if not _check_arr_monotonic(line_wavelengths['wavelength']): raise ValueError('Wavelengths must be strictly increasing or decreasing.') self._matched_line_list = hstack([self._matched_line_list, line_wavelengths]) # Parse desired catalogs of lines for matching. if catalog is not None: # For now we avoid going into the later logic and just throw an error raise NotImplementedError("No catalogs are available yet, please input " "wavelengths with line_wavelengths or as a " f"column in {pixel_arg}") if isinstance(catalog, QTable): if "wavelength" not in catalog.columns: raise ValueError("Catalog table must have a 'wavelength' column.") self._catalog = catalog else: # This will need to be updated to match up with Tim's catalog code if isinstance(catalog, list): self._catalog = catalog else: self._catalog = [catalog] for cat in self._catalog: if isinstance(cat, str): if cat not in self._available_catalogs: raise ValueError(f"Line list '{cat}' is not an available catalog.") def identify_lines(self): """ ToDo: Code matching algorithm between line pixel locations and potential line wavelengths from catalogs. """ pass def _clear_cache(self, *attrs): """ provide convenience function to clearing the cache for cached_properties """ if not len(attrs): attrs = self._cached_properties for attr in attrs: if attr in self.__dict__: del self.__dict__[attr] @property def available_catalogs(self): return self._available_catalogs @property def input_spectrum(self): return self._input_spectrum @input_spectrum.setter def input_spectrum(self, new_spectrum): # We want to clear the refined locations if a new calibration spectrum is provided self._clear_cache() self._input_spectrum = new_spectrum @property def input_model(self): return self._input_model @input_model.setter def input_model(self, input_model): self._clear_cache() self._input_model = input_model @cached_property def fitted_model(self): # computes and returns WCS after fitting self.model to self.refined_pixels x = self._matched_line_list["pixel_center"] y = self._matched_line_list["wavelength"] if self.fitter is None: # Flexible defaulting if self.fitter is None if self.input_model.linear: fitter = LinearLSQFitter(calc_uncertainties=True) else: fitter = LMLSQFitter(calc_uncertainties=True) else: fitter = self.fitter # Fit the model return fitter(self.input_model, x, y) @cached_property def residuals(self): """ calculate fit residuals between matched line list pixel centers and wavelengths and the evaluated fit model. """ x = self._matched_line_list["pixel_center"] y = self._matched_line_list["wavelength"] # Get the fit residuals by evaulating model return y - self.fitted_model(x) @cached_property def wcs(self): # Build a GWCS pipeline from the fitted model pixel_frame = cf.CoordinateFrame(1, "SPECTRAL", [0,], axes_names=["x",], unit=[u.pix,]) spectral_frame = cf.SpectralFrame(axes_names=["wavelength",], unit=[self._matched_line_list["wavelength"].unit,]) pipeline = [(pixel_frame, self.fitted_model), (spectral_frame, None)] wcsobj = wcs.WCS(pipeline) return wcsobj def apply_to_spectrum(self, spectrum=None): # returns spectrum1d with wavelength calibration applied # actual line refinement and WCS solution should already be done so that this can # be called on multiple science sources spectrum = self.input_spectrum if spectrum is None else spectrum updated_spectrum = Spectrum1D(spectrum.flux, wcs=self.wcs, mask=spectrum.mask, uncertainty=spectrum.uncertainty) return updated_spectrum ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1741790933.728497 specreduce-1.5.1/specreduce.egg-info/0000755000175100001660000000000014764317326017074 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790933.0 specreduce-1.5.1/specreduce.egg-info/PKG-INFO0000644000175100001660000000747114764317325020201 0ustar00runnerdockerMetadata-Version: 2.2 Name: specreduce Version: 1.5.1 Summary: Astropy coordinated package for Spectroscopic Reductions Author-email: Astropy Specreduce contributors License: Copyright (c) 2017, Astropy-specreduce Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Project-URL: Homepage, http://astropy.org/ Project-URL: Repository, https://github.com/astropy/specreduce.git Project-URL: Documentation, https://specreduce.readthedocs.io/ Requires-Python: >=3.10 Description-Content-Type: text/x-rst Requires-Dist: numpy Requires-Dist: astropy Requires-Dist: scipy Requires-Dist: specutils>=1.9.1 Requires-Dist: gwcs Provides-Extra: test Requires-Dist: pytest-astropy; extra == "test" Requires-Dist: photutils; extra == "test" Requires-Dist: tox; extra == "test" Provides-Extra: docs Requires-Dist: sphinx-astropy; extra == "docs" Requires-Dist: matplotlib; extra == "docs" Requires-Dist: photutils; extra == "docs" Requires-Dist: synphot; extra == "docs" Provides-Extra: all Requires-Dist: matplotlib; extra == "all" Requires-Dist: photutils; extra == "all" Requires-Dist: synphot; extra == "all" Specreduce ========== .. 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/ :alt: Documentation Status .. 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/ :alt: Powered by Astropy 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 ------- Specreduce is licensed under a 3-clause BSD style license. Please see the licenses/LICENSE.rst file. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790933.0 specreduce-1.5.1/specreduce.egg-info/SOURCES.txt0000644000175100001660000000477114764317325020770 0ustar00runnerdocker.flake8 .gitignore .readthedocs.yaml CHANGES.rst CITATION.cff MANIFEST.in README.rst conftest.py pyproject.toml tox.ini .github/dependabot.yml .github/workflows/cron-tests.yml .github/workflows/publish-to-pypi.yml .github/workflows/tox-tests.yml docs/Makefile docs/api.rst docs/atm_transmission_secz1.5_1.6mm.dat docs/conf.py docs/extinction.rst docs/extraction_quickstart.rst docs/index.rst docs/make.bat docs/specphot_standards.rst docs/terms.rst docs/wavelength_calibration.rst docs/_static/logo_icon.ico docs/_static/logo_icon.png docs/_static/specreduce.css docs/_templates/autosummary/base.rst docs/_templates/autosummary/class.rst docs/_templates/autosummary/module.rst docs/mask_treatment/fig_masking_apply.svg docs/mask_treatment/fig_masking_apply_mask_only.svg docs/mask_treatment/fig_masking_apply_nan_only.svg docs/mask_treatment/fig_masking_ignore.svg docs/mask_treatment/fig_masking_nan_fill.svg docs/mask_treatment/fig_masking_propagate.svg docs/mask_treatment/fig_masking_zero_fill.svg docs/mask_treatment/mask_treatment.rst docs/process/NIR_MOS_arc.odg docs/process/NIR_MOS_arc.svg docs/process/NIR_MOS_flat.odg docs/process/NIR_MOS_flat.svg docs/process/NIR_MOS_science.odg docs/process/NIR_MOS_science.svg docs/process/NIR_MOS_trace.odg docs/process/NIR_MOS_trace.svg docs/process/NIR_arcs.rst docs/process/NIR_flats.rst docs/process/NIR_science_data.rst docs/process/index.rst licenses/KOSMOS_LICENSE licenses/LICENSE.rst specreduce/__init__.py specreduce/background.py specreduce/calibration_data.py specreduce/core.py specreduce/extract.py specreduce/fluxcal.py specreduce/line_matching.py specreduce/table_utils.py specreduce/tracing.py specreduce/version.py specreduce/wavelength_calibration.py specreduce.egg-info/PKG-INFO specreduce.egg-info/SOURCES.txt specreduce.egg-info/dependency_links.txt specreduce.egg-info/requires.txt specreduce.egg-info/top_level.txt specreduce/tests/__init__.py specreduce/tests/test_background.py specreduce/tests/test_core.py specreduce/tests/test_extinction.py specreduce/tests/test_extract.py specreduce/tests/test_image_parsing.py specreduce/tests/test_line_matching.py specreduce/tests/test_linelists.py specreduce/tests/test_mask_treatment.py specreduce/tests/test_specphot_stds.py specreduce/tests/test_synth_data.py specreduce/tests/test_tracing.py specreduce/tests/test_utils.py specreduce/tests/test_wavelength_calibration.py specreduce/tests/data/transposed_det_image_seq5_MIRIMAGE_P750Lexp1_s2d.fits specreduce/utils/__init__.py specreduce/utils/synth_data.py specreduce/utils/utils.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790933.0 specreduce-1.5.1/specreduce.egg-info/dependency_links.txt0000644000175100001660000000000114764317325023141 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790933.0 specreduce-1.5.1/specreduce.egg-info/requires.txt0000644000175100001660000000024714764317325021476 0ustar00runnerdockernumpy astropy scipy specutils>=1.9.1 gwcs [all] matplotlib photutils synphot [docs] sphinx-astropy matplotlib photutils synphot [test] pytest-astropy photutils tox ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790933.0 specreduce-1.5.1/specreduce.egg-info/top_level.txt0000644000175100001660000000005714764317325021627 0ustar00runnerdockerdist docs licenses notebook_sandbox specreduce ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1741790923.0 specreduce-1.5.1/tox.ini0000644000175100001660000000525514764317313014576 0ustar00runnerdocker[tox] envlist = py{310,311,312,313}-test{,-alldeps}{,-oldestdeps,-devdeps,-predeps}{,-cov} linkcheck codestyle [testenv] # Pass through the following environment variables which may be needed for the CI passenv = HOME,WINDIR,CI setenv = 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 changedir = .tmp/{envname} # tox environments are constructed with so-called 'factors' (or terms) # separated by hyphens, e.g. test-devdeps-cov. Lines below starting with factor: # will only take effect if that factor is included in the environment name. To # see a list of example environments that can be run, along with a description, # run: # # tox -l -v # description = run tests alldeps: with optional dependencies oldestdeps: with the oldest supported version of key dependencies 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: photutils>=0.0.dev0 devdeps: git+https://github.com/spacetelescope/synphot_refactor.git#egg=synphot 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 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 = pip freeze !cov: pytest --pyargs specreduce {toxinidir}/docs {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:linkcheck] changedir = docs description = check the links in the HTML docs extras = docs commands = pip freeze sphinx-build -W -b linkcheck . _build/html [testenv:codestyle] skip_install = true changedir = . description = check code style, e.g., with flake8 deps = flake8 commands = flake8 specreduce --count --extend-ignore E203