From 247586216ecaa8403299d7ed2fafb2600cb2b59d Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 13:17:18 -0500 Subject: [PATCH 01/31] Add pre-commit config --- .github/CODEOWNERS | 2 +- .github/dependabot.yml | 1 - .github/workflows/downstream.yml | 2 +- .pre-commit-config.yaml | 60 ++++++++++++++++++++++++++ CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 2 +- README.rst | 6 +-- docs/_templates/autosummary/base.rst | 2 +- docs/_templates/autosummary/class.rst | 2 +- docs/_templates/autosummary/module.rst | 2 +- docs/gwcs/fits_analog.rst | 2 +- docs/gwcs/ifu.rst | 9 ++-- docs/gwcs/points_to_wcs.rst | 40 ++++++++--------- docs/gwcs/pure_asdf.rst | 4 +- docs/gwcs/using_wcs.rst | 8 ++-- docs/gwcs/wcstools.rst | 3 +- 16 files changed, 101 insertions(+), 46 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3beeeb49..88cc5998 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # automatically requests pull request reviews for files matching the given pattern; the last match takes precendence -* @spacetelescope/gwcs-maintainers +* @spacetelescope/gwcs-maintainers diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9c39a860..1a218f5e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,4 +13,3 @@ updates: actions: patterns: - "*" - diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 3f1bce7e..d9c1c26f 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -83,7 +83,7 @@ jobs: GALSIM_CAT_PATH: ${{ needs.romanisim_data.outputs.cache_path }}/galsim_data/real_galaxy_catalog_23.5_example.fits FFTW_DIR: /opt/homebrew/opt/fftw/lib/ cache-path: ${{ needs.romanisim_data.outputs.cache_path }} - cache-key: ${{ needs.romanisim_data.outputs.cache_key }} + cache-key: ${{ needs.romanisim_data.outputs.cache_key }} envs: | - linux: py311-test-romanisim-cov-xdist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..02965fee --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,60 @@ +ci: + autofix_prs: false + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + args: ["--enforce-all", "--maxkb=300"] + # Prevent giant files from being committed. + - id: check-case-conflict + # Check for files with names that would conflict on a case-insensitive + # filesystem like MacOS HFS+ or Windows FAT. + - id: check-json + # Attempts to load all json files to verify syntax. + - id: check-merge-conflict + # Check for files that contain merge conflict strings. + - id: check-symlinks + # Checks for symlinks which do not point to anything. + - id: check-toml + # Attempts to load all TOML files to verify syntax. + - id: check-xml + # Attempts to load all xml files to verify syntax. + - id: check-yaml + # Attempts to load all yaml files to verify syntax. + - id: detect-private-key + # Checks for the existence of private keys. + - id: end-of-file-fixer + # Makes sure files end in a newline and only a newline. + exclude: ".*(data.*)$" + # - id: fix-encoding-pragma # covered by pyupgrade + - id: trailing-whitespace + # Trims trailing whitespace. + exclude_types: [python] # Covered by Ruff W291. + exclude: ".*(data.*)$" + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-directive-colons + # Detect mistake of rst directive not ending with double colon. + - id: rst-inline-touching-normal + # Detect mistake of inline code touching normal text in rst. + - id: text-unicode-replacement-char + # Forbid files which have a UTF-8 Unicode replacement character. + + # - repo: https://github.com/codespell-project/codespell + # rev: v2.3.0 + # hooks: + # - id: codespell + # args: ["--write-changes"] + # additional_dependencies: + # - tomli + + # - repo: https://github.com/astral-sh/ruff-pre-commit + # rev: v0.6.9 + # hooks: + # - id: ruff + # args: ["--fix", "--show-fixes"] + # - id: ruff-format diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ebfc6acc..ddba00df 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,6 @@ # Spacetelescope Open Source Code of Conduct -We expect all "spacetelescope" organization projects to adopt a code of conduct that ensures a productive, respectful environment for all open source contributors and participants. We are committed to providing a strong and enforced code of conduct and expect everyone in our community to follow these guidelines when interacting with others in all forums. Our goal is to keep ours a positive, inclusive, successful, and growing community. The community of participants in open source Astronomy projects is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences success and continued growth. +We expect all "spacetelescope" organization projects to adopt a code of conduct that ensures a productive, respectful environment for all open source contributors and participants. We are committed to providing a strong and enforced code of conduct and expect everyone in our community to follow these guidelines when interacting with others in all forums. Our goal is to keep ours a positive, inclusive, successful, and growing community. The community of participants in open source Astronomy projects is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences success and continued growth. As members of the community, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3df6cff..0f12a5b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ Please open a new issue or new pull request for bugs, feedback, or new features you would like to see. If there is an issue you would like to work on, please leave a comment and we will be happy to assist. New contributions and contributors are very welcome! -New to github or open source projects? If you are unsure about where to start or haven't used github before, please feel free to contact the package maintainers. +New to github or open source projects? If you are unsure about where to start or haven't used github before, please feel free to contact the package maintainers. Feedback and feature requests? Is there something missing you would like to see? Please open an issue or send an email to the maintainers. This package follows the Spacetelescope [Code of Conduct](CODE_OF_CONDUCT.md) strives to provide a welcoming community to all of our users and contributors. diff --git a/README.rst b/README.rst index cb6bd6fc..ca1ecbfa 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ GWCS - Generalized World Coordinate System .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat :target: http://www.astropy.org :alt: Powered by Astropy Badge - + .. image:: https://img.shields.io/badge/powered%20by-STScI-blue.svg?colorA=707170&colorB=3e8ddd&style=flat :target: http://www.stsci.edu :alt: Powered by STScI Badge @@ -37,7 +37,7 @@ Installation To install:: - pip install gwcs + pip install gwcs To clone from github and install the master branch:: @@ -45,7 +45,7 @@ To clone from github and install the master branch:: cd gwcs pip install --editable . - + Contributing Code, Documentation, or Feedback --------------------------------------------- diff --git a/docs/_templates/autosummary/base.rst b/docs/_templates/autosummary/base.rst index 9cabaf52..16217194 100644 --- a/docs/_templates/autosummary/base.rst +++ b/docs/_templates/autosummary/base.rst @@ -1,2 +1,2 @@ {% 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. #} \ No newline at end of file +{# 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. #} diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst index 6b214a5c..0fa59f28 100644 --- a/docs/_templates/autosummary/class.rst +++ b/docs/_templates/autosummary/class.rst @@ -1,2 +1,2 @@ {% 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. #} \ No newline at end of file +{# 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. #} diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst index f38315b2..230cd6e2 100644 --- a/docs/_templates/autosummary/module.rst +++ b/docs/_templates/autosummary/module.rst @@ -1,2 +1,2 @@ {% 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. #} \ No newline at end of file +{# 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. #} diff --git a/docs/gwcs/fits_analog.rst b/docs/gwcs/fits_analog.rst index b7952f62..e10ffb6f 100644 --- a/docs/gwcs/fits_analog.rst +++ b/docs/gwcs/fits_analog.rst @@ -95,7 +95,7 @@ coordinates(1, 2) to sky coordinates: -The :meth:`~gwcs.wcs.WCS.invert` method evaluates the :meth:`~gwcs.wcs.WCS.backward_transform` to provide a mapping from sky coordinates to pixel coordinates +The :meth:`~gwcs.wcs.WCS.invert` method evaluates the :meth:`~gwcs.wcs.WCS.backward_transform` to provide a mapping from sky coordinates to pixel coordinates if available, otherwise it applies an iterative method to calculate the pixel coordinates. >>> wcsobj.invert(sky) diff --git a/docs/gwcs/ifu.rst b/docs/gwcs/ifu.rst index 200731fd..d6fcd14c 100644 --- a/docs/gwcs/ifu.rst +++ b/docs/gwcs/ifu.rst @@ -3,7 +3,7 @@ An IFU Example - managing a discontiguous WCS An IFU image represents the projection of several slices on a detector. Between the slices there are pixels which don't belong to any slice. -In general each slice has a unique WCS transform. +In general each slice has a unique WCS transform. There are two ways to represent this kind of transforms in GWCS depending on the way the instrument is calibrated and the available information. @@ -106,9 +106,9 @@ storing transforms in a dictionary. The model would look like this: from astropy.modeling.core import Model from astropy.modeling.parameters import Parameter - + class CustomModel(Model): - + inputs = ('label', 'x', 'y') outputs = ('xout', 'yout') @@ -117,8 +117,7 @@ storing transforms in a dictionary. The model would look like this: super().__init__() self.labels = labels self.models = models - + def evaluate(self, label, x, y): index = self.labels.index(label) return self.models[index](x, y) - diff --git a/docs/gwcs/points_to_wcs.rst b/docs/gwcs/points_to_wcs.rst index e20b4de8..432fdb62 100644 --- a/docs/gwcs/points_to_wcs.rst +++ b/docs/gwcs/points_to_wcs.rst @@ -4,33 +4,33 @@ Fitting a WCS to input pixels & sky positions ============================================= -Suppose we have an image where we have centroid positions for a number of sources, and we have matched these +Suppose we have an image where we have centroid positions for a number of sources, and we have matched these positions to an external catalog to obtain (RA, Dec). If this data is missing or has inaccurate WCS information, it is useful to fit or re-fit a GWCS object with this matched list of coordinate pairs to be able to transform -between pixel and sky. +between pixel and sky. -This example shows how to use the `~gwcs.wcstools.wcs_from_points` tool to fit a WCS to a matched set of +This example shows how to use the `~gwcs.wcstools.wcs_from_points` tool to fit a WCS to a matched set of pixel and sky positions. Along with arrays of the (x,y) pixel position in the image and the matched sky coordinates, the fiducial point for the projection must be supplied as a `~astropy.coordinates.SkyCoord` object. Additionally, the projection type must be specified from the available projections in `~astropy.modeling.projections.projcodes`. -Geometric distortion can also be fit to the input coordinates - the distortion type (2D polynomial, chebyshev, legendre) and +Geometric distortion can also be fit to the input coordinates - the distortion type (2D polynomial, chebyshev, legendre) and the degree can be supplied to fit this component of the model. -The following example will show how to fit a WCS, including a 4th degree 2D polynomial, to a set of input pixel positions of -sources in an image and their corresponding positions on the sky obtained from a catalog. +The following example will show how to fit a WCS, including a 4th degree 2D polynomial, to a set of input pixel positions of +sources in an image and their corresponding positions on the sky obtained from a catalog. Import the wcs_from_points function, >>> from gwcs.wcstools import wcs_from_points - + along with some useful general imports. >>> from astropy.coordinates import SkyCoord >>> from astropy.io import ascii >>> import astropy.units as u >>> import numpy as np - + A collection of 20 matched coordinate pairs in x, y, RA, and Dec will be used to fit the WCS information. The function requires tuples of arrays for x & y, and a `~astropy.coordinates.SkyCoord` object for sky coordinates. >>> xy = (np.array([2810.156, 2810.156, 650.236, 1820.927, 3425.779, 2750.369, @@ -52,34 +52,34 @@ A collection of 20 matched coordinate pairs in x, y, RA, and Dec will be used to ... 43.48218262, 43.46908299, 43.46131665, 43.46560591, ... 43.47791234, 43.45973025, 43.47208325, 43.47779988])) >>> radec = SkyCoord(ra, dec, unit=(u.deg, u.deg)) - - + + We can now choose the reference point on the sky for the projection. This can either be an `~astropy.coordinates.SkyCoord` object, or the string 'center' to use the geometric center of input -points. In this example, we will specify exact coordinates for the fiducial. - +points. In this example, we will specify exact coordinates for the fiducial. + >>> proj_point = SkyCoord(246.7368408, 43.480712949, frame = 'icrs', unit = (u.deg,u.deg)) - + We can now call the function that returns a GWCS object corresponding to the best fit parameters that relate the input pixels and sky coordinates with a TAN projection centered at the reference point -we specified, with a distortion model (degree 4 polynomial). This function will return a GWCS object that +we specified, with a distortion model (degree 4 polynomial). This function will return a GWCS object that can be used to transform between coordinate frames. - + >>> gwcs_obj = wcs_from_points(xy, radec, proj_point) -This GWCS object contains parameters for a TAN projection, rotation, scale, skew and a polynomial fit to x and y +This GWCS object contains parameters for a TAN projection, rotation, scale, skew and a polynomial fit to x and y that represent the best-fit to the input coordinates. With WCS information associated with the data now, we can -easily work in both pixel and sky space, and transform between frames. +easily work in both pixel and sky space, and transform between frames. The GWCS object, which by default when called executes for forward transformation, can be used to convert coordinates from pixel to world. >>> gwcs_obj(36.235,642.215) # doctest: +FLOAT_CMP (246.72158004206716, 43.46075091731673) - -Or using the common WCS API + +Or using the common WCS API >>> gwcs_obj.pixel_to_world_values(36.235,642.215) # doctest: +FLOAT_CMP (246.72158004206716, 43.46075091731673) >>> gwcs_obj.pixel_to_world(36.235,642.215) # doctest: +FLOAT_CMP \ No newline at end of file + (246.7215802, 43.46075103)> diff --git a/docs/gwcs/pure_asdf.rst b/docs/gwcs/pure_asdf.rst index 26c644a4..c83433bd 100644 --- a/docs/gwcs/pure_asdf.rst +++ b/docs/gwcs/pure_asdf.rst @@ -4,7 +4,7 @@ Listing of ``imaging_wcs.asdf`` =============================== Listing of ``imaging_wcs.asdf``:: - + #ASDF 1.0.0 #ASDF_STANDARD 1.2.0 @@ -133,5 +133,3 @@ Listing of ``imaging_wcs.asdf``:: frame_attributes: {} unit: [!unit/unit-1.0.0 deg, !unit/unit-1.0.0 deg] ... - - diff --git a/docs/gwcs/using_wcs.rst b/docs/gwcs/using_wcs.rst index c3ff9d26..7ec02312 100644 --- a/docs/gwcs/using_wcs.rst +++ b/docs/gwcs/using_wcs.rst @@ -77,14 +77,14 @@ Calling the :meth:`~gwcs.WCS.footprint` returns the footprint on the sky. .. warning:: GWCS and astropy default to different tuple ordering conventions for representing - multi-dimensional bounding boxes. - + multi-dimensional bounding boxes. + * GWCS uses the ``"F"`` ordering convention, where the tuples are ordered ``((x0min, x0max), (x1min, x1max), ..., (xnmin, xnmax))`` (x,y,z ordering). * While astropy uses the ``"C"`` ordering convention, where tuples are ordered ``((xnmin, xnmax), ..., (x1min, x1max), (x0min, x0max))`` (z, y, x ordering). - This means that given the same tuple of tuples, say ``((a, b), (c, d))``, setting + This means that given the same tuple of tuples, say ``((a, b), (c, d))``, setting the bounding box on the transform prior to creating the GWCS will result in a different bounding box than if one sets the same tuple of tuples on the GWCS object itself. Indeed, in this case the former will assume ``(c, d)`` is the bounding box @@ -99,7 +99,7 @@ Calling the :meth:`~gwcs.WCS.footprint` returns the footprint on the sky. `~astropy.modeling.bind_bounding_box` with the ``order`` argument properly set. -.. note :: +.. note:: The GWCS will always convert or assume the bounding box to the ``"F"`` ordering convention when setting the bounding box on the GWCS object itself and will diff --git a/docs/gwcs/wcstools.rst b/docs/gwcs/wcstools.rst index d90f2d65..bf309ea0 100644 --- a/docs/gwcs/wcstools.rst +++ b/docs/gwcs/wcstools.rst @@ -7,7 +7,7 @@ WCS User Tools >>> bounding_box = ((0, 4096), (0, 2048)) >>> x, y = grid_from_bounding_box(bounding_box) >>> ra, dec = w(x, y) # doctest: +SKIP - + The `~gwcs.wcstools` module contains functions of general usability. `~gwcs.wcstools.wcs_from_fiducial` is a function which given a fiducial in some coordinate system, @@ -28,4 +28,3 @@ Any additional transforms are prepended to the projection and sky rotation. >>> w = wcs_from_fiducial(fiducial, projection=tan, transform=trans) >>> w(2048, 1024) # doctest: +FLOAT_CMP (5.46, -72.2) - From c5d1631f53221ba9f87dc1950072a11760fb1b60 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 13:27:34 -0500 Subject: [PATCH 02/31] Add spell checking --- .github/CODEOWNERS | 2 +- .pre-commit-config.yaml | 15 ++++++++------- CHANGES.rst | 4 ++-- convert_schemas.py | 2 +- docs/gwcs/ifu.rst | 2 +- docs/index.rst | 2 +- gwcs/coordinate_frames.py | 4 ++-- gwcs/region.py | 2 +- gwcs/spectroscopy.py | 8 ++++---- gwcs/tests/test_api.py | 6 +++--- gwcs/tests/test_coordinate_systems.py | 12 ++++++------ gwcs/tests/test_region.py | 2 +- gwcs/tests/test_wcs.py | 4 ++-- gwcs/utils.py | 6 +++--- gwcs/wcs.py | 12 ++++++------ gwcs/wcstools.py | 16 ++++++++-------- tox.ini | 2 +- 17 files changed, 51 insertions(+), 50 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 88cc5998..6e12fe79 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ -# automatically requests pull request reviews for files matching the given pattern; the last match takes precendence +# automatically requests pull request reviews for files matching the given pattern; the last match takes precedence * @spacetelescope/gwcs-maintainers diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02965fee..cedc6700 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,13 +44,14 @@ repos: - id: text-unicode-replacement-char # Forbid files which have a UTF-8 Unicode replacement character. - # - repo: https://github.com/codespell-project/codespell - # rev: v2.3.0 - # hooks: - # - id: codespell - # args: ["--write-changes"] - # additional_dependencies: - # - tomli + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: ["--write-changes"] + additional_dependencies: + - tomli + exclude: ".*.asdf" # - repo: https://github.com/astral-sh/ruff-pre-commit # rev: v0.6.9 diff --git a/CHANGES.rst b/CHANGES.rst index 16bcb536..17d51151 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -221,7 +221,7 @@ New Features - Added ``insert_frame`` method to modify the pipeline of a ``WCS`` object. [#299] - Added ``to_fits_tab`` method to generate FITS header and binary table - extension following FITS WCS ``-TAB`` convension. [#295] + extension following FITS WCS ``-TAB`` conversion. [#295] - Added ``in_image`` function for testing whether a point in world coordinates maps back to the domain of definition of the forward transformation. [#322] @@ -277,7 +277,7 @@ New Features - Removed astropy-helpers from package. [#249] -- Added a method ``fix_inputs`` which rturns an unique WCS from a compound +- Added a method ``fix_inputs`` which returns an unique WCS from a compound WCS by fixing inputs. [#254] - Added two new transforms - ``ToDirectionCosines`` and ``FromDirectionCosines``. [#256] diff --git a/convert_schemas.py b/convert_schemas.py index feb5d327..ccc6bfd2 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -74,7 +74,7 @@ def format_range(var_middle, var_end, minimum, maximum, the ``x`` in ``0 ≤ x ≤ 2``. var_end : str or None - The string to put at one end of a single comparision, such as + The string to put at one end of a single comparison, such as the ``x`` in ``x ≤ 0``. minimum : number diff --git a/docs/gwcs/ifu.rst b/docs/gwcs/ifu.rst index d6fcd14c..09e2685d 100644 --- a/docs/gwcs/ifu.rst +++ b/docs/gwcs/ifu.rst @@ -98,7 +98,7 @@ a specific label. Custom model storing transforms in a dictionary ----------------------------------------------- -In case a pixel to slice mapping is not available, one can write a custom mdoel +In case a pixel to slice mapping is not available, one can write a custom model storing transforms in a dictionary. The model would look like this: .. code:: diff --git a/docs/index.rst b/docs/index.rst index d90f59a7..4b06a726 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -159,7 +159,7 @@ involves 4 sequential transformations: projection. If the field of view is small, the inaccuracies resulting leaving this out will be small; however, this is generally applied. - Transforming the center pixel to the appropriate celestial coordinate - with the approprate orientation on the sky. For simplicity's sake, + with the appropriate orientation on the sky. For simplicity's sake, we assume the detector array is already oriented with north up, and that the array has the appropriate parity as the sky coordinates. diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 3f93c5d0..42b2bdb1 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -41,7 +41,7 @@ Each frame instance is both metadata for the inputs/outputs of a transform and also a converter between those inputs/outputs and richer coordinate -representations of those inputs/ouputs. +representations of those inputs/outputs. For example, an output frame of type `~gwcs.coordinate_frames.SpectralFrame` provides metadata to the `.WCS` object such as the ``axes_type`` being @@ -361,7 +361,7 @@ def _native_world_axis_object_components(self): """ This property holds the "native" frame order of the components. - The native order of the componets is the order the frame assumes the + The native order of the components is the order the frame assumes the axes are in when creating the high level objects, for example ``CelestialFrame`` creates ``SkyCoord`` objects which are in lon, lat order (in their positional args). diff --git a/gwcs/region.py b/gwcs/region.py index 8faeaee6..e6013355 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -183,7 +183,7 @@ def scan(self, data): - Initialize the Active Edge Table (AET) to be empty - For each scan line: 1. Add edges from GET to AET for which ymin==y - 2. Remove edges from AET fro which ymax==y + 2. Remove edges from AET for which ymax==y 3. Compute the intersection of the current scan line with all edges in the AET 4. Sort on X of intersection point 5. Set elements between pairs of X in the AET to the Edge's ID diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index fac4327a..2d8725f4 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -324,10 +324,10 @@ def evaluate(self, wavelength, temp, ref_temp, ref_pressure, temp = temp.to(u.Celsius) ref_temp = ref_temp.to(u.Celsius) else: - KtoC = 273.15 # kelvin to celcius conversion + KtoC = 273.15 # kelvin to celsius conversion temp -= KtoC ref_temp -= KtoC - delt = temp - ref_temp + delta = temp - ref_temp D0, D1, D2 = D_coef[0] E0, E1, lam_tk = E_coef[0] @@ -347,8 +347,8 @@ def evaluate(self, wavelength, temp, ref_temp, ref_pressure, # Compute the absolute index of the glass delnabs = (0.5 * (nrel ** 2 - 1.) / nrel) * \ - (D0 * delt + D1 * delt ** 2 + D2 * delt ** 3 + \ - (E0 * delt + E1 * delt ** 2) / (lamrel ** 2 - lam_tk ** 2)) + (D0 * delta + D1 * delta ** 2 + D2 * delta ** 3 + \ + (E0 * delta + E1 * delta ** 2) / (lamrel ** 2 - lam_tk ** 2)) nabs_obs = nabs_ref + delnabs # Define the relative index at the system's operating T and P. diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index 5d196905..c7bfeb50 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -64,7 +64,7 @@ def wcs_ndim_types_units(request): @fixture_all_wcses def test_lowlevel_types(wcsobj): try: - # Skip this on older versions of astropy where it dosen't exist. + # Skip this on older versions of astropy where it doesn't exist. from astropy.wcs.wcsapi.tests.utils import validate_low_level_wcs_types except ImportError: return @@ -123,7 +123,7 @@ def test_pixel_to_world_values_units_2d(gwcs_2d_shift_scale_quantity, x, y): call_world = wcsobj(*call_pixel) api_world = wcsobj.pixel_to_world_values(*api_pixel) - # Check that call returns quantities and api dosen't + # Check that call returns quantities and api doesn't assert all(list(isinstance(a, u.Quantity) for a in call_world)) assert all(list(not isinstance(a, u.Quantity) for a in api_world)) @@ -147,7 +147,7 @@ def test_pixel_to_world_values_units_1d(gwcs_1d_freq_quantity, x): call_world = wcsobj(call_pixel) api_world = wcsobj.pixel_to_world_values(api_pixel) - # Check that call returns quantities and api dosen't + # Check that call returns quantities and api doesn't assert isinstance(call_world, u.Quantity) assert not isinstance(api_world, u.Quantity) diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index aa572096..f65339ee 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -80,12 +80,12 @@ def coordinate_to_quantity(*inputs, frame): @pytest.mark.parametrize('inputs', inputs2) def test_coordinates_spatial(inputs): - sky_coo = coordinates(*inputs, frame=icrs) - assert isinstance(sky_coo, coord.SkyCoord) - assert_allclose((sky_coo.ra.value, sky_coo.dec.value), inputs) - focal_coo = coordinates(*inputs, frame=focal) - assert_allclose([coo.value for coo in focal_coo], inputs) - assert [coo.unit for coo in focal_coo] == [u.m, u.m] + sky_coord = coordinates(*inputs, frame=icrs) + assert isinstance(sky_coord, coord.SkyCoord) + assert_allclose((sky_coord.ra.value, sky_coord.dec.value), inputs) + focal_coord = coordinates(*inputs, frame=focal) + assert_allclose([coord.value for coord in focal_coord], inputs) + assert [coord.unit for coord in focal_coord] == [u.m, u.m] @pytest.mark.parametrize('inputs', inputs1) diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 6304b786..37779292 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -223,7 +223,7 @@ def test_RegionsSelector(): # test inverse rsinv = reg_selector.inverse - # The label_mapper arays should be the same + # The label_mapper arrays should be the same assert_equal(reg_selector.label_mapper.mapper, rsinv.label_mapper.mapper) # the transforms of the inverse ``RegionsSelector`` should be the inverse of the # transforms of the ``RegionsSelector`` model. diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index d10cfed9..86476350 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -1416,7 +1416,7 @@ def test_bounding_box_is_returned_F(): def test_no_bounding_box_if_read_from_file(tmp_path): bad_wcs = gwcs_2d_bad_bounding_box_order() - # Check the waring is issued for the bounding box of this WCS object + # Check the warning is issued for the bounding box of this WCS object with pytest.warns(wcs.GwcsBoundingBoxWarning): bad_wcs.bounding_box @@ -1447,7 +1447,7 @@ def test_split_frame_wcs(): # what the projections require in astropy. # Input is (lat, wave, lon) - # lat: multuply by 20 arcsec, lon: multiply by 15 deg + # lat: multiply by 20 arcsec, lon: multiply by 15 deg # result should be 20 arcsec, 10nm, 45 deg spatial = models.Multiply(20*u.arcsec/u.pix) & models.Multiply(15*u.deg/u.pix) compound = models.Linear1D(intercept=0*u.nm, slope=10*u.nm/u.pix) & spatial diff --git a/gwcs/utils.py b/gwcs/utils.py index e50c6486..9fc5ce5f 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -249,7 +249,7 @@ def get_axes(header): Returns ------- sky_inmap, spectral_inmap, unknown : lists - indices in the input representing sky and spectral cordinates. + indices in the input representing sky and spectral coordinates. """ if isinstance(header, fits.Header): @@ -282,7 +282,7 @@ def get_axes(header): def _is_skysys_consistent(ctype, sky_inmap): - """ Determine if the sky axes in CTYPE mathch to form a standard celestial system.""" + """ Determine if the sky axes in CTYPE match to form a standard celestial system.""" for item in sky_pairs.values(): if ctype[sky_inmap[0]] == item[0]: @@ -430,7 +430,7 @@ def fitswcs_nonlinear(header): if sky_axes: phip, lonp = [wcs_info['CRVAL'][i] for i in sky_axes] # TODO: write "def compute_lonpole(projcode, l)" - # Set a defaul tvalue for now + # Set a default tvalue for now thetap = 180 n2c = astmodels.RotateNative2Celestial(phip, lonp, thetap, name="crval") transforms.append(n2c) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 735cd288..de684805 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -100,7 +100,7 @@ def __init__(self, axis, frame, world_axis_order, cunit, ctype, input_axes): Index of this axis in `gwcs.WCS.output_frame.axes_order` cunit : str - Axis unit using FITS convension (``CUNIT``). + Axis unit using FITS conversion (``CUNIT``). ctype : str Axis FITS type (``CTYPE``). @@ -1394,7 +1394,7 @@ def bounding_box(self): ): warnings.warn( "The bounding_box was set in C order on the transform prior to being used in the gwcs!\n" - "Check that you indended that ordering for the bounding_box, and consider setting it in F order.\n" + "Check that you intended that ordering for the bounding_box, and consider setting it in F order.\n" "The bounding_box will remain meaning the same but will be converted to F order for consistency in the GWCS.", GwcsBoundingBoxWarning ) @@ -2208,7 +2208,7 @@ def to_fits_tab(self, bounding_box=None, bin_ext_name='WCS-TABLE', bin_ext_name : str, optional Extension name for the `~astropy.io.fits.BinTableHDU` HDU for those axes groups that will be converted using FITW WCS' ``-TAB`` - algorith. Extension version will be determined automatically + algorithm. Extension version will be determined automatically based on the number of separable group of axes. coord_col_name : str, optional @@ -2311,7 +2311,7 @@ def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, ``max_inv_pix_error``, ``inv_degree``, and ``projection``) are ignored. When a WCS, in addition to celestial frame, contains other types of axes, SIP distortion fitting is - disabled (ony linear terms are fitted for celestial frame). + disabled (only linear terms are fitted for celestial frame). Parameters ---------- @@ -2385,7 +2385,7 @@ def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, bin_ext_name : str, optional Extension name for the `~astropy.io.fits.BinTableHDU` HDU for those axes groups that will be converted using FITW WCS' ``-TAB`` - algorith. Extension version will be determined automatically + algorithm. Extension version will be determined automatically based on the number of separable group of axes. coord_col_name : str, optional @@ -2764,7 +2764,7 @@ def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, def _calc_approx_inv(self, max_inv_pix_error=5, inv_degree=None, npoints=16): """ Compute polynomial fit for the inverse transformation to be used as - initial aproximation/guess for the iterative solution. + initial approximation/guess for the iterative solution. """ self._approx_inverse = None diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 10716679..39cf56f2 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -39,7 +39,7 @@ def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, Projection instance - required if there is a celestial component in the fiducial. transform : `~astropy.modeling.Model` (optional) - An optional tranform to be prepended to the transform constructed by + An optional transform to be prepended to the transform constructed by the fiducial point. The number of outputs of this transform must equal the number of axes in the coordinate frame. name : str @@ -213,18 +213,18 @@ def _bbox_to_pixel(bbox): # 1D case if np.isscalar(bounding_box[0]): - nd = 1 + ndim = 1 bounding_box = (bounding_box, ) else: - nd = len(bounding_box) + ndim = len(bounding_box) if center: bb = tuple([_bbox_to_pixel(bb) for bb in bounding_box]) else: bb = bounding_box step = np.atleast_1d(step) - if nd > 1 and len(step) == 1: - step = np.repeat(step, nd) + if ndim > 1 and len(step) == 1: + step = np.repeat(step, ndim) if len(step) != len(bb): raise ValueError('`step` must be a scalar, or tuple with length ' @@ -234,7 +234,7 @@ def _bbox_to_pixel(bbox): for d, s in zip(bb, step): slices.append(slice(d[0], d[1] + s, s)) grid = np.mgrid[slices[::-1]][::-1] - if nd == 1: + if ndim == 1: return grid[0] return grid @@ -251,7 +251,7 @@ def wcs_from_points(xy, world_coords, proj_point='center', ``world_coords`` are transformed to a projection plane using the specified projection. A polynomial fits ``xy`` and the projected coordinates. The fitted polynomials and the projection transforms are combined into a - tranform from detector to sky. The input coordinate frame is set to + transform from detector to sky. The input coordinate frame is set to ``detector``. The output coordinate frame is initialized based on the frame in the fiducial. @@ -259,7 +259,7 @@ def wcs_from_points(xy, world_coords, proj_point='center', Parameters ---------- xy : tuple of 2 ndarrays - Points in the input cooridnate frame - x, y inputs. + Points in the input coordinate frame - x, y inputs. world_coords : `~astropy.coordinates.SkyCoord` Points in the output coordinate frame. The order matches the order of ``xy``. diff --git a/tox.ini b/tox.ini index 6f9c6531..e2fb37f1 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = check-{style,security} test{,-dev}{,-oldestdeps,-cov}{-jwst,-romancal,-romanisim,-specutils,-dkist,-ndcube} docs -requres = +requires = tox-uv # tox environments are constructed with so-called 'factors' (or terms) From 964feb99da5f0db3d41ec112725df50a0171f2fa Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 13:34:05 -0500 Subject: [PATCH 03/31] Add basic ruff checks --- .pre-commit-config.yaml | 10 +++++----- docs/conf.py | 8 ++++---- gwcs/converters/selector.py | 8 ++++---- gwcs/selector.py | 10 +++++----- gwcs/tests/test_api.py | 2 +- gwcs/tests/test_api_slicing.py | 12 ++++++------ gwcs/tests/test_geometry.py | 6 +++--- gwcs/wcstools.py | 2 +- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cedc6700..962f08e7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,9 +53,9 @@ repos: - tomli exclude: ".*.asdf" - # - repo: https://github.com/astral-sh/ruff-pre-commit - # rev: v0.6.9 - # hooks: - # - id: ruff - # args: ["--fix", "--show-fixes"] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] # - id: ruff-format diff --git a/docs/conf.py b/docs/conf.py index 8e15eb80..0f6aa565 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,12 +53,12 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns.append('_templates') +exclude_patterns.append('_templates') # noqa: F405 # This is added to the end of RST files - a good place to put substitutions to # be used globally. -rst_epilog += """ -""" +rst_epilog += """ +""" # noqa: F405 # Top-level directory containing ASDF schemas (relative to current directory) asdf_schema_path = '../gwcs/schemas' @@ -148,7 +148,7 @@ [author], 1)] sys.path.insert(0, str(Path(__file__).parent / 'sphinxext')) -extensions += ['sphinx_asdf'] +extensions += ['sphinx_asdf'] # noqa: F405 # Enable nitpicky mode - which ensures that all references in the docs resolve. nitpicky = True diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index bd543dba..451204c7 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -44,7 +44,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): labels = mapper.get('labels') transforms = mapper.get('models') if isiterable(labels[0]): - labels = [tuple(l) for l in labels] + labels = [tuple(label) for label in labels] dict_mapper = dict(zip(labels, transforms)) return LabelMapperRange(inputs, dict_mapper, inputs_mapping) else: @@ -74,7 +74,7 @@ def to_yaml_tree_transform(self, model, tag, ctx): for k in labels: transforms.append(model.mapper[k]) if isiterable(labels[0]): - labels = [list(l) for l in labels] + labels = [list(label) for label in labels] mapper['labels'] = labels mapper['models'] = transforms node['mapper'] = mapper @@ -105,8 +105,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): node = OrderedDict() labels = list(model.selector) values = [] - for l in labels: - values.append(model.selector[l]) + for label in labels: + values.append(model.selector[label]) selector['labels'] = labels selector['transforms'] = values node['inputs'] = list(model.inputs) diff --git a/gwcs/selector.py b/gwcs/selector.py index 8b4d88b4..d20cca95 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -415,12 +415,12 @@ def _has_overlapping(ranges): start = ranges[:, 0] end = ranges[:, 1] start.sort() - l = [] + values = [] for v in start: - l.append([v, d[v]]) - l = np.array(l) - start = np.roll(l[:, 0], -1) - end = l[:, 1] + values.append([v, d[v]]) + values = np.array(values) + start = np.roll(values[:, 0], -1) + end = values[:, 1] if any((end - start)[:-1] > 0) or any(start[-1] > end): return True else: diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index c7bfeb50..dffc1dea 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -195,7 +195,7 @@ def test_world_axis_object_components_4d(gwcs_4d_identity_units): ('celestial', 1), ('spectral', 0), ('temporal', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) def test_world_axis_object_classes_2d(gwcs_2d_spatial_shift): diff --git a/gwcs/tests/test_api_slicing.py b/gwcs/tests/test_api_slicing.py index 3d38a9e9..b32723b6 100644 --- a/gwcs/tests/test_api_slicing.py +++ b/gwcs/tests/test_api_slicing.py @@ -56,7 +56,7 @@ def test_ellipsis(gwcs_3d_galactic_spectral): ('spectral', 0), ('celestial', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord assert wcs.world_axis_object_classes['celestial'][1] == () @@ -121,7 +121,7 @@ def test_spectral_slice(gwcs_3d_galactic_spectral): assert first_two == [('celestial', 1), ('celestial', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord assert wcs.world_axis_object_classes['celestial'][1] == () @@ -186,7 +186,7 @@ def test_spectral_range(gwcs_3d_galactic_spectral): ('spectral', 0), ('celestial', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord assert wcs.world_axis_object_classes['celestial'][1] == () @@ -254,7 +254,7 @@ def test_celestial_slice(gwcs_3d_galactic_spectral): ('spectral', 0), ('celestial', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord assert wcs.world_axis_object_classes['celestial'][1] == () @@ -323,7 +323,7 @@ def test_celestial_range(gwcs_3d_galactic_spectral): ('spectral', 0), ('celestial', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord assert wcs.world_axis_object_classes['celestial'][1] == () @@ -395,7 +395,7 @@ def test_no_array_shape(gwcs_3d_galactic_spectral): ('spectral', 0), ('celestial', 0)] - assert all([callable(l) for l in last_one]) + assert all([callable(last) for last in last_one]) assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord assert wcs.world_axis_object_classes['celestial'][1] == () diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index b00934c4..0806083f 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -21,10 +21,10 @@ def test_spherical_cartesian_inverse(): t = geometry.SphericalToCartesian() - assert type(t.inverse) == geometry.CartesianToSpherical + assert isinstance(t.inverse, geometry.CartesianToSpherical) t = geometry.CartesianToSpherical() - assert type(t.inverse) == geometry.SphericalToCartesian + assert isinstance(t.inverse, geometry.SphericalToCartesian) @pytest.mark.parametrize( @@ -123,7 +123,7 @@ def test_spher2cart_roundrip_arr(lonlat, unit, wrap_at): if isinstance(lon, np.ndarray): lon = np.mod(lon - 180.0, 360.0) - 180.0 else: - lon = [((l - 180.0) % 360.0) - 180.0 for l in lon] + lon = [((deg - 180.0) % 360.0) - 180.0 for deg in lon] atol = 1e-15 if unit is None: diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 39cf56f2..0038d1f3 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -9,7 +9,7 @@ from astropy import coordinates as coord from astropy import units as u -from .coordinate_frames import * # noqa +from .coordinate_frames import CelestialFrame, SpectralFrame, Frame2D, CompositeFrame from .utils import UnsupportedTransformError, UnsupportedProjectionError from .utils import _compute_lon_pole From 81f91e9188021309c7b440013f0c4fce84e52930 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 13:35:31 -0500 Subject: [PATCH 04/31] Apply code formatter --- .pre-commit-config.yaml | 2 +- convert_schemas.py | 309 +++---- docs/conf.py | 49 +- gwcs/__init__.py | 6 +- gwcs/api.py | 20 +- gwcs/converters/geometry.py | 42 +- gwcs/converters/selector.py | 112 +-- gwcs/converters/spectroscopy.py | 114 ++- gwcs/converters/tests/test_selector.py | 65 +- gwcs/converters/tests/test_transforms.py | 54 +- gwcs/converters/tests/test_wcs.py | 91 +-- gwcs/converters/wcs.py | 100 +-- gwcs/coordinate_frames.py | 366 ++++++--- gwcs/examples.py | 540 ++++++++----- gwcs/extension.py | 40 +- gwcs/geometry.py | 52 +- gwcs/region.py | 3 +- gwcs/selector.py | 111 ++- gwcs/spectroscopy.py | 137 ++-- gwcs/tests/conftest.py | 20 +- gwcs/tests/test_api.py | 268 +++--- gwcs/tests/test_api_slicing.py | 246 +++--- gwcs/tests/test_bounding_box.py | 92 ++- gwcs/tests/test_coordinate_systems.py | 356 +++++--- gwcs/tests/test_extension.py | 18 +- gwcs/tests/test_geometry.py | 123 +-- gwcs/tests/test_region.py | 170 ++-- gwcs/tests/test_spectroscopy_models.py | 42 +- gwcs/tests/test_utils.py | 90 ++- gwcs/tests/test_wcs.py | 802 ++++++++++-------- gwcs/tests/utils.py | 24 +- gwcs/utils.py | 173 ++-- gwcs/wcs.py | 990 ++++++++++++++--------- gwcs/wcstools.py | 156 ++-- 34 files changed, 3513 insertions(+), 2270 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 962f08e7..e2b2d6ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,4 +58,4 @@ repos: hooks: - id: ruff args: ["--fix", "--show-fixes"] - # - id: ruff-format + - id: ruff-format diff --git a/convert_schemas.py b/convert_schemas.py index ccc6bfd2..f8ca257d 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -12,7 +12,7 @@ def write_if_different(filename, data): - """ Write ``data`` to ``filename``, if the content of the file is different. + """Write ``data`` to ``filename``, if the content of the file is different. Parameters ---------- @@ -25,15 +25,14 @@ def write_if_different(filename, data): os.makedirs(os.path.dirname(filename)) if os.path.exists(filename): - with open(filename, 'rb') as fd: + with open(filename, "rb") as fd: original_data = fd.read() else: original_data = None if original_data != data: - print("Converting schema {0}".format( - os.path.basename(filename))) - with open(filename, 'wb') as fd: + print("Converting schema {0}".format(os.path.basename(filename))) + with open(filename, "wb") as fd: fd.write(data) @@ -51,18 +50,19 @@ def write_header(o, content, level): level : int The level of the header """ - levels = '=-~^.' + levels = "=-~^." if level >= len(levels): - o.write('**{0}**\n\n'.format(content)) + o.write("**{0}**\n\n".format(content)) else: o.write(content) - o.write('\n') + o.write("\n") o.write(levels[level] * len(content)) - o.write('\n\n') + o.write("\n\n") -def format_range(var_middle, var_end, minimum, maximum, - exclusiveMinimum, exclusiveMaximum): +def format_range( + var_middle, var_end, minimum, maximum, exclusiveMinimum, exclusiveMaximum +): """ Formats an mathematical description of a range, for example, ``0 ≤ x ≤ 2``. @@ -95,35 +95,35 @@ def format_range(var_middle, var_end, minimum, maximum, The formatted range expression """ if minimum is not None and maximum is not None: - part = '{0} '.format(minimum) + part = "{0} ".format(minimum) if exclusiveMinimum: - part += '<' + part += "<" else: - part += '≤' - part += ' {0} '.format(var_middle) + part += "≤" + part += " {0} ".format(var_middle) if exclusiveMaximum: - part += '<' + part += "<" else: - part += '≤' - part += ' {0}'.format(maximum) + part += "≤" + part += " {0}".format(maximum) elif minimum is not None: if var_end is not None: - part = '{0} '.format(var_end) + part = "{0} ".format(var_end) else: - part = '' + part = "" if exclusiveMinimum: - part += '> {0}'.format(minimum) + part += "> {0}".format(minimum) else: - part += '≥ {0}'.format(minimum) + part += "≥ {0}".format(minimum) elif maximum is not None: if var_end is not None: - part = '{0} '.format(var_end) + part = "{0} ".format(var_end) else: - part = '' + part = "" if exclusiveMaximum: - part += '< {0}'.format(maximum) + part += "< {0}".format(maximum) else: - part += '≤ {0}'.format(maximum) + part += "≤ {0}".format(maximum) else: return None return part @@ -140,88 +140,108 @@ def format_type(schema, root): root : str The JSON path to the schema fragment. """ - if 'anyOf' in schema: - return ' :soft:`or` '.join( - format_type(x, root) for x in schema['anyOf']) - - elif 'allOf' in schema: - return ' :soft:`and` '.join( - format_type(x, root) for x in schema['allOf']) - - elif '$ref' in schema: - ref = schema['$ref'] - if ref.startswith('#/'): - return ':ref:`{0} <{1}/{2}>`'.format(ref[2:], root, ref[2:]) + if "anyOf" in schema: + return " :soft:`or` ".join(format_type(x, root) for x in schema["anyOf"]) + + elif "allOf" in schema: + return " :soft:`and` ".join(format_type(x, root) for x in schema["allOf"]) + + elif "$ref" in schema: + ref = schema["$ref"] + if ref.startswith("#/"): + return ":ref:`{0} <{1}/{2}>`".format(ref[2:], root, ref[2:]) else: basename = os.path.basename(ref) if "tag:stsci.edu:asdf" in ref or "tag:astropy.org:astropy" in ref: - return '`{0} <{1}>`'.format(basename, ref) + return "`{0} <{1}>`".format(basename, ref) else: - return ':doc:`{0} <{1}>`'.format(basename, ref) + return ":doc:`{0} <{1}>`".format(basename, ref) else: - type = schema.get('type') + type = schema.get("type") if isinstance(type, list): - parts = [' or '.join(type)] + parts = [" or ".join(type)] elif type is None: - parts = ['any'] + parts = ["any"] else: parts = [type] - if type == 'string': - range = format_range('*len*', '*len*', schema.get('minLength'), - schema.get('maxLength'), False, False) - if range is not None or 'pattern' in schema or 'format' in schema: - parts.append('(') + if type == "string": + range = format_range( + "*len*", + "*len*", + schema.get("minLength"), + schema.get("maxLength"), + False, + False, + ) + if range is not None or "pattern" in schema or "format" in schema: + parts.append("(") if range is not None: parts.append(range) - if 'pattern' in schema: - pattern = schema['pattern'].encode('unicode_escape') - pattern = pattern.decode('ascii') - parts.append(':soft:`regex` :regexp:`{0}`'.format(pattern)) - if 'format' in schema: - parts.append(':soft:`format` {0}'.format(schema['format'])) - parts.append(')') - - elif type in ('integer', 'number'): - range = format_range('*x*', '', schema.get('minimum'), - schema.get('maximum'), - schema.get('exclusiveMinimum'), - schema.get('exclusiveMaximum')) + if "pattern" in schema: + pattern = schema["pattern"].encode("unicode_escape") + pattern = pattern.decode("ascii") + parts.append(":soft:`regex` :regexp:`{0}`".format(pattern)) + if "format" in schema: + parts.append(":soft:`format` {0}".format(schema["format"])) + parts.append(")") + + elif type in ("integer", "number"): + range = format_range( + "*x*", + "", + schema.get("minimum"), + schema.get("maximum"), + schema.get("exclusiveMinimum"), + schema.get("exclusiveMaximum"), + ) if range is not None: parts.append(range) # TODO: multipleOf - elif type == 'object': - range = format_range('*len*', '*len*', schema.get('minProperties'), - schema.get('maxProperties'), False, False) + elif type == "object": + range = format_range( + "*len*", + "*len*", + schema.get("minProperties"), + schema.get("maxProperties"), + False, + False, + ) if range is not None: parts.append(range) # TODO: Dependencies # TODO: Pattern properties - elif type == 'array': - items = schema.get('items') - if schema.get('items') and isinstance(items, dict): - if schema.get('uniqueItems'): - parts.append(':soft:`of unique`') + elif type == "array": + items = schema.get("items") + if schema.get("items") and isinstance(items, dict): + if schema.get("uniqueItems"): + parts.append(":soft:`of unique`") else: - parts.append(':soft:`of`') - parts.append('(') + parts.append(":soft:`of`") + parts.append("(") parts.append(format_type(items, root)) - parts.append(')') - range = format_range('*len*', '*len*', schema.get('minItems'), - schema.get('maxItems'), False, False) + parts.append(")") + range = format_range( + "*len*", + "*len*", + schema.get("minItems"), + schema.get("maxItems"), + False, + False, + ) if range is not None: parts.append(range) - if 'enum' in schema: - parts.append(':soft:`from`') - parts.append(json.dumps(schema['enum'])) + if "enum" in schema: + parts.append(":soft:`from`") + parts.append(json.dumps(schema["enum"])) - return ' '.join(parts) + return " ".join(parts) def reindent(content, indent): @@ -230,9 +250,9 @@ def reindent(content, indent): """ content = textwrap.dedent(content) lines = [] - for line in content.split('\n'): + for line in content.split("\n"): lines.append(indent + line) - return '\n'.join(lines) + return "\n".join(lines) def recurse(o, name, schema, path, level, required=False): @@ -258,16 +278,16 @@ def recurse(o, name, schema, path, level, required=False): If `True` the entry is required by the schema and will be documented as such. """ - indent = ' ' * max(level, 0) - o.write('\n\n') + indent = " " * max(level, 0) + o.write("\n\n") o.write(indent) - o.write('.. _{0}:\n\n'.format(os.path.join(*path))) + o.write(".. _{0}:\n\n".format(os.path.join(*path))) if level == 0: write_header(o, name, level) else: - if name != 'items': + if name != "items": o.write(indent) - o.write(':entry:`{0}`\n\n'.format(name)) + o.write(":entry:`{0}`\n\n".format(name)) o.write(indent) if path[0].startswith("tag:stsci.edu:asdf"): @@ -275,90 +295,95 @@ def recurse(o, name, schema, path, level, required=False): else: o.write(":soft:`Type:` ") o.write(format_type(schema, path[0])) - o.write('.') + o.write(".") if required: - o.write(' Required.') - o.write('\n\n') + o.write(" Required.") + o.write("\n\n") - o.write(reindent(schema.get('title', ''), indent)) - o.write('\n\n') + o.write(reindent(schema.get("title", ""), indent)) + o.write("\n\n") - o.write(reindent(schema.get('description', ''), indent)) - o.write('\n\n') + o.write(reindent(schema.get("description", ""), indent)) + o.write("\n\n") - if 'default' in schema: + if "default" in schema: o.write(indent) - o.write(':soft:`Default:` {0}'.format( - json.dumps(schema['default']))) - o.write('\n\n') + o.write(":soft:`Default:` {0}".format(json.dumps(schema["default"]))) + o.write("\n\n") - if 'definitions' in schema: + if "definitions" in schema: o.write(indent) o.write(":category:`Definitions:`\n\n") - for key, val in schema['definitions'].items(): - recurse(o, key, val, path + ['definitions', key], level + 1) + for key, val in schema["definitions"].items(): + recurse(o, key, val, path + ["definitions", key], level + 1) - if 'anyOf' in schema and len(schema['anyOf']) > 1: + if "anyOf" in schema and len(schema["anyOf"]) > 1: o.write(indent) - o.write(':category:`Any of:`\n\n') - for i, subschema in enumerate(schema['anyOf']): - recurse(o, '—', subschema, path + ['anyOf', str(i)], level + 1) + o.write(":category:`Any of:`\n\n") + for i, subschema in enumerate(schema["anyOf"]): + recurse(o, "—", subschema, path + ["anyOf", str(i)], level + 1) - elif 'allOf' in schema and len(schema['allOf']) > 1: + elif "allOf" in schema and len(schema["allOf"]) > 1: o.write(indent) - o.write(':category:`All of:`\n\n') - for i, subschema in enumerate(schema['allOf']): - recurse(o, i, subschema, path + ['allOf', str(i)], level + 1) + o.write(":category:`All of:`\n\n") + for i, subschema in enumerate(schema["allOf"]): + recurse(o, i, subschema, path + ["allOf", str(i)], level + 1) - if schema.get('type') == 'object': + if schema.get("type") == "object": o.write(indent) - o.write(':category:`Properties:`\n\n') - for key, val in schema.get('properties', {}).items(): - recurse(o, key, val, path + ['properties', key], level + 1, - key in schema.get('required', [])) - - elif schema.get('type') == 'array': + o.write(":category:`Properties:`\n\n") + for key, val in schema.get("properties", {}).items(): + recurse( + o, + key, + val, + path + ["properties", key], + level + 1, + key in schema.get("required", []), + ) + + elif schema.get("type") == "array": o.write(indent) - o.write(':category:`Items:`\n\n') - items = schema.get('items') + o.write(":category:`Items:`\n\n") + items = schema.get("items") if isinstance(items, dict): - recurse(o, 'items', items, path + ['items'], level + 1) + recurse(o, "items", items, path + ["items"], level + 1) elif isinstance(items, list): for i, val in enumerate(items): - name = 'index[{0}]'.format(i) + name = "index[{0}]".format(i) recurse(o, name, val, path + [str(i)], level + 1) - if 'examples' in schema: + if "examples" in schema: o.write(indent) o.write(":category:`Examples:`\n\n") - for description, example in schema['examples']: + for description, example in schema["examples"]: o.write(reindent(description + "::\n\n", indent)) - o.write(reindent(example, indent + ' ')) - o.write('\n\n') + o.write(reindent(example, indent + " ")) + o.write("\n\n") def convert_schema_to_rst(src, dst): """ Convert a YAML schema to reStructuredText. """ - with open(src, 'rb') as fd: + with open(src, "rb") as fd: schema = yaml.safe_load(fd) - with open(src, 'rb') as fd: + with open(src, "rb") as fd: yaml_content = fd.read() o = io.StringIO() - id = schema.get('id', '#') + id = schema.get("id", "#") name = os.path.basename(src[:-5]) - if 'title' in schema: - name += ': ' + schema['title'].strip() + if "title" in schema: + name += ": " + schema["title"].strip() recurse(o, name, schema, [id], 0) - #o.write(".. only:: html\n\n :download:`Original schema in YAML <{0}>`\n". - #os.path.basename(src))) + # o.write(".. only:: html\n\n :download:`Original schema in YAML <{0}>`\n". + # os.path.basename(src))) write_if_different(dst, yaml_content) - write_if_different(dst[:-5] + ".rst", o.getvalue().encode('utf-8')) + write_if_different(dst[:-5] + ".rst", o.getvalue().encode("utf-8")) def construct_mapping(self, node, deep=False): @@ -367,9 +392,12 @@ def construct_mapping(self, node, deep=False): original file. """ if not isinstance(node, yaml.MappingNode): - raise yaml.constructor.ConstructorError(None, None, - "expected a mapping node, but found %s" % node.id, - node.start_mark) + raise yaml.constructor.ConstructorError( + None, + None, + "expected a mapping node, but found %s" % node.id, + node.start_mark, + ) mapping = OrderedDict() for key_node, value_node in node.value: key = self.construct_object(key_node, deep=deep) @@ -377,15 +405,17 @@ def construct_mapping(self, node, deep=False): hash(key) except TypeError as exc: raise yaml.constructor.ConstructorError( - "while constructing a mapping", node.start_mark, - "found unacceptable key (%s)" % exc, key_node.start_mark) + "while constructing a mapping", + node.start_mark, + "found unacceptable key (%s)" % exc, + key_node.start_mark, + ) value = self.construct_object(value_node, deep=deep) mapping[key] = value return mapping -yaml.SafeLoader.add_constructor( - 'tag:yaml.org,2002:map', construct_mapping) +yaml.SafeLoader.add_constructor("tag:yaml.org,2002:map", construct_mapping) def main(src, dst): @@ -394,8 +424,7 @@ def main(src, dst): if not fname.endswith(".yaml"): continue src_path = os.path.join(root, fname) - dst_path = os.path.join( - dst, os.path.relpath(src_path, src)) + dst_path = os.path.join(dst, os.path.relpath(src_path, src)) convert_schema_to_rst(src_path, dst_path) @@ -404,7 +433,7 @@ def decode_filename(fname): return fname -if __name__ == '__main__': +if __name__ == "__main__": src = decode_filename(sys.argv[-2]) dst = decode_filename(sys.argv[-1]) diff --git a/docs/conf.py b/docs/conf.py index 0f6aa565..41a91823 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,7 +39,9 @@ try: from sphinx_astropy.conf.v1 import * # noqa except ImportError: - print('ERROR: the documentation requires the sphinx-astropy package to be installed') + print( + "ERROR: the documentation requires the sphinx-astropy package to be installed" + ) sys.exit(1) # -- General configuration ---------------------------------------------------- @@ -53,7 +55,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns.append('_templates') # noqa: F405 +exclude_patterns.append("_templates") # noqa: F405 # This is added to the end of RST files - a good place to put substitutions to # be used globally. @@ -61,12 +63,14 @@ """ # noqa: F405 # Top-level directory containing ASDF schemas (relative to current directory) -asdf_schema_path = '../gwcs/schemas' +asdf_schema_path = "../gwcs/schemas" # This is the prefix common to all schema IDs in this repository -asdf_schema_standard_prefix = 'stsci.edu/gwcs' +asdf_schema_standard_prefix = "stsci.edu/gwcs" asdf_schema_reference_mappings = [ - ('tag:stsci.edu:asdf', - 'http://asdf-standard.readthedocs.io/en/latest/generated/stsci.edu/asdf/'), + ( + "tag:stsci.edu:asdf", + "http://asdf-standard.readthedocs.io/en/latest/generated/stsci.edu/asdf/", + ), ] # -- Project information ------------------------------------------------------ @@ -74,10 +78,10 @@ # This does not *have* to match the package name, but typically does with open(Path(__file__).parent.parent / "pyproject.toml", "rb") as metadata_file: configuration = tomllib.load(metadata_file) - metadata = configuration['project'] -project = metadata['name'] + metadata = configuration["project"] +project = metadata["name"] author = metadata["authors"][0]["name"] -copyright = f'{datetime.now().year}, {author}' +copyright = f"{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 @@ -85,7 +89,7 @@ release = importlib.metadata.version(project) # for example take major/minor -version = '.'.join(release.split('.')[:2]) +version = ".".join(release.split(".")[:2]) # -- Options for HTML output --------------------------------------------------- @@ -108,9 +112,9 @@ # See sphinx-bootstrap-theme for documentation of these options # https://github.com/ryan-roemer/sphinx-bootstrap-theme html_theme_options = { - 'logotext1': 'g', # white, semi-bold - 'logotext2': 'wcs', # orange, light - 'logotext3': ':docs' # white, light + "logotext1": "g", # white, semi-bold + "logotext2": "wcs", # orange, light + "logotext3": ":docs", # white, light } # Custom sidebar templates, maps document names to template names. @@ -127,10 +131,10 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = '{0} v{1}'.format(project, release) +html_title = "{0} v{1}".format(project, release) # Output file base name for HTML help builder. -htmlhelp_basename = project + 'doc' +htmlhelp_basename = project + "doc" # -- Options for LaTeX output -------------------------------------------------- @@ -144,17 +148,16 @@ # 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)] +man_pages = [("index", project.lower(), project + " Documentation", [author], 1)] -sys.path.insert(0, str(Path(__file__).parent / 'sphinxext')) -extensions += ['sphinx_asdf'] # noqa: F405 +sys.path.insert(0, str(Path(__file__).parent / "sphinxext")) +extensions += ["sphinx_asdf"] # noqa: F405 # Enable nitpicky mode - which ensures that all references in the docs resolve. nitpicky = True nitpick_ignore = [ - ('py:class', 'gwcs.api.GWCSAPIMixin'), - ('py:obj', 'astropy.modeling.projections.projcodes'), - ('py:attr', 'gwcs.WCS.bounding_box'), - ('py:meth', 'gwcs.WCS.footprint') + ("py:class", "gwcs.api.GWCSAPIMixin"), + ("py:obj", "astropy.modeling.projections.projcodes"), + ("py:attr", "gwcs.WCS.bounding_box"), + ("py:meth", "gwcs.WCS.footprint"), ] diff --git a/gwcs/__init__.py b/gwcs/__init__.py index 18d5c3c8..bd092eb1 100644 --- a/gwcs/__init__.py +++ b/gwcs/__init__.py @@ -66,7 +66,7 @@ pass # pragma: no cover -from .wcs import * # noqa -from .wcstools import * # noqa +from .wcs import * # noqa +from .wcstools import * # noqa from .coordinate_frames import * # noqa -from .selector import * # noqa +from .selector import * # noqa diff --git a/gwcs/api.py b/gwcs/api.py index 0c32482e..7fbdb7ed 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -63,15 +63,17 @@ def world_axis_units(self): specification document, units that do not follow this standard are still allowed, but just not recommended). """ - return tuple(unit.to_string(format='vounit') for unit in self.output_frame.unit) + return tuple(unit.to_string(format="vounit") for unit in self.output_frame.unit) def _remove_quantity_output(self, result, frame): if self.forward_transform.uses_quantity: if frame.naxes == 1: result = [result] - result = tuple(r.to_value(unit) if isinstance(r, u.Quantity) else r - for r, unit in zip(result, frame.unit)) + result = tuple( + r.to_value(unit) if isinstance(r, u.Quantity) else r + for r, unit in zip(result, frame.unit) + ) # If we only have one output axes, we shouldn't return a tuple. if self.output_frame.naxes == 1 and isinstance(result, tuple): @@ -219,9 +221,11 @@ def pixel_shape(self, value): return wcs_naxes = self.input_frame.naxes if len(value) != wcs_naxes: - raise ValueError("The number of data axes, " - "{}, does not equal the " - "shape {}.".format(wcs_naxes, len(value))) + raise ValueError( + "The number of data axes, " + "{}, does not equal the " + "shape {}.".format(wcs_naxes, len(value)) + ) self._pixel_shape = tuple(value) @@ -260,7 +264,7 @@ def pixel_axis_names(self): """ if self.input_frame is not None: return self.input_frame.axes_names - return tuple([''] * self.pixel_n_dim) + return tuple([""] * self.pixel_n_dim) @property def world_axis_names(self): @@ -269,4 +273,4 @@ def world_axis_names(self): """ if self.output_frame is not None: return self.output_frame.axes_names - return tuple([''] * self.world_n_dim) + return tuple([""] * self.world_n_dim) diff --git a/gwcs/converters/geometry.py b/gwcs/converters/geometry.py index e625e13e..88f47557 100644 --- a/gwcs/converters/geometry.py +++ b/gwcs/converters/geometry.py @@ -2,66 +2,66 @@ ASDF tags for geometry related models. """ + from asdf_astropy.converters.transform.core import TransformConverterBase -__all__ = ['DirectionCosinesConverter', 'SphericalCartesianConverter'] +__all__ = ["DirectionCosinesConverter", "SphericalCartesianConverter"] class DirectionCosinesConverter(TransformConverterBase): tags = ["tag:stsci.edu:gwcs/direction_cosines-*"] - types = ["gwcs.geometry.ToDirectionCosines", - "gwcs.geometry.FromDirectionCosines"] + types = ["gwcs.geometry.ToDirectionCosines", "gwcs.geometry.FromDirectionCosines"] def from_yaml_tree_transform(self, node, tag, ctx): from ..geometry import ToDirectionCosines, FromDirectionCosines - transform_type = node['transform_type'] - if transform_type == 'to_direction_cosines': + + transform_type = node["transform_type"] + if transform_type == "to_direction_cosines": return ToDirectionCosines() - elif transform_type == 'from_direction_cosines': + elif transform_type == "from_direction_cosines": return FromDirectionCosines() else: raise TypeError(f"Unknown model_type {transform_type}") def to_yaml_tree_transform(self, model, tag, ctx): from ..geometry import ToDirectionCosines, FromDirectionCosines + if isinstance(model, FromDirectionCosines): - transform_type = 'from_direction_cosines' + transform_type = "from_direction_cosines" elif isinstance(model, ToDirectionCosines): - transform_type = 'to_direction_cosines' + transform_type = "to_direction_cosines" else: raise TypeError(f"Model of type {model.__class__} is not supported.") - node = {'transform_type': transform_type} + node = {"transform_type": transform_type} return node class SphericalCartesianConverter(TransformConverterBase): tags = ["tag:stsci.edu:gwcs/spherical_cartesian-*"] - types = ["gwcs.geometry.SphericalToCartesian", - "gwcs.geometry.CartesianToSpherical"] + types = ["gwcs.geometry.SphericalToCartesian", "gwcs.geometry.CartesianToSpherical"] def from_yaml_tree_transform(self, node, tag, ctx): from ..geometry import SphericalToCartesian, CartesianToSpherical - transform_type = node['transform_type'] - wrap_lon_at = node['wrap_lon_at'] - if transform_type == 'spherical_to_cartesian': + + transform_type = node["transform_type"] + wrap_lon_at = node["wrap_lon_at"] + if transform_type == "spherical_to_cartesian": return SphericalToCartesian(wrap_lon_at=wrap_lon_at) - elif transform_type == 'cartesian_to_spherical': + elif transform_type == "cartesian_to_spherical": return CartesianToSpherical(wrap_lon_at=wrap_lon_at) else: raise TypeError(f"Unknown model_type {transform_type}") def to_yaml_tree_transform(self, model, tag, ctx): from ..geometry import SphericalToCartesian, CartesianToSpherical + if isinstance(model, SphericalToCartesian): - transform_type = 'spherical_to_cartesian' + transform_type = "spherical_to_cartesian" elif isinstance(model, CartesianToSpherical): - transform_type = 'cartesian_to_spherical' + transform_type = "cartesian_to_spherical" else: raise TypeError(f"Model of type {model.__class__} is not supported.") - node = { - 'transform_type': transform_type, - 'wrap_lon_at': model.wrap_lon_at - } + node = {"transform_type": transform_type, "wrap_lon_at": model.wrap_lon_at} return node diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index 451204c7..b176090d 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -11,38 +11,53 @@ from asdf_astropy.converters.transform.core import TransformConverterBase -__all__ = ['LabelMapperConverter', 'RegionsSelectorConverter'] +__all__ = ["LabelMapperConverter", "RegionsSelectorConverter"] class LabelMapperConverter(TransformConverterBase): tags = ["tag:stsci.edu:gwcs/label_mapper-*"] - types = ["gwcs.selector.LabelMapperArray", "gwcs.selector.LabelMapperDict", - "gwcs.selector.LabelMapperRange", "gwcs.selector.LabelMapper"] + types = [ + "gwcs.selector.LabelMapperArray", + "gwcs.selector.LabelMapperDict", + "gwcs.selector.LabelMapperRange", + "gwcs.selector.LabelMapper", + ] def from_yaml_tree_transform(self, node, tag, ctx): - from ..selector import (LabelMapperArray, LabelMapperDict, - LabelMapperRange, LabelMapper) - inputs_mapping = node.get('inputs_mapping', None) - if inputs_mapping is not None and not isinstance(inputs_mapping, models.Mapping): - raise TypeError("inputs_mapping must be an instance" - "of astropy.modeling.models.Mapping.") - mapper = node['mapper'] - atol = node.get('atol', 1e-8) - no_label = node.get('no_label', np.nan) + from ..selector import ( + LabelMapperArray, + LabelMapperDict, + LabelMapperRange, + LabelMapper, + ) + + inputs_mapping = node.get("inputs_mapping", None) + if inputs_mapping is not None and not isinstance( + inputs_mapping, models.Mapping + ): + raise TypeError( + "inputs_mapping must be an instance" + "of astropy.modeling.models.Mapping." + ) + mapper = node["mapper"] + atol = node.get("atol", 1e-8) + no_label = node.get("no_label", np.nan) if isinstance(mapper, NDArrayType): if mapper.ndim != 2: raise NotImplementedError("GWCS currently only supports 2D masks.") return LabelMapperArray(mapper, inputs_mapping) elif isinstance(mapper, Model): - inputs = node.get('inputs') - return LabelMapper(inputs, mapper, inputs_mapping=inputs_mapping, no_label=no_label) + inputs = node.get("inputs") + return LabelMapper( + inputs, mapper, inputs_mapping=inputs_mapping, no_label=no_label + ) else: - inputs = node.get('inputs', None) + inputs = node.get("inputs", None) if inputs is not None: inputs = tuple(inputs) - labels = mapper.get('labels') - transforms = mapper.get('models') + labels = mapper.get("labels") + transforms = mapper.get("models") if isiterable(labels[0]): labels = [tuple(label) for label in labels] dict_mapper = dict(zip(labels, transforms)) @@ -52,21 +67,26 @@ def from_yaml_tree_transform(self, node, tag, ctx): return LabelMapperDict(inputs, dict_mapper, inputs_mapping, atol=atol) def to_yaml_tree_transform(self, model, tag, ctx): - from ..selector import (LabelMapperArray, LabelMapperDict, - LabelMapperRange, LabelMapper) + from ..selector import ( + LabelMapperArray, + LabelMapperDict, + LabelMapperRange, + LabelMapper, + ) + node = OrderedDict() - node['no_label'] = model.no_label + node["no_label"] = model.no_label if model.inputs_mapping is not None: - node['inputs_mapping'] = model.inputs_mapping + node["inputs_mapping"] = model.inputs_mapping if isinstance(model, LabelMapperArray): - node['mapper'] = model.mapper + node["mapper"] = model.mapper elif isinstance(model, LabelMapper): - node['mapper'] = model.mapper - node['inputs'] = list(model.inputs) + node["mapper"] = model.mapper + node["inputs"] = list(model.inputs) elif isinstance(model, (LabelMapperDict, LabelMapperRange)): - if hasattr(model, 'atol'): - node['atol'] = model.atol + if hasattr(model, "atol"): + node["atol"] = model.atol mapper = OrderedDict() labels = list(model.mapper) @@ -75,10 +95,10 @@ def to_yaml_tree_transform(self, model, tag, ctx): transforms.append(model.mapper[k]) if isiterable(labels[0]): labels = [list(label) for label in labels] - mapper['labels'] = labels - mapper['models'] = transforms - node['mapper'] = mapper - node['inputs'] = list(model.inputs) + mapper["labels"] = labels + mapper["models"] = transforms + node["mapper"] = mapper + node["inputs"] = list(model.inputs) else: raise TypeError("Unrecognized type of LabelMapper - {0}".format(model)) @@ -91,14 +111,16 @@ class RegionsSelectorConverter(TransformConverterBase): def from_yaml_tree_transform(self, node, tag, ctx): from ..selector import RegionsSelector - inputs = node['inputs'] - outputs = node['outputs'] - label_mapper = node['label_mapper'] - undefined_transform_value = node['undefined_transform_value'] - sel = node['selector'] - sel = dict(zip(sel['labels'], sel['transforms'])) - return RegionsSelector(inputs, outputs, - sel, label_mapper, undefined_transform_value) + + inputs = node["inputs"] + outputs = node["outputs"] + label_mapper = node["label_mapper"] + undefined_transform_value = node["undefined_transform_value"] + sel = node["selector"] + sel = dict(zip(sel["labels"], sel["transforms"])) + return RegionsSelector( + inputs, outputs, sel, label_mapper, undefined_transform_value + ) def to_yaml_tree_transform(self, model, tag, ctx): selector = OrderedDict() @@ -107,11 +129,11 @@ def to_yaml_tree_transform(self, model, tag, ctx): values = [] for label in labels: values.append(model.selector[label]) - selector['labels'] = labels - selector['transforms'] = values - node['inputs'] = list(model.inputs) - node['outputs'] = list(model.outputs) - node['selector'] = selector - node['label_mapper'] = model.label_mapper - node['undefined_transform_value'] = model.undefined_transform_value + selector["labels"] = labels + selector["transforms"] = values + node["inputs"] = list(model.inputs) + node["outputs"] = list(model.outputs) + node["selector"] = selector + node["label_mapper"] = model.label_mapper + node["undefined_transform_value"] = model.undefined_transform_value return node diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index 8b762503..98f9cde8 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -2,14 +2,20 @@ ASDF tags for spectroscopy related models. """ + from astropy import units as u from asdf_astropy.converters.transform.core import ( - TransformConverterBase, parameter_to_value + TransformConverterBase, + parameter_to_value, ) -__all__ = ['GratingEquationConverter', 'SellmeierGlassConverter', - 'SellmeierZemaxConverter', 'Snell3DConverter'] +__all__ = [ + "GratingEquationConverter", + "SellmeierGlassConverter", + "SellmeierZemaxConverter", + "Snell3DConverter", +] class SellmeierGlassConverter(TransformConverterBase): @@ -18,11 +24,14 @@ class SellmeierGlassConverter(TransformConverterBase): def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import SellmeierGlass - return SellmeierGlass(node['B_coef'], node['C_coef']) + + return SellmeierGlass(node["B_coef"], node["C_coef"]) def to_yaml_tree_transform(self, model, tag, ctx): - node = {'B_coef': parameter_to_value(model.B_coef), - 'C_coef': parameter_to_value(model.C_coef)} + node = { + "B_coef": parameter_to_value(model.B_coef), + "C_coef": parameter_to_value(model.C_coef), + } return node @@ -32,20 +41,29 @@ class SellmeierZemaxConverter(TransformConverterBase): def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import SellmeierZemax - return SellmeierZemax(node['temperature'], node['ref_temperature'], - node['ref_pressure'], node['pressure'], - node['B_coef'], node['C_coef'], node['D_coef'], - node['E_coef']) + + return SellmeierZemax( + node["temperature"], + node["ref_temperature"], + node["ref_pressure"], + node["pressure"], + node["B_coef"], + node["C_coef"], + node["D_coef"], + node["E_coef"], + ) def to_yaml_tree_transform(self, model, tag, ctx): - node = {'B_coef': parameter_to_value(model.B_coef), - 'C_coef': parameter_to_value(model.C_coef), - 'D_coef': parameter_to_value(model.D_coef), - 'E_coef': parameter_to_value(model.E_coef), - 'temperature': parameter_to_value(model.temperature), - 'ref_temperature': parameter_to_value(model.ref_temperature), - 'pressure': parameter_to_value(model.pressure), - 'ref_pressure': parameter_to_value(model.ref_pressure)} + node = { + "B_coef": parameter_to_value(model.B_coef), + "C_coef": parameter_to_value(model.C_coef), + "D_coef": parameter_to_value(model.D_coef), + "E_coef": parameter_to_value(model.E_coef), + "temperature": parameter_to_value(model.temperature), + "ref_temperature": parameter_to_value(model.ref_temperature), + "pressure": parameter_to_value(model.pressure), + "ref_pressure": parameter_to_value(model.ref_pressure), + } return node @@ -55,6 +73,7 @@ class Snell3DConverter(TransformConverterBase): def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import Snell3D + return Snell3D() def to_yaml_tree_transform(self, model, tag, ctx): @@ -63,42 +82,53 @@ def to_yaml_tree_transform(self, model, tag, ctx): class GratingEquationConverter(TransformConverterBase): tags = ["tag:stsci.edu:gwcs/grating_equation-*"] - types = ["gwcs.spectroscopy.AnglesFromGratingEquation3D", - "gwcs.spectroscopy.WavelengthFromGratingEquation"] + types = [ + "gwcs.spectroscopy.AnglesFromGratingEquation3D", + "gwcs.spectroscopy.WavelengthFromGratingEquation", + ] def from_yaml_tree_transform(self, node, tag, ctx): - from ..spectroscopy import (AnglesFromGratingEquation3D, - WavelengthFromGratingEquation) - groove_density = node['groove_density'] - order = node['order'] - output = node['output'] + from ..spectroscopy import ( + AnglesFromGratingEquation3D, + WavelengthFromGratingEquation, + ) + + groove_density = node["groove_density"] + order = node["order"] + output = node["output"] if output == "wavelength": - model = WavelengthFromGratingEquation(groove_density=groove_density, - spectral_order=order) + model = WavelengthFromGratingEquation( + groove_density=groove_density, spectral_order=order + ) elif output == "angle": - model = AnglesFromGratingEquation3D(groove_density=groove_density, - spectral_order=order) + model = AnglesFromGratingEquation3D( + groove_density=groove_density, spectral_order=order + ) else: - raise ValueError("Can't create a GratingEquation model with " - "output {0}".format(output)) + raise ValueError( + "Can't create a GratingEquation model with " "output {0}".format(output) + ) return model def to_yaml_tree_transform(self, model, tag, ctx): - from ..spectroscopy import (AnglesFromGratingEquation3D, - WavelengthFromGratingEquation) + from ..spectroscopy import ( + AnglesFromGratingEquation3D, + WavelengthFromGratingEquation, + ) + if model.groove_density.unit is not None: - groove_density = u.Quantity(model.groove_density.value, - unit=model.groove_density.unit) + groove_density = u.Quantity( + model.groove_density.value, unit=model.groove_density.unit + ) else: groove_density = model.groove_density.value - node = {'order': model.spectral_order.value, - 'groove_density': groove_density - } + node = {"order": model.spectral_order.value, "groove_density": groove_density} if isinstance(model, AnglesFromGratingEquation3D): - node['output'] = 'angle' + node["output"] = "angle" elif isinstance(model, WavelengthFromGratingEquation): - node['output'] = 'wavelength' + node["output"] = "wavelength" else: - raise TypeError("Can't serialize an instance of {0}" - .format(model.__class__.__name__)) + raise TypeError( + "Can't serialize an instance of {0}".format(model.__class__.__name__) + ) return node diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index ab86ac38..d6c0d607 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -18,13 +18,13 @@ def _assert_mapper_equal(a, b): assert type(a) is type(b) if isinstance(a.mapper, dict): - assert(a.mapper.__class__ == b.mapper.__class__) # nosec - assert(np.isin(list(a.mapper), list(b.mapper)).all()) # nosec + assert a.mapper.__class__ == b.mapper.__class__ # nosec + assert np.isin(list(a.mapper), list(b.mapper)).all() # nosec for k in a.mapper: - assert (a.mapper[k].__class__ == b.mapper[k].__class__) # nosec - assert(all(a.mapper[k].parameters == b.mapper[k].parameters)) # nosec - assert (a.inputs == b.inputs) # nosec - assert (a.inputs_mapping.mapping == b.inputs_mapping.mapping) # nosec + assert a.mapper[k].__class__ == b.mapper[k].__class__ # nosec + assert all(a.mapper[k].parameters == b.mapper[k].parameters) # nosec + assert a.inputs == b.inputs # nosec + assert a.inputs_mapping.mapping == b.inputs_mapping.mapping # nosec else: assert_array_equal(a.mapper, b.mapper) @@ -74,53 +74,56 @@ def test_regions_selector(tmpdir): a[:, 1:3] = 1 a[:, 4:5] = 2 mask = selector.LabelMapperArray(a) - rs = selector.RegionsSelector(inputs=('x', 'y'), outputs=('ra', 'dec', 'lam'), - selector=sel, label_mapper=mask) + rs = selector.RegionsSelector( + inputs=("x", "y"), outputs=("ra", "dec", "lam"), selector=sel, label_mapper=mask + ) assert_selector_roundtrip(rs, tmpdir) def test_LabelMapperArray_str(tmpdir): - a = np.array([["label1", "", "label2"], - ["label1", "", ""], - ["label1", "label2", "label2"]]) + a = np.array( + [["label1", "", "label2"], ["label1", "", ""], ["label1", "label2", "label2"]] + ) mask = selector.LabelMapperArray(a) assert_selector_roundtrip(mask, tmpdir) def test_labelMapperArray_int(tmpdir): - - a = np.array([[1, 0, 2], - [1, 0, 0], - [1, 2, 2]]) + a = np.array([[1, 0, 2], [1, 0, 0], [1, 2, 2]]) mask = selector.LabelMapperArray(a) assert_selector_roundtrip(mask, tmpdir) def test_LabelMapperDict(tmpdir): dmapper = create_scalar_mapper() - sel = selector.LabelMapperDict(('x', 'y'), dmapper, - inputs_mapping=Mapping((0,), n_inputs=2), atol=1e-3) + sel = selector.LabelMapperDict( + ("x", "y"), dmapper, inputs_mapping=Mapping((0,), n_inputs=2), atol=1e-3 + ) assert_selector_roundtrip(sel, tmpdir) def test_LabelMapperRange(tmpdir): m = [] - for i in np.arange(9) * .1: + for i in np.arange(9) * 0.1: c0_0, c1_0, c0_1, c1_1 = np.ones((4,)) * i - m.append(Polynomial2D(2, c0_0=c0_0, - c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) - keys = np.array([[4.88, 5.64], - [5.75, 6.5], - [6.67, 7.47], - [7.7, 8.63], - [8.83, 9.96], - [10.19, 11.49], - [11.77, 13.28], - [13.33, 15.34], - [15.56, 18.09]]) + m.append(Polynomial2D(2, c0_0=c0_0, c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) + keys = np.array( + [ + [4.88, 5.64], + [5.75, 6.5], + [6.67, 7.47], + [7.7, 8.63], + [8.83, 9.96], + [10.19, 11.49], + [11.77, 13.28], + [13.33, 15.34], + [15.56, 18.09], + ] + ) rmapper = {} for k, v in zip(keys, m): rmapper[tuple(k)] = v - sel = selector.LabelMapperRange(('x', 'y'), rmapper, - inputs_mapping=Mapping((0,), n_inputs=2)) + sel = selector.LabelMapperRange( + ("x", "y"), rmapper, inputs_mapping=Mapping((0,), n_inputs=2) + ) assert_selector_roundtrip(sel, tmpdir) diff --git a/gwcs/converters/tests/test_transforms.py b/gwcs/converters/tests/test_transforms.py index e5a460b6..cefde098 100644 --- a/gwcs/converters/tests/test_transforms.py +++ b/gwcs/converters/tests/test_transforms.py @@ -8,37 +8,49 @@ try: from asdf_astropy.testing.helpers import assert_model_roundtrip except ImportError: - from asdf_astropy.converters.transform.tests.test_transform import assert_model_roundtrip + from asdf_astropy.converters.transform.tests.test_transform import ( + assert_model_roundtrip, + ) from ... import spectroscopy as sp from ... import geometry -sell_glass = sp.SellmeierGlass(B_coef=[0.58339748, 0.46085267, 3.8915394], - C_coef=[0.00252643, 0.010078333, 1200.556]) -sell_zemax = sp.SellmeierZemax(65, 35, 0, 0, [0.58339748, 0.46085267, 3.8915394], - [0.00252643, 0.010078333, 1200.556], [-2.66e-05, 0.0, 0.0]) +sell_glass = sp.SellmeierGlass( + B_coef=[0.58339748, 0.46085267, 3.8915394], + C_coef=[0.00252643, 0.010078333, 1200.556], +) +sell_zemax = sp.SellmeierZemax( + 65, + 35, + 0, + 0, + [0.58339748, 0.46085267, 3.8915394], + [0.00252643, 0.010078333, 1200.556], + [-2.66e-05, 0.0, 0.0], +) snell = sp.Snell3D() todircos = geometry.ToDirectionCosines() fromdircos = geometry.FromDirectionCosines() tocart = geometry.SphericalToCartesian() tospher = geometry.CartesianToSpherical() -transforms = [todircos, - fromdircos, - tospher, - tocart, - snell, - sell_glass, - sell_zemax, - sell_zemax & todircos| snell & Identity(1) | fromdircos, - sell_glass & todircos | snell & Identity(1) | fromdircos, - sp.WavelengthFromGratingEquation(50000, -1), - sp.AnglesFromGratingEquation3D(20000, 1), - sp.WavelengthFromGratingEquation(15000*1 / u.m, -1), - ] - - -@pytest.mark.parametrize(('model'), transforms) +transforms = [ + todircos, + fromdircos, + tospher, + tocart, + snell, + sell_glass, + sell_zemax, + sell_zemax & todircos | snell & Identity(1) | fromdircos, + sell_glass & todircos | snell & Identity(1) | fromdircos, + sp.WavelengthFromGratingEquation(50000, -1), + sp.AnglesFromGratingEquation3D(20000, 1), + sp.WavelengthFromGratingEquation(15000 * 1 / u.m, -1), +] + + +@pytest.mark.parametrize(("model"), transforms) def test_transforms(tmpdir, model): assert_model_roundtrip(model, tmpdir) diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index fa38035a..2308a08c 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -3,7 +3,7 @@ import os.path import pytest -astropy = pytest.importorskip('astropy', minversion='3.0') +astropy = pytest.importorskip("astropy", minversion="3.0") from astropy.modeling import models # noqa: E402 from astropy import coordinates as coord # noqa: E402 @@ -12,7 +12,8 @@ import asdf # noqa: E402 from asdf_astropy.testing.helpers import ( # noqa: E402 - assert_model_equal) + assert_model_equal, +) from ... import coordinate_frames as cf # noqa: E402 from ... import wcs # noqa: E402 @@ -51,9 +52,9 @@ def assert_frame_roundtrip(frame, tmpdir, version=None): def _assert_wcs_equal(a, b): - assert a.name == b.name # nosec + assert a.name == b.name # nosec assert a.pixel_shape == b.pixel_shape - assert len(a.available_frames) == len(b.available_frames) # nosec + assert len(a.available_frames) == len(b.available_frames) # nosec for a_step, b_step in zip(a.pipeline, b.pipeline): _assert_frame_equal(a_step.frame, b_step.frame) assert_model_equal(a_step.transform, b_step.transform) @@ -71,10 +72,10 @@ def assert_wcs_roundtrip(wcs, tmpdir, version=None): def test_create_wcs(tmpdir): m1 = models.Shift(12.4) & models.Shift(-2) - icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS()) - det = cf.Frame2D(name='detector', axes_order=(0, 1)) - gw1 = wcs.WCS(output_frame='icrs', input_frame='detector', forward_transform=m1) - gw2 = wcs.WCS(output_frame='icrs', forward_transform=m1) + icrs = cf.CelestialFrame(name="icrs", reference_frame=coord.ICRS()) + det = cf.Frame2D(name="detector", axes_order=(0, 1)) + gw1 = wcs.WCS(output_frame="icrs", input_frame="detector", forward_transform=m1) + gw2 = wcs.WCS(output_frame="icrs", forward_transform=m1) gw3 = wcs.WCS(output_frame=icrs, input_frame=det, forward_transform=m1) gw4 = wcs.WCS(output_frame=icrs, input_frame=det, forward_transform=m1) gw4.pixel_shape = (100, 200) @@ -91,12 +92,12 @@ def test_composite_frame(tmpdir): cel1 = cf.CelestialFrame(reference_frame=icrs) cel2 = cf.CelestialFrame(reference_frame=fk5) - spec1 = cf.SpectralFrame(name='freq', unit=(u.Hz, ), axes_order=(2, )) - spec2 = cf.SpectralFrame(name='wave', unit=(u.m, ), axes_order=(2, )) + spec1 = cf.SpectralFrame(name="freq", unit=(u.Hz,), axes_order=(2,)) + spec2 = cf.SpectralFrame(name="wave", unit=(u.m,), axes_order=(2,)) comp1 = cf.CompositeFrame([cel1, spec1]) comp2 = cf.CompositeFrame([cel2, spec2]) - comp = cf.CompositeFrame([comp1, cf.SpectralFrame(axes_order=(3, ), unit=(u.m, ))]) + comp = cf.CompositeFrame([comp1, cf.SpectralFrame(axes_order=(3,), unit=(u.m,))]) assert_frame_roundtrip(comp, tmpdir) assert_frame_roundtrip(comp1, tmpdir) @@ -108,56 +109,44 @@ def create_test_frames(): frames = [ cf.CelestialFrame(reference_frame=coord.ICRS()), - - cf.CelestialFrame( - reference_frame=coord.FK5(equinox=time.Time('2010-01-01'))), - + cf.CelestialFrame(reference_frame=coord.FK5(equinox=time.Time("2010-01-01"))), cf.CelestialFrame( reference_frame=coord.FK4( - equinox=time.Time('2010-01-01'), - obstime=time.Time('2015-01-01')) - ), - + equinox=time.Time("2010-01-01"), obstime=time.Time("2015-01-01") + ) + ), cf.CelestialFrame( reference_frame=coord.FK4NoETerms( - equinox=time.Time('2010-01-01'), - obstime=time.Time('2015-01-01')) - ), - - cf.CelestialFrame( - reference_frame=coord.Galactic()), - + equinox=time.Time("2010-01-01"), obstime=time.Time("2015-01-01") + ) + ), + cf.CelestialFrame(reference_frame=coord.Galactic()), cf.CelestialFrame( reference_frame=coord.Galactocentric( # A default galcen_coord is used since none is provided here galcen_distance=5.0 * u.m, z_sun=3 * u.pc, - roll=3 * u.deg) - ), - + roll=3 * u.deg, + ) + ), cf.CelestialFrame( reference_frame=coord.GCRS( - obstime=time.Time('2010-01-01'), + obstime=time.Time("2010-01-01"), obsgeoloc=[1, 3, 2000] * u.pc, - obsgeovel=[2, 1, 8] * (u.m / u.s))), - - cf.CelestialFrame( - reference_frame=coord.CIRS( - obstime=time.Time('2010-01-01'))), - - cf.CelestialFrame( - reference_frame=coord.ITRS( - obstime=time.Time('2022-01-03'))), - + obsgeovel=[2, 1, 8] * (u.m / u.s), + ) + ), + cf.CelestialFrame(reference_frame=coord.CIRS(obstime=time.Time("2010-01-01"))), + cf.CelestialFrame(reference_frame=coord.ITRS(obstime=time.Time("2022-01-03"))), cf.CelestialFrame( reference_frame=coord.PrecessedGeocentric( - obstime=time.Time('2010-01-01'), + obstime=time.Time("2010-01-01"), obsgeoloc=[1, 3, 2000] * u.pc, - obsgeovel=[2, 1, 8] * (u.m / u.s))), - + obsgeovel=[2, 1, 8] * (u.m / u.s), + ) + ), cf.StokesFrame(), - - cf.TemporalFrame(time.Time("2011-01-01")) + cf.TemporalFrame(time.Time("2011-01-01")), ] return frames @@ -171,9 +160,9 @@ def test_frames(tmpdir): def test_references(tmpdir): m1 = models.Shift(12.4) & models.Shift(-2) - icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS()) - det = cf.Frame2D(name='detector', axes_order=(0, 1)) - focal = cf.Frame2D(name='focal', axes_order=(0, 1)) + icrs = cf.CelestialFrame(name="icrs", reference_frame=coord.ICRS()) + det = cf.Frame2D(name="detector", axes_order=(0, 1)) + focal = cf.Frame2D(name="focal", axes_order=(0, 1)) pipe1 = [(det, m1), (focal, m1), (icrs, None)] gw1 = wcs.WCS(pipe1) @@ -181,14 +170,14 @@ def test_references(tmpdir): pipe2 = [(det, m1), (det, m1), (icrs, None)] gw2 = wcs.WCS(pipe2) - tree = {'wcs1': gw1, 'wcs2': gw2} + tree = {"wcs1": gw1, "wcs2": gw2} af = asdf.AsdfFile(tree) output_path = os.path.join(str(tmpdir), "test.asdf") af.write_to(output_path) with asdf.open(output_path) as af: - gw1 = af.tree['wcs1'] - gw2 = af.tree['wcs2'] + gw1 = af.tree["wcs1"] + gw2 = af.tree["wcs2"] assert gw1.pipeline[0].transform is gw1.pipeline[1].transform assert gw2.pipeline[0].transform is gw2.pipeline[1].transform assert gw2.pipeline[0].frame is gw2.pipeline[1].frame diff --git a/gwcs/converters/wcs.py b/gwcs/converters/wcs.py index 6f2cbd78..de9e1e6f 100644 --- a/gwcs/converters/wcs.py +++ b/gwcs/converters/wcs.py @@ -6,9 +6,16 @@ from asdf.extension import Converter -__all__ = ["WCSConverter", "CelestialFrameConverter", "CompositeFrameConverter", - "FrameConverter", "SpectralFrameConverter", "StepConverter", - "TemporalFrameConverter", "StokesFrameConverter"] +__all__ = [ + "WCSConverter", + "CelestialFrameConverter", + "CompositeFrameConverter", + "FrameConverter", + "SpectralFrameConverter", + "StepConverter", + "TemporalFrameConverter", + "StokesFrameConverter", +] class WCSConverter(Converter): @@ -17,23 +24,24 @@ class WCSConverter(Converter): def from_yaml_tree(self, node, tag, ctx): from ..wcs import WCS, GwcsBoundingBoxWarning - gwcsobj = WCS(node['steps'], name=node['name']) - if 'pixel_shape' in node: - gwcsobj.pixel_shape = node['pixel_shape'] + + gwcsobj = WCS(node["steps"], name=node["name"]) + if "pixel_shape" in node: + gwcsobj.pixel_shape = node["pixel_shape"] # Ignore the warning about the bounding box order for data read from a # file. This is causing issues with files from MAST. with suppress(AttributeError), warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=GwcsBoundingBoxWarning) + warnings.filterwarnings("ignore", category=GwcsBoundingBoxWarning) _ = gwcsobj.bounding_box return gwcsobj def to_yaml_tree(self, gwcsobj, tag, ctx): return { - 'name': gwcsobj.name, - 'steps': gwcsobj.pipeline, - 'pixel_shape': gwcsobj.pixel_shape, + "name": gwcsobj.name, + "steps": gwcsobj.pipeline, + "pixel_shape": gwcsobj.pixel_shape, } @@ -43,13 +51,11 @@ class StepConverter(Converter): def from_yaml_tree(self, node, tag, ctx): from ..wcs import Step - return Step(frame=node['frame'], transform=node.get('transform', None)) + + return Step(frame=node["frame"], transform=node.get("transform", None)) def to_yaml_tree(self, step, tag, ctx): - return { - 'frame': step.frame, - 'transform': step.transform - } + return {"frame": step.frame, "transform": step.transform} class FrameConverter(Converter): @@ -57,27 +63,25 @@ class FrameConverter(Converter): types = ["gwcs.coordinate_frames.CoordinateFrame"] def _from_yaml_tree(self, node, tag, ctx): - kwargs = {'name': node['name']} + kwargs = {"name": node["name"]} - if 'axes_type' in node and 'naxes' in node: - kwargs.update({ - 'axes_type': node['axes_type'], - 'naxes': node['naxes']}) + if "axes_type" in node and "naxes" in node: + kwargs.update({"axes_type": node["axes_type"], "naxes": node["naxes"]}) - if 'axes_names' in node: - kwargs['axes_names'] = node['axes_names'] + if "axes_names" in node: + kwargs["axes_names"] = node["axes_names"] - if 'reference_frame' in node: - kwargs['reference_frame'] = node['reference_frame'] + if "reference_frame" in node: + kwargs["reference_frame"] = node["reference_frame"] - if 'axes_order' in node: - kwargs['axes_order'] = tuple(node['axes_order']) + if "axes_order" in node: + kwargs["axes_order"] = tuple(node["axes_order"]) - if 'unit' in node: - kwargs['unit'] = tuple(node['unit']) + if "unit" in node: + kwargs["unit"] = tuple(node["unit"]) - if 'axis_physical_types' in node: - kwargs['axis_physical_types'] = tuple(node['axis_physical_types']) + if "axis_physical_types" in node: + kwargs["axis_physical_types"] = tuple(node["axis_physical_types"]) return kwargs @@ -86,32 +90,33 @@ def _to_yaml_tree(self, frame, tag, ctx): node = {} - node['name'] = frame.name + node["name"] = frame.name # We want to check that it is exactly this type and not a subclass if type(frame) is CoordinateFrame: - node['axes_type'] = frame.axes_type - node['naxes'] = frame.naxes + node["axes_type"] = frame.axes_type + node["naxes"] = frame.naxes if frame.axes_order is not None: - node['axes_order'] = list(frame.axes_order) + node["axes_order"] = list(frame.axes_order) if frame.axes_names is not None: - node['axes_names'] = list(frame.axes_names) + node["axes_names"] = list(frame.axes_names) if frame.reference_frame is not None: - node['reference_frame'] = frame.reference_frame + node["reference_frame"] = frame.reference_frame if frame.unit is not None: - node['unit'] = list(frame.unit) + node["unit"] = list(frame.unit) if frame.axis_physical_types is not None: - node['axis_physical_types'] = list(frame.axis_physical_types) + node["axis_physical_types"] = list(frame.axis_physical_types) return node def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import CoordinateFrame + node = self._from_yaml_tree(node, tag, ctx) return CoordinateFrame(**node) @@ -125,6 +130,7 @@ class Frame2DConverter(FrameConverter): def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import Frame2D + node = self._from_yaml_tree(node, tag, ctx) return Frame2D(**node) @@ -135,6 +141,7 @@ class CelestialFrameConverter(FrameConverter): def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import CelestialFrame + node = self._from_yaml_tree(node, tag, ctx) return CelestialFrame(**node) @@ -145,6 +152,7 @@ class SpectralFrameConverter(FrameConverter): def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import SpectralFrame + node = self._from_yaml_tree(node, tag, ctx) return SpectralFrame(**node) @@ -156,19 +164,17 @@ class CompositeFrameConverter(FrameConverter): def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import CompositeFrame + if len(node) != 2: raise ValueError("CompositeFrame has extra properties") - name = node['name'] - frames = node['frames'] + name = node["name"] + frames = node["frames"] return CompositeFrame(frames, name) def to_yaml_tree(self, frame, tag, ctx): - return { - 'name': frame.name, - 'frames': frame.frames - } + return {"name": frame.name, "frames": frame.frames} class TemporalFrameConverter(FrameConverter): @@ -177,6 +183,7 @@ class TemporalFrameConverter(FrameConverter): def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import TemporalFrame + node = self._from_yaml_tree(node, tag, ctx) return TemporalFrame(**node) @@ -187,14 +194,15 @@ class StokesFrameConverter(FrameConverter): def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import StokesFrame + node = self._from_yaml_tree(node, tag, ctx) return StokesFrame(**node) def to_yaml_tree(self, frame, tag, ctx): node = {} - node['name'] = frame.name + node["name"] = frame.name if frame.axes_order: - node['axes_order'] = list(frame.axes_order) + node["axes_order"] = list(frame.axes_order) return node diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 42b2bdb1..46979f3d 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -125,14 +125,24 @@ from astropy import units as u from astropy import utils as astutil from astropy import coordinates as coord -from astropy.wcs.wcsapi.low_level_api import (validate_physical_types, - VALID_UCDS) +from astropy.wcs.wcsapi.low_level_api import validate_physical_types, VALID_UCDS from astropy.wcs.wcsapi.fitswcs import CTYPE_TO_UCD1 -from astropy.wcs.wcsapi.high_level_api import high_level_objects_to_values, values_to_high_level_objects +from astropy.wcs.wcsapi.high_level_api import ( + high_level_objects_to_values, + values_to_high_level_objects, +) from astropy.coordinates import StokesCoord -__all__ = ['BaseCoordinateFrame', 'Frame2D', 'CelestialFrame', 'SpectralFrame', 'CompositeFrame', - 'CoordinateFrame', 'TemporalFrame', 'StokesFrame'] +__all__ = [ + "BaseCoordinateFrame", + "Frame2D", + "CelestialFrame", + "SpectralFrame", + "CompositeFrame", + "CoordinateFrame", + "TemporalFrame", + "StokesFrame", +] def _ucd1_to_ctype_name_mapping(ctype_to_ucd, allowed_ucd_duplicates): @@ -153,22 +163,24 @@ def _ucd1_to_ctype_name_mapping(ctype_to_ucd, allowed_ucd_duplicates): logging.warning( "Found unsupported duplicate physical type in 'astropy' mapping to CTYPE.\n" "Update 'gwcs' to the latest version or notify 'gwcs' developer.\n" - "Duplicate physical types will be mapped to the following CTYPEs:\n" + - '\n'.join([f'{repr(ucd):s} --> {repr(inv_map[ucd]):s}' for ucd in new_ucd]) + "Duplicate physical types will be mapped to the following CTYPEs:\n" + + "\n".join( + [f"{repr(ucd):s} --> {repr(inv_map[ucd]):s}" for ucd in new_ucd] + ) ) return inv_map + # List below allowed physical type duplicates and a corresponding CTYPE # to which all duplicates will be mapped to: _ALLOWED_UCD_DUPLICATES = { - 'time': 'TIME', - 'em.wl': 'WAVE', + "time": "TIME", + "em.wl": "WAVE", } UCD1_TO_CTYPE = _ucd1_to_ctype_name_mapping( - ctype_to_ucd=CTYPE_TO_UCD1, - allowed_ucd_duplicates=_ALLOWED_UCD_DUPLICATES + ctype_to_ucd=CTYPE_TO_UCD1, allowed_ucd_duplicates=_ALLOWED_UCD_DUPLICATES ) STANDARD_REFERENCE_FRAMES = [frame.upper() for frame in coord.builtin_frames.__all__] @@ -234,7 +246,9 @@ def __post_init__(self, naxes): if isinstance(self.axis_physical_types, str): self.axis_physical_types = (self.axis_physical_types,) elif not isiterable(self.axis_physical_types): - raise TypeError("axis_physical_types must be of type string or iterable of strings") + raise TypeError( + "axis_physical_types must be of type string or iterable of strings" + ) if len(self.axis_physical_types) != naxes: raise ValueError(f'"axis_physical_types" must be of length {naxes}') ph_type = [] @@ -351,8 +365,9 @@ def world_axis_object_components(self): # If we have more than one axis then we should sort the native # components by the axes_order. - ordered = np.array(self._native_world_axis_object_components, - dtype=object)[np.argsort(self.axes_order)] + ordered = np.array(self._native_world_axis_object_components, dtype=object)[ + np.argsort(self.axes_order) + ] return list(map(tuple, ordered)) @property @@ -394,9 +409,17 @@ class CoordinateFrame(BaseCoordinateFrame): Name of this frame. """ - def __init__(self, naxes, axes_type, axes_order, reference_frame=None, - unit=None, axes_names=None, - name=None, axis_physical_types=None): + def __init__( + self, + naxes, + axes_type, + axes_order, + reference_frame=None, + unit=None, + axes_names=None, + name=None, + axis_physical_types=None, + ): self._naxes = naxes self._axes_order = tuple(axes_order) self._reference_frame = reference_frame @@ -417,7 +440,7 @@ def __init__(self, naxes, axes_type, axes_order, reference_frame=None, axes_type, unit, axes_names, - axis_physical_types or self._default_axis_physical_types(axes_type) + axis_physical_types or self._default_axis_physical_types(axes_type), ) super().__init__() @@ -431,8 +454,12 @@ def _default_axis_physical_types(self, axes_type): def __repr__(self): fmt = '<{0}(name="{1}", unit={2}, axes_names={3}, axes_order={4}'.format( - self.__class__.__name__, self.name, - self.unit, self.axes_names, self.axes_order) + self.__class__.__name__, + self.name, + self.unit, + self.axes_names, + self.axes_order, + ) if self.reference_frame is not None: fmt += ", reference_frame={0}".format(self.reference_frame) fmt += ")>" @@ -444,23 +471,22 @@ def __str__(self): return self.__class__.__name__ def _sort_property(self, property): - sorted_prop = sorted(zip(property, self.axes_order), - key=lambda x: x[1]) + sorted_prop = sorted(zip(property, self.axes_order), key=lambda x: x[1]) return tuple([t[0] for t in sorted_prop]) @property def name(self): - """ A custom name of this frame.""" + """A custom name of this frame.""" return self._name @name.setter def name(self, val): - """ A custom name of this frame.""" + """A custom name of this frame.""" self._name = val @property def naxes(self): - """ The number of axes in this frame.""" + """The number of axes in this frame.""" return self._naxes @property @@ -470,22 +496,22 @@ def unit(self): @property def axes_names(self): - """ Names of axes in the frame.""" + """Names of axes in the frame.""" return self._sort_property(self._prop.axes_names) @property def axes_order(self): - """ A tuple of indices which map inputs to axes.""" + """A tuple of indices which map inputs to axes.""" return self._axes_order @property def reference_frame(self): - """ Reference frame, used to convert to world coordinate objects. """ + """Reference frame, used to convert to world coordinate objects.""" return self._reference_frame @property def axes_type(self): - """ Type of this frame : 'SPATIAL', 'SPECTRAL', 'TIME'. """ + """Type of this frame : 'SPATIAL', 'SPECTRAL', 'TIME'.""" return self._sort_property(self._prop.axes_type) @property @@ -499,14 +525,17 @@ def axis_physical_types(self): @property def world_axis_object_classes(self): - return {f"{at}{i}" if i != 0 else at: (u.Quantity, - (), - {'unit': unit}) - for i, (at, unit) in enumerate(zip(self.axes_type, self.unit))} + return { + f"{at}{i}" if i != 0 else at: (u.Quantity, (), {"unit": unit}) + for i, (at, unit) in enumerate(zip(self.axes_type, self.unit)) + } @property def _native_world_axis_object_components(self): - return [(f"{at}{i}" if i != 0 else at, 0, 'value') for i, at in enumerate(self._prop.axes_type)] + return [ + (f"{at}{i}" if i != 0 else at, 0, "value") + for i, at in enumerate(self._prop.axes_type) + ] @property def serialized_classes(self): @@ -536,9 +565,14 @@ def to_high_level_coordinates(self, *values): One (or more) high level object describing the coordinate. """ # We allow Quantity-like objects here which values_to_high_level_objects does not. - values = [v.to_value(unit) if hasattr(v, "to_value") else v for v, unit in zip(values, self.unit)] - - if not all([isinstance(v, numbers.Number) or type(v) is np.ndarray for v in values]): + values = [ + v.to_value(unit) if hasattr(v, "to_value") else v + for v, unit in zip(values, self.unit) + ] + + if not all( + [isinstance(v, numbers.Number) or type(v) is np.ndarray for v in values] + ): raise TypeError("All values should be a scalar number or a numpy array.") high_level = values_to_high_level_objects(*values, low_level_wcs=self) @@ -594,16 +628,24 @@ class CelestialFrame(CoordinateFrame): The UCD 1+ physical types for the axes, in frame order (lon, lat). """ - def __init__(self, axes_order=None, reference_frame=None, - unit=None, axes_names=None, - name=None, axis_physical_types=None): + def __init__( + self, + axes_order=None, + reference_frame=None, + unit=None, + axes_names=None, + name=None, + axis_physical_types=None, + ): naxes = 2 if reference_frame is not None: if not isinstance(reference_frame, str): if reference_frame.name.upper() in STANDARD_REFERENCE_FRAMES: - _axes_names = list(reference_frame.representation_component_names.values()) - if 'distance' in _axes_names: - _axes_names.remove('distance') + _axes_names = list( + reference_frame.representation_component_names.values() + ) + if "distance" in _axes_names: + _axes_names.remove("distance") if axes_names is None: axes_names = _axes_names naxes = len(_axes_names) @@ -613,24 +655,29 @@ def __init__(self, axes_order=None, reference_frame=None, axes_order = self.native_axes_order if unit is None: unit = tuple([u.degree] * naxes) - axes_type = ['SPATIAL'] * naxes - - pht = axis_physical_types or self._default_axis_physical_types(reference_frame, axes_names) - super().__init__(naxes=naxes, - axes_type=axes_type, - axes_order=axes_order, - reference_frame=reference_frame, - unit=unit, - axes_names=axes_names, - name=name, - axis_physical_types=pht) + axes_type = ["SPATIAL"] * naxes + + pht = axis_physical_types or self._default_axis_physical_types( + reference_frame, axes_names + ) + super().__init__( + naxes=naxes, + axes_type=axes_type, + axes_order=axes_order, + reference_frame=reference_frame, + unit=unit, + axes_names=axes_names, + name=name, + axis_physical_types=pht, + ) def _default_axis_physical_types(self, reference_frame, axes_names): if isinstance(reference_frame, coord.Galactic): return "pos.galactic.lon", "pos.galactic.lat" - elif isinstance(reference_frame, (coord.GeocentricTrueEcliptic, - coord.GCRS, - coord.PrecessedGeocentric)): + elif isinstance( + reference_frame, + (coord.GeocentricTrueEcliptic, coord.GCRS, coord.PrecessedGeocentric), + ): return "pos.bodyrc.lon", "pos.bodyrc.lat" elif isinstance(reference_frame, coord.builtin_frames.BaseRADecFrame): return "pos.eq.ra", "pos.eq.dec" @@ -641,16 +688,20 @@ def _default_axis_physical_types(self, reference_frame, axes_names): @property def world_axis_object_classes(self): - return {'celestial': ( - coord.SkyCoord, - (), - {'frame': self.reference_frame, - 'unit': self._prop.unit})} + return { + "celestial": ( + coord.SkyCoord, + (), + {"frame": self.reference_frame, "unit": self._prop.unit}, + ) + } @property def _native_world_axis_object_components(self): - return [('celestial', 0, lambda sc: sc.spherical.lon.to_value(self._prop.unit[0])), - ('celestial', 1, lambda sc: sc.spherical.lat.to_value(self._prop.unit[1]))] + return [ + ("celestial", 0, lambda sc: sc.spherical.lon.to_value(self._prop.unit[0])), + ("celestial", 1, lambda sc: sc.spherical.lat.to_value(self._prop.unit[1])), + ] class SpectralFrame(CoordinateFrame): @@ -672,18 +723,30 @@ class SpectralFrame(CoordinateFrame): """ - def __init__(self, axes_order=(0,), reference_frame=None, unit=None, - axes_names=None, name=None, axis_physical_types=None): - + def __init__( + self, + axes_order=(0,), + reference_frame=None, + unit=None, + axes_names=None, + name=None, + axis_physical_types=None, + ): if not isiterable(unit): unit = (unit,) unit = [u.Unit(un) for un in unit] pht = axis_physical_types or self._default_axis_physical_types(unit) - super().__init__(naxes=1, axes_type="SPECTRAL", axes_order=axes_order, - axes_names=axes_names, reference_frame=reference_frame, - unit=unit, name=name, - axis_physical_types=pht) + super().__init__( + naxes=1, + axes_type="SPECTRAL", + axes_order=axes_order, + axes_names=axes_names, + reference_frame=reference_frame, + unit=unit, + name=name, + axis_physical_types=pht, + ) def _default_axis_physical_types(self, unit): if unit[0].physical_type == "frequency": @@ -694,23 +757,22 @@ def _default_axis_physical_types(self, unit): return ("em.energy",) elif unit[0].physical_type == "speed": return ("spect.dopplerVeloc",) - logging.warning("Physical type may be ambiguous. Consider " - "setting the physical type explicitly as " - "either 'spect.dopplerVeloc.optical' or " - "'spect.dopplerVeloc.radio'.") + logging.warning( + "Physical type may be ambiguous. Consider " + "setting the physical type explicitly as " + "either 'spect.dopplerVeloc.optical' or " + "'spect.dopplerVeloc.radio'." + ) else: return ("custom:{}".format(unit[0].physical_type),) @property def world_axis_object_classes(self): - return {'spectral': ( - coord.SpectralCoord, - (), - {'unit': self.unit[0]})} + return {"spectral": (coord.SpectralCoord, (), {"unit": self.unit[0]})} @property def _native_world_axis_object_components(self): - return [('spectral', 0, lambda sc: sc.to_value(self.unit[0]))] + return [("spectral", 0, lambda sc: sc.to_value(self.unit[0]))] class TemporalFrame(CoordinateFrame): @@ -734,17 +796,31 @@ class TemporalFrame(CoordinateFrame): Name for this frame. """ - def __init__(self, reference_frame, unit=u.s, axes_order=(0,), - axes_names=None, name=None, axis_physical_types=None): - axes_names = axes_names or "{}({}; {}".format(reference_frame.format, - reference_frame.scale, - reference_frame.location) + def __init__( + self, + reference_frame, + unit=u.s, + axes_order=(0,), + axes_names=None, + name=None, + axis_physical_types=None, + ): + axes_names = axes_names or "{}({}; {}".format( + reference_frame.format, reference_frame.scale, reference_frame.location + ) pht = axis_physical_types or self._default_axis_physical_types() - super().__init__(naxes=1, axes_type="TIME", axes_order=axes_order, - axes_names=axes_names, reference_frame=reference_frame, - unit=unit, name=name, axis_physical_types=pht) + super().__init__( + naxes=1, + axes_type="TIME", + axes_order=axes_order, + axes_names=axes_names, + reference_frame=reference_frame, + unit=unit, + name=name, + axis_physical_types=pht, + ) self._attrs = {} for a in self.reference_frame.info._represent_as_dict_extra_attrs: try: @@ -756,12 +832,14 @@ def _default_axis_physical_types(self): return ("time",) def _convert_to_time(self, dt, *, unit, **kwargs): - if (not isinstance(dt, time.TimeDelta) and - isinstance(dt, time.Time) or - isinstance(self.reference_frame.value, np.ndarray)): + if ( + not isinstance(dt, time.TimeDelta) + and isinstance(dt, time.Time) + or isinstance(self.reference_frame.value, np.ndarray) + ): return time.Time(dt, **kwargs) - if not hasattr(dt, 'unit'): + if not hasattr(dt, "unit"): dt = dt * unit return self.reference_frame + dt @@ -771,19 +849,21 @@ def world_axis_object_classes(self): comp = ( time.Time, (), - {'unit': self.unit[0], **self._attrs}, - self._convert_to_time) + {"unit": self.unit[0], **self._attrs}, + self._convert_to_time, + ) - return {'temporal': comp} + return {"temporal": comp} @property def _native_world_axis_object_components(self): if isinstance(self.reference_frame.value, np.ndarray): - return [('temporal', 0, 'value')] + return [("temporal", 0, "value")] def offset_from_time_and_reference(time): return (time - self.reference_frame).sec - return [('temporal', 0, offset_from_time_and_reference)] + + return [("temporal", 0, offset_from_time_and_reference)] class CompositeFrame(CoordinateFrame): @@ -819,15 +899,21 @@ def __init__(self, frames, name=None): ph_type += list(frame._prop.axis_physical_types) if len(np.unique(axes_order)) != len(axes_order): - raise ValueError("Incorrect numbering of axes, " - "axes_order should contain unique numbers, " - f"got {axes_order}.") - - super().__init__(naxes, axes_type=axes_type, - axes_order=axes_order, - unit=unit, axes_names=axes_names, - axis_physical_types=tuple(ph_type), - name=name) + raise ValueError( + "Incorrect numbering of axes, " + "axes_order should contain unique numbers, " + f"got {axes_order}." + ) + + super().__init__( + naxes, + axes_type=axes_type, + axes_order=axes_order, + unit=unit, + axes_names=axes_names, + axis_physical_types=tuple(ph_type), + name=name, + ) self._axis_physical_types = tuple(ph_type) @property @@ -886,7 +972,9 @@ def world_axis_object_components(self): out[ao] = components[i] if any([o is None for o in out]): - raise ValueError("axes_order leads to incomplete world_axis_object_components") + raise ValueError( + "axes_order leads to incomplete world_axis_object_components" + ) return out @@ -907,28 +995,41 @@ class StokesFrame(CoordinateFrame): A dimension in the data that corresponds to this axis. """ - def __init__(self, axes_order=(0,), axes_names=("stokes",), name=None, axis_physical_types=None): - + def __init__( + self, + axes_order=(0,), + axes_names=("stokes",), + name=None, + axis_physical_types=None, + ): pht = axis_physical_types or self._default_axis_physical_types() - super().__init__(1, ["STOKES"], axes_order, name=name, - axes_names=axes_names, unit=u.one, - axis_physical_types=pht) + super().__init__( + 1, + ["STOKES"], + axes_order, + name=name, + axes_names=axes_names, + unit=u.one, + axis_physical_types=pht, + ) def _default_axis_physical_types(self): return ("phys.polarization.stokes",) @property def world_axis_object_classes(self): - return {'stokes': ( - StokesCoord, - (), - {}, - )} + return { + "stokes": ( + StokesCoord, + (), + {}, + ) + } @property def _native_world_axis_object_components(self): - return [('stokes', 0, 'value')] + return [("stokes", 0, "value")] class Frame2D(CoordinateFrame): @@ -947,15 +1048,28 @@ class Frame2D(CoordinateFrame): Name of this frame. """ - def __init__(self, axes_order=(0, 1), unit=(u.pix, u.pix), axes_names=('x', 'y'), - name=None, axes_type=["SPATIAL", "SPATIAL"], axis_physical_types=None): - - pht = axis_physical_types or self._default_axis_physical_types(axes_names, axes_type) + def __init__( + self, + axes_order=(0, 1), + unit=(u.pix, u.pix), + axes_names=("x", "y"), + name=None, + axes_type=["SPATIAL", "SPATIAL"], + axis_physical_types=None, + ): + pht = axis_physical_types or self._default_axis_physical_types( + axes_names, axes_type + ) - super().__init__(naxes=2, axes_type=axes_type, - axes_order=axes_order, name=name, - axes_names=axes_names, unit=unit, - axis_physical_types=pht) + super().__init__( + naxes=2, + axes_type=axes_type, + axes_order=axes_order, + name=name, + axes_names=axes_names, + unit=unit, + axis_physical_types=pht, + ) def _default_axis_physical_types(self, axes_names, axes_type): if axes_names is not None and all(axes_names): diff --git a/gwcs/examples.py b/gwcs/examples.py index a0379796..59cc59c8 100644 --- a/gwcs/examples.py +++ b/gwcs/examples.py @@ -10,14 +10,16 @@ from . import wcs # frames -DETECTOR_1D_FRAME = cf.CoordinateFrame(name='detector', axes_order=(0,), naxes=1, axes_type="detector") -DETECTOR_2D_FRAME = cf.Frame2D(name='detector', axes_order=(0, 1)) -ICRC_SKY_FRAME = cf.CelestialFrame(reference_frame=coord.ICRS(), - axes_order=(0, 1)) +DETECTOR_1D_FRAME = cf.CoordinateFrame( + name="detector", axes_order=(0,), naxes=1, axes_type="detector" +) +DETECTOR_2D_FRAME = cf.Frame2D(name="detector", axes_order=(0, 1)) +ICRC_SKY_FRAME = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_order=(0, 1)) -FREQ_FRAME = cf.SpectralFrame(name='freq', unit=u.Hz, axes_order=(0, )) -WAVE_FRAME = cf.SpectralFrame(name='wave', unit=u.m, axes_order=(2, ), - axes_names=('lambda', )) +FREQ_FRAME = cf.SpectralFrame(name="freq", unit=u.Hz, axes_order=(0,)) +WAVE_FRAME = cf.SpectralFrame( + name="wave", unit=u.m, axes_order=(2,), axes_names=("lambda",) +) # transforms MODEL_2D_SHIFT = models.Shift(1) & models.Shift(2) @@ -25,7 +27,13 @@ def gwcs_2d_quantity_shift(): - frame = cf.CoordinateFrame(name="quantity", axes_order=(0, 1), naxes=2, axes_type=("SPATIAL", "SPATIAL"), unit=(u.km, u.km)) + frame = cf.CoordinateFrame( + name="quantity", + axes_order=(0, 1), + naxes=2, + axes_type=("SPATIAL", "SPATIAL"), + unit=(u.km, u.km), + ) pipe = [(DETECTOR_2D_FRAME, MODEL_2D_SHIFT), (frame, None)] return wcs.WCS(pipe) @@ -43,9 +51,12 @@ def gwcs_2d_spatial_reordered(): """ A simple one step spatial WCS, in ICRS with a 1 and 2 px shift. """ - out_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), - axes_order=(1, 0)) - return wcs.WCS(MODEL_2D_SHIFT | models.Mapping((1, 0)), input_frame=DETECTOR_2D_FRAME, output_frame=out_frame) + out_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_order=(1, 0)) + return wcs.WCS( + MODEL_2D_SHIFT | models.Mapping((1, 0)), + input_frame=DETECTOR_2D_FRAME, + output_frame=out_frame, + ) def gwcs_1d_freq(): @@ -56,13 +67,16 @@ def gwcs_3d_spatial_wave(): comp1 = cf.CompositeFrame([ICRC_SKY_FRAME, WAVE_FRAME]) m = MODEL_2D_SHIFT & MODEL_1D_SCALE - detector_frame = cf.CoordinateFrame(name="detector", naxes=3, - axes_order=(0, 1, 2), - axes_type=("pixel", "pixel", "pixel"), - axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) + detector_frame = cf.CoordinateFrame( + name="detector", + naxes=3, + axes_order=(0, 1, 2), + axes_type=("pixel", "pixel", "pixel"), + axes_names=("x", "y", "z"), + unit=(u.pix, u.pix, u.pix), + ) - return wcs.WCS([(detector_frame, m), - (comp1, None)]) + return wcs.WCS([(detector_frame, m), (comp1, None)]) def gwcs_2d_shift_scale(): @@ -72,6 +86,7 @@ def gwcs_2d_shift_scale(): pipe = [(DETECTOR_2D_FRAME, m3), (ICRC_SKY_FRAME, None)] return wcs.WCS(pipe) + def gwcs_2d_bad_bounding_box_order(): m1 = models.Shift(1) & models.Shift(2) m2 = models.Scale(5) & models.Scale(10) @@ -85,25 +100,24 @@ def gwcs_2d_bad_bounding_box_order(): def gwcs_1d_freq_quantity(): - - detector_1d = cf.CoordinateFrame(name='detector', axes_order=(0,), naxes=1, unit=u.pix, axes_type="detector") - return wcs.WCS([(detector_1d, models.Multiply(1 * u.Hz / u.pix)), (FREQ_FRAME, None)]) + detector_1d = cf.CoordinateFrame( + name="detector", axes_order=(0,), naxes=1, unit=u.pix, axes_type="detector" + ) + return wcs.WCS( + [(detector_1d, models.Multiply(1 * u.Hz / u.pix)), (FREQ_FRAME, None)] + ) def gwcs_2d_shift_scale_quantity(): m4 = models.Shift(1 * u.pix) & models.Shift(2 * u.pix) m5 = models.Scale(5 * u.deg) m6 = models.Scale(10 * u.deg) - m5.input_units_equivalencies = {'x': u.pixel_scale(1 * u.deg / u.pix)} - m6.input_units_equivalencies = {'x': u.pixel_scale(1 * u.deg / u.pix)} - m5.inverse = models.Scale(1. / 5 * u.pix) - m6.inverse = models.Scale(1. / 10 * u.pix) - m5.inverse.input_units_equivalencies = { - 'x': u.pixel_scale(1 * u.pix / u.deg) - } - m6.inverse.input_units_equivalencies = { - 'x': u.pixel_scale(1 * u.pix / u.deg) - } + m5.input_units_equivalencies = {"x": u.pixel_scale(1 * u.deg / u.pix)} + m6.input_units_equivalencies = {"x": u.pixel_scale(1 * u.deg / u.pix)} + m5.inverse = models.Scale(1.0 / 5 * u.pix) + m6.inverse = models.Scale(1.0 / 10 * u.pix) + m5.inverse.input_units_equivalencies = {"x": u.pixel_scale(1 * u.pix / u.deg)} + m6.inverse.input_units_equivalencies = {"x": u.pixel_scale(1 * u.pix / u.deg)} m7 = m5 & m6 m8 = m4 | m7 pipe2 = [(DETECTOR_2D_FRAME, m8), (ICRC_SKY_FRAME, None)] @@ -114,144 +128,188 @@ def gwcs_3d_identity_units(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ - identity = (models.Multiply(1 * u.arcsec / u.pixel) & - models.Multiply(1 * u.arcsec / u.pixel) & - models.Multiply(1 * u.nm / u.pixel)) - sky_frame = cf.CelestialFrame(axes_order=(0, 1), name='icrs', - reference_frame=coord.ICRS(), - axes_names=("longitude", "latitude"), - unit=(u.arcsec, u.arcsec)) - wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm, axes_names=("wavelength",)) + identity = ( + models.Multiply(1 * u.arcsec / u.pixel) + & models.Multiply(1 * u.arcsec / u.pixel) + & models.Multiply(1 * u.nm / u.pixel) + ) + sky_frame = cf.CelestialFrame( + axes_order=(0, 1), + name="icrs", + reference_frame=coord.ICRS(), + axes_names=("longitude", "latitude"), + unit=(u.arcsec, u.arcsec), + ) + wave_frame = cf.SpectralFrame( + axes_order=(2,), unit=u.nm, axes_names=("wavelength",) + ) frame = cf.CompositeFrame([sky_frame, wave_frame]) - detector_frame = cf.CoordinateFrame(name="detector", naxes=3, - axes_order=(0, 1, 2), - axes_type=("pixel", "pixel", "pixel"), - axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) + detector_frame = cf.CoordinateFrame( + name="detector", + naxes=3, + axes_order=(0, 1, 2), + axes_type=("pixel", "pixel", "pixel"), + axes_names=("x", "y", "z"), + unit=(u.pix, u.pix, u.pix), + ) - return wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame) + return wcs.WCS( + forward_transform=identity, output_frame=frame, input_frame=detector_frame + ) def gwcs_4d_identity_units(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ - identity = (models.Multiply(1*u.arcsec/u.pixel) & models.Multiply(1*u.arcsec/u.pixel) & - models.Multiply(1*u.nm/u.pixel) & models.Multiply(1*u.s/u.pixel)) - sky_frame = cf.CelestialFrame(axes_order=(0, 1), name='icrs', - reference_frame=coord.ICRS()) - wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm) - time_frame = cf.TemporalFrame(axes_order=(3, ), unit=u.s, - reference_frame=Time("2000-01-01T00:00:00")) + identity = ( + models.Multiply(1 * u.arcsec / u.pixel) + & models.Multiply(1 * u.arcsec / u.pixel) + & models.Multiply(1 * u.nm / u.pixel) + & models.Multiply(1 * u.s / u.pixel) + ) + sky_frame = cf.CelestialFrame( + axes_order=(0, 1), name="icrs", reference_frame=coord.ICRS() + ) + wave_frame = cf.SpectralFrame(axes_order=(2,), unit=u.nm) + time_frame = cf.TemporalFrame( + axes_order=(3,), unit=u.s, reference_frame=Time("2000-01-01T00:00:00") + ) frame = cf.CompositeFrame([sky_frame, wave_frame, time_frame]) - detector_frame = cf.CoordinateFrame(name="detector", naxes=4, - axes_order=(0, 1, 2, 3), - axes_type=("pixel", "pixel", "pixel", "pixel"), - axes_names=("x", "y", "z", "s"), unit=(u.pix, u.pix, u.pix, u.pix)) + detector_frame = cf.CoordinateFrame( + name="detector", + naxes=4, + axes_order=(0, 1, 2, 3), + axes_type=("pixel", "pixel", "pixel", "pixel"), + axes_names=("x", "y", "z", "s"), + unit=(u.pix, u.pix, u.pix, u.pix), + ) - return wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame) + return wcs.WCS( + forward_transform=identity, output_frame=frame, input_frame=detector_frame + ) def gwcs_simple_imaging_units(): - shift_by_crpix = models.Shift(-2048*u.pix) & models.Shift(-1024*u.pix) - matrix = np.array([[1.290551569736E-05, 5.9525007864732E-06], - [5.0226382102765E-06 , -1.2644844123757E-05]]) - rotation = models.AffineTransformation2D(matrix * u.deg, - translation=[0, 0] * u.deg) - rotation.input_units_equivalencies = {"x": u.pixel_scale(1*u.deg/u.pix), - "y": u.pixel_scale(1*u.deg/u.pix)} - rotation.inverse = models.AffineTransformation2D(np.linalg.inv(matrix) * u.pix, - translation=[0, 0] * u.pix) - rotation.inverse.input_units_equivalencies = {"x": u.pixel_scale(1*u.pix/u.deg), - "y": u.pixel_scale(1*u.pix/u.deg)} + shift_by_crpix = models.Shift(-2048 * u.pix) & models.Shift(-1024 * u.pix) + matrix = np.array( + [ + [1.290551569736e-05, 5.9525007864732e-06], + [5.0226382102765e-06, -1.2644844123757e-05], + ] + ) + rotation = models.AffineTransformation2D(matrix * u.deg, translation=[0, 0] * u.deg) + rotation.input_units_equivalencies = { + "x": u.pixel_scale(1 * u.deg / u.pix), + "y": u.pixel_scale(1 * u.deg / u.pix), + } + rotation.inverse = models.AffineTransformation2D( + np.linalg.inv(matrix) * u.pix, translation=[0, 0] * u.pix + ) + rotation.inverse.input_units_equivalencies = { + "x": u.pixel_scale(1 * u.pix / u.deg), + "y": u.pixel_scale(1 * u.pix / u.deg), + } tan = models.Pix2Sky_TAN() - celestial_rotation = models.RotateNative2Celestial(5.63056810618*u.deg, - -72.05457184279*u.deg, - 180*u.deg) + celestial_rotation = models.RotateNative2Celestial( + 5.63056810618 * u.deg, -72.05457184279 * u.deg, 180 * u.deg + ) det2sky = shift_by_crpix | rotation | tan | celestial_rotation det2sky.name = "linear_transform" - detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"), - unit=(u.pix, u.pix)) - sky_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), name='icrs', - unit=(u.deg, u.deg)) - pipeline = [(detector_frame, det2sky), - (sky_frame, None) - ] + detector_frame = cf.Frame2D( + name="detector", axes_names=("x", "y"), unit=(u.pix, u.pix) + ) + sky_frame = cf.CelestialFrame( + reference_frame=coord.ICRS(), name="icrs", unit=(u.deg, u.deg) + ) + pipeline = [(detector_frame, det2sky), (sky_frame, None)] return wcs.WCS(pipeline) def gwcs_simple_imaging(): shift_by_crpix = models.Shift(-2048) & models.Shift(-1024) - matrix = np.array([[1.290551569736E-05, 5.9525007864732E-06], - [5.0226382102765E-06 , -1.2644844123757E-05]]) - rotation = models.AffineTransformation2D(matrix, - translation=[0, 0]) - rotation.inverse = models.AffineTransformation2D(np.linalg.inv(matrix), - translation=[0, 0]) + matrix = np.array( + [ + [1.290551569736e-05, 5.9525007864732e-06], + [5.0226382102765e-06, -1.2644844123757e-05], + ] + ) + rotation = models.AffineTransformation2D(matrix, translation=[0, 0]) + rotation.inverse = models.AffineTransformation2D( + np.linalg.inv(matrix), translation=[0, 0] + ) tan = models.Pix2Sky_TAN() - celestial_rotation = models.RotateNative2Celestial(5.63056810618, - -72.05457184279, - 180) + celestial_rotation = models.RotateNative2Celestial( + 5.63056810618, -72.05457184279, 180 + ) det2sky = shift_by_crpix | rotation | tan | celestial_rotation det2sky.name = "linear_transform" detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y")) - sky_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), name='icrs') - pipeline = [(detector_frame, det2sky), - (sky_frame, None) - ] + sky_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), name="icrs") + pipeline = [(detector_frame, det2sky), (sky_frame, None)] return wcs.WCS(pipeline) def gwcs_stokes_lookup(): - transform = models.Tabular1D([0, 1, 2, 3] * u.pix, [1, 2, 3, 4] * u.one, - method="nearest", fill_value=np.nan, bounds_error=False) + transform = models.Tabular1D( + [0, 1, 2, 3] * u.pix, + [1, 2, 3, 4] * u.one, + method="nearest", + fill_value=np.nan, + bounds_error=False, + ) frame = cf.StokesFrame() - detector_frame = cf.CoordinateFrame(name="detector", naxes=1, - axes_order=(0,), - axes_type=("pixel",), - axes_names=("x",), unit=(u.pix,)) + detector_frame = cf.CoordinateFrame( + name="detector", + naxes=1, + axes_order=(0,), + axes_type=("pixel",), + axes_names=("x",), + unit=(u.pix,), + ) - return wcs.WCS(forward_transform=transform, output_frame=frame, input_frame=detector_frame) + return wcs.WCS( + forward_transform=transform, output_frame=frame, input_frame=detector_frame + ) def gwcs_3spectral_orders(): comp1 = cf.CompositeFrame([ICRC_SKY_FRAME, WAVE_FRAME]) - detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"), - unit=(u.pix, u.pix)) + detector_frame = cf.Frame2D( + name="detector", axes_names=("x", "y"), unit=(u.pix, u.pix) + ) m = MODEL_2D_SHIFT & MODEL_1D_SCALE - return wcs.WCS([(detector_frame, m), - (comp1, None)]) + return wcs.WCS([(detector_frame, m), (comp1, None)]) def gwcs_with_frames_strings(): transform = models.Shift(1) & models.Shift(1) & models.Polynomial2D(1) - pipe = [('detector', transform), - ('world', None) - ] + pipe = [("detector", transform), ("world", None)] return wcs.WCS(pipe) def sellmeier_glass(): - B_coef = [0.58339748, 0.46085267, 3.8915394] + B_coef = [0.58339748, 0.46085267, 3.8915394] C_coef = [0.00252643, 0.010078333, 1200.556] return sp.SellmeierGlass(B_coef, C_coef) def sellmeier_zemax(): - B_coef = [0.58339748, 0.46085267, 3.8915394] + B_coef = [0.58339748, 0.46085267, 3.8915394] C_coef = [0.00252643, 0.010078333, 1200.556] D_coef = [-2.66e-05, 0.0, 0.0] - E_coef = [0., 0., 0.] - return sp.SellmeierZemax(65, 35, 0, 0, B_coef = B_coef, - C_coef=C_coef, D_coef=D_coef, - E_coef=E_coef) + E_coef = [0.0, 0.0, 0.0] + return sp.SellmeierZemax( + 65, 35, 0, 0, B_coef=B_coef, C_coef=C_coef, D_coef=D_coef, E_coef=E_coef + ) def gwcs_3d_galactic_spectral(): @@ -271,20 +329,30 @@ def gwcs_3d_galactic_spectral(): wave_model = models.Shift(-crpix2) | models.Multiply(cdelt2) | models.Shift(crval2) - transform = models.Mapping((2, 0, 1)) | celestial & wave_model | models.Mapping((1, 2, 0)) + transform = ( + models.Mapping((2, 0, 1)) | celestial & wave_model | models.Mapping((1, 2, 0)) + ) - sky_frame = cf.CelestialFrame(axes_order=(2, 0), - reference_frame=coord.Galactic(), axes_names=("Longitude", "Latitude")) - wave_frame = cf.SpectralFrame(axes_order=(1, ), unit=u.Hz, axes_names=("Frequency",)) + sky_frame = cf.CelestialFrame( + axes_order=(2, 0), + reference_frame=coord.Galactic(), + axes_names=("Longitude", "Latitude"), + ) + wave_frame = cf.SpectralFrame(axes_order=(1,), unit=u.Hz, axes_names=("Frequency",)) frame = cf.CompositeFrame([sky_frame, wave_frame]) - detector_frame = cf.CoordinateFrame(name="detector", naxes=3, - axes_order=(0, 1, 2), - axes_type=("pixel", "pixel", "pixel"), - unit=(u.pix, u.pix, u.pix)) + detector_frame = cf.CoordinateFrame( + name="detector", + naxes=3, + axes_order=(0, 1, 2), + axes_type=("pixel", "pixel", "pixel"), + unit=(u.pix, u.pix, u.pix), + ) - owcs = wcs.WCS(forward_transform=transform, output_frame=frame, input_frame=detector_frame) + owcs = wcs.WCS( + forward_transform=transform, output_frame=frame, input_frame=detector_frame + ) owcs.bounding_box = ((-1, 35), (-2, 45), (5, 50)) owcs.array_shape = (30, 20, 10) owcs.pixel_shape = (10, 20, 30) @@ -298,16 +366,19 @@ def gwcs_1d_spectral(): """ wave_model = models.Shift(-5) | models.Multiply(3.7) | models.Shift(20) wave_model.bounding_box = (7, 50) - wave_frame = cf.SpectralFrame(axes_order=(0, ), unit=u.Hz, axes_names=("Frequency",)) + wave_frame = cf.SpectralFrame(axes_order=(0,), unit=u.Hz, axes_names=("Frequency",)) detector_frame = cf.CoordinateFrame( - name="detector", naxes=1, axes_order=(0, ), - axes_type=("pixel",), unit=(u.pix, ) + name="detector", naxes=1, axes_order=(0,), axes_type=("pixel",), unit=(u.pix,) ) - owcs = wcs.WCS(forward_transform=wave_model, output_frame=wave_frame, input_frame=detector_frame) - owcs.array_shape = (44, ) - owcs.pixel_shape = (44, ) + owcs = wcs.WCS( + forward_transform=wave_model, + output_frame=wave_frame, + input_frame=detector_frame, + ) + owcs.array_shape = (44,) + owcs.pixel_shape = (44,) return owcs @@ -319,36 +390,48 @@ def gwcs_spec_cel_time_4d(): # spectroscopic frame: wave_model = models.Shift(-5) | models.Multiply(3.7) | models.Shift(20) wave_model.bounding_box = (7, 50) - wave_frame = cf.SpectralFrame(name='wave', unit=u.m, axes_order=(0,), axes_names=('lambda',)) + wave_frame = cf.SpectralFrame( + name="wave", unit=u.m, axes_order=(0,), axes_names=("lambda",) + ) # time frame: time_model = models.Identity(1) # models.Linear1D(10, 0) - time_frame = cf.TemporalFrame(Time("2010-01-01T00:00"), name='time', unit=u.s, axes_order=(3,)) + time_frame = cf.TemporalFrame( + Time("2010-01-01T00:00"), name="time", unit=u.s, axes_order=(3,) + ) # Values from data/acs.hdr: crpix = (12, 13) crval = (5.63, -72.05) - cd = [[1.291E-05, 5.9532E-06], [5.02215E-06, -1.2645E-05]] - aff = models.AffineTransformation2D(matrix=cd, name='rotation') - offx = models.Shift(-crpix[0], name='x_translation') - offy = models.Shift(-crpix[1], name='y_translation') + cd = [[1.291e-05, 5.9532e-06], [5.02215e-06, -1.2645e-05]] + aff = models.AffineTransformation2D(matrix=cd, name="rotation") + offx = models.Shift(-crpix[0], name="x_translation") + offy = models.Shift(-crpix[1], name="y_translation") wcslin = models.Mapping((1, 0)) | (offx & offy) | aff - tan = models.Pix2Sky_TAN(name='tangent_projection') - n2c = models.RotateNative2Celestial(*crval, 180, name='sky_rotation') + tan = models.Pix2Sky_TAN(name="tangent_projection") + n2c = models.RotateNative2Celestial(*crval, 180, name="sky_rotation") cel_model = wcslin | tan | n2c - icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_order=(2, 1)) + icrs = cf.CelestialFrame( + reference_frame=coord.ICRS(), name="sky", axes_order=(2, 1) + ) wcs_forward = wave_model & cel_model & time_model - comp_frm = cf.CompositeFrame(frames=[wave_frame, icrs, time_frame], name='TEST 4D FRAME') + comp_frm = cf.CompositeFrame( + frames=[wave_frame, icrs, time_frame], name="TEST 4D FRAME" + ) detector_frame = cf.CoordinateFrame( - name="detector", naxes=4, axes_order=(0, 1, 2, 3), + name="detector", + naxes=4, + axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), - unit=(u.pix, u.pix, u.pix, u.pix) + unit=(u.pix, u.pix, u.pix, u.pix), ) - w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame) + w = wcs.WCS( + forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame + ) w.bounding_box = ((0, 63), (0, 127), (0, 255), (0, 9)) w.array_shape = (10, 256, 128, 64) @@ -365,41 +448,56 @@ def gwcs_cube_with_separable_spectral(axes_order): """ cube_size = (128, 64, 100) - spectral_axes_order = (axes_order.index(2), ) + spectral_axes_order = (axes_order.index(2),) cel_axes_order = (axes_order.index(0), axes_order.index(1)) # Values from data/acs.hdr: crpix = (64, 32) crval = (5.63056810618, -72.0545718428) - cd = [[1.29058667557984E-05, 5.95320245884555E-06], - [5.02215195623825E-06, -1.2645010396976E-05]] + cd = [ + [1.29058667557984e-05, 5.95320245884555e-06], + [5.02215195623825e-06, -1.2645010396976e-05], + ] - aff = models.AffineTransformation2D(matrix=cd, name='rotation') - offx = models.Shift(-crpix[0], name='x_translation') - offy = models.Shift(-crpix[1], name='y_translation') + aff = models.AffineTransformation2D(matrix=cd, name="rotation") + offx = models.Shift(-crpix[0], name="x_translation") + offy = models.Shift(-crpix[1], name="y_translation") wcslin = (offx & offy) | aff - tan = models.Pix2Sky_TAN(name='tangent_projection') - n2c = models.RotateNative2Celestial(*crval, 180, name='sky_rotation') - icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', - axes_order=cel_axes_order) + tan = models.Pix2Sky_TAN(name="tangent_projection") + n2c = models.RotateNative2Celestial(*crval, 180, name="sky_rotation") + icrs = cf.CelestialFrame( + reference_frame=coord.ICRS(), name="sky", axes_order=cel_axes_order + ) spec = cf.SpectralFrame( - name='wave', unit=[u.m,], axes_order=spectral_axes_order, - axes_names=('lambda',) - ) - comp_frm = cf.CompositeFrame(frames=[icrs, spec], name='TEST 3D FRAME WITH SPECTRAL AXIS') - wcs_forward = ((wcslin & models.Identity(1)) | - (tan & models.Identity(1)) | - (n2c & models.Identity(1)) | - models.Mapping(axes_order)) - - detector_frame = cf.CoordinateFrame(name="detector", naxes=3, - axes_order=(0, 1, 2), - axes_type=("pixel", "pixel", "pixel"), - unit=(u.pix, u.pix, u.pix)) - - w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, - input_frame=detector_frame) + name="wave", + unit=[ + u.m, + ], + axes_order=spectral_axes_order, + axes_names=("lambda",), + ) + comp_frm = cf.CompositeFrame( + frames=[icrs, spec], name="TEST 3D FRAME WITH SPECTRAL AXIS" + ) + wcs_forward = ( + (wcslin & models.Identity(1)) + | (tan & models.Identity(1)) + | (n2c & models.Identity(1)) + | models.Mapping(axes_order) + ) + + detector_frame = cf.CoordinateFrame( + name="detector", + naxes=3, + axes_order=(0, 1, 2), + axes_type=("pixel", "pixel", "pixel"), + unit=(u.pix, u.pix, u.pix), + ) + + w = wcs.WCS( + forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame + ) w.bounding_box = tuple((0, k - 1) for k in cube_size) w.pixel_shape = cube_size w.array_shape = w.pixel_shape[::-1] @@ -417,40 +515,47 @@ def gwcs_cube_with_separable_time(axes_order): """ cube_size = (64, 32, 128) - time_axes_order = (axes_order.index(2), ) + time_axes_order = (axes_order.index(2),) cel_axes_order = (axes_order.index(0), axes_order.index(1)) detector_frame = cf.CoordinateFrame( - name="detector", naxes=3, axes_order=(0, 1, 2), - axes_type=("pixel", "pixel", "pixel"), unit=(u.pix, u.pix, u.pix) + name="detector", + naxes=3, + axes_order=(0, 1, 2), + axes_type=("pixel", "pixel", "pixel"), + unit=(u.pix, u.pix, u.pix), ) # time frame: time_model = models.Identity(1) # models.Linear1D(10, 0) - time_frame = cf.TemporalFrame(Time("2010-01-01T00:00"), name='time', - unit=u.s, axes_order=time_axes_order) + time_frame = cf.TemporalFrame( + Time("2010-01-01T00:00"), name="time", unit=u.s, axes_order=time_axes_order + ) # Values from data/acs.hdr: crpix = (12, 13) crval = (5.63, -72.05) - cd = [[1.291E-05, 5.9532E-06], [5.02215E-06, -1.2645E-05]] - aff = models.AffineTransformation2D(matrix=cd, name='rotation') - offx = models.Shift(-crpix[0], name='x_translation') - offy = models.Shift(-crpix[1], name='y_translation') + cd = [[1.291e-05, 5.9532e-06], [5.02215e-06, -1.2645e-05]] + aff = models.AffineTransformation2D(matrix=cd, name="rotation") + offx = models.Shift(-crpix[0], name="x_translation") + offy = models.Shift(-crpix[1], name="y_translation") wcslin = models.Mapping((1, 0)) | (offx & offy) | aff - tan = models.Pix2Sky_TAN(name='tangent_projection') - n2c = models.RotateNative2Celestial(*crval, 180, name='sky_rotation') + tan = models.Pix2Sky_TAN(name="tangent_projection") + n2c = models.RotateNative2Celestial(*crval, 180, name="sky_rotation") cel_model = wcslin | tan | n2c - icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', - axes_order=cel_axes_order) + icrs = cf.CelestialFrame( + reference_frame=coord.ICRS(), name="sky", axes_order=cel_axes_order + ) wcs_forward = (cel_model & time_model) | models.Mapping(axes_order) - comp_frm = cf.CompositeFrame(frames=[icrs, time_frame], - name='TEST 3D FRAME WITH TIME') + comp_frm = cf.CompositeFrame( + frames=[icrs, time_frame], name="TEST 3D FRAME WITH TIME" + ) - w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, - input_frame=detector_frame) + w = wcs.WCS( + forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame + ) w.bounding_box = tuple((0, k - 1) for k in cube_size) w.pixel_shape = cube_size @@ -466,44 +571,51 @@ def gwcs_7d_complex_mapping(): - includes one frame with 3 input and 4 output axes (1 degenerate), with separable world axes (3, 5) and (0, 6). """ - offx = models.Shift(-64, name='x_translation') - offy = models.Shift(-32, name='y_translation') + offx = models.Shift(-64, name="x_translation") + offy = models.Shift(-32, name="y_translation") cd = np.array([[1.2906, 0.59532], [0.50222, -1.2645]]) - aff = models.AffineTransformation2D(matrix=1e-5 * cd, name='rotation') - aff2 = models.AffineTransformation2D(matrix=cd, name='rotation2') + aff = models.AffineTransformation2D(matrix=1e-5 * cd, name="rotation") + aff2 = models.AffineTransformation2D(matrix=cd, name="rotation2") wcslin = (offx & offy) | aff - tan = models.Pix2Sky_TAN(name='tangent_projection') - n2c = models.RotateNative2Celestial(5.630568, -72.0546, 180, name='skyrot') - icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_order=(2, 1)) - spec = cf.SpectralFrame(name='wave', unit=[u.m], axes_order=(4,), axes_names=('lambda',)) + tan = models.Pix2Sky_TAN(name="tangent_projection") + n2c = models.RotateNative2Celestial(5.630568, -72.0546, 180, name="skyrot") + icrs = cf.CelestialFrame( + reference_frame=coord.ICRS(), name="sky", axes_order=(2, 1) + ) + spec = cf.SpectralFrame( + name="wave", unit=[u.m], axes_order=(4,), axes_names=("lambda",) + ) cmplx = cf.CoordinateFrame( name="complex", naxes=4, axes_order=(3, 5, 0, 6), - axis_physical_types=(['em.wl', 'em.wl', 'time', 'time']), + axis_physical_types=(["em.wl", "em.wl", "time", "time"]), axes_type=("SPATIAL", "SPATIAL", "TIME", "TIME"), - axes_names=("x", "y", "t", 'tau'), - unit=(u.m, u.m, u.second, u.second) + axes_names=("x", "y", "t", "tau"), + unit=(u.m, u.m, u.second, u.second), ) - comp_frm = cf.CompositeFrame(frames=[icrs, spec, cmplx], name='TEST 7D') - wcs_forward = ((wcslin & models.Shift(-3.14) & models.Scale(2.7) & aff2) | - (tan & models.Identity(1) & models.Identity(1) & models.Identity(2)) | - (n2c & models.Identity(1) & models.Identity(1) & models.Identity(2)) | - models.Mapping((3, 1, 0, 4, 2, 5, 3))) - + comp_frm = cf.CompositeFrame(frames=[icrs, spec, cmplx], name="TEST 7D") + wcs_forward = ( + (wcslin & models.Shift(-3.14) & models.Scale(2.7) & aff2) + | (tan & models.Identity(1) & models.Identity(1) & models.Identity(2)) + | (n2c & models.Identity(1) & models.Identity(1) & models.Identity(2)) + | models.Mapping((3, 1, 0, 4, 2, 5, 3)) + ) detector_frame = cf.CoordinateFrame( - name="detector", naxes=6, + name="detector", + naxes=6, axes_order=(0, 1, 2, 3, 4, 5), axes_type=("pixel", "pixel", "pixel", "pixel", "pixel", "pixel"), - unit=(u.pix, u.pix, u.pix, u.pix, u.pix, u.pix) + unit=(u.pix, u.pix, u.pix, u.pix, u.pix, u.pix), ) # pipeline = [('detector', wcs_forward), (comp_frm, None)] - w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, - input_frame=detector_frame) + w = wcs.WCS( + forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame + ) w.bounding_box = ((0, 15), (0, 31), (0, 20), (0, 10), (0, 10), (0, 1)) w.array_shape = (2, 11, 11, 21, 32, 16) @@ -513,20 +625,26 @@ def gwcs_7d_complex_mapping(): def gwcs_with_pipeline_celestial(): - input_frame = cf.CoordinateFrame(2, ["PIXEL"]*2, - axes_order=list(range(2)), - unit=[u.pix]*2, - name="input") + input_frame = cf.CoordinateFrame( + 2, ["PIXEL"] * 2, axes_order=list(range(2)), unit=[u.pix] * 2, name="input" + ) - spatial = models.Multiply(20*u.arcsec/u.pix) & models.Multiply(15*u.deg/u.pix) + spatial = models.Multiply(20 * u.arcsec / u.pix) & models.Multiply( + 15 * u.deg / u.pix + ) - celestial_frame = cf.CelestialFrame(axes_order=(0, 1), unit=(u.arcsec, u.deg), - reference_frame=coord.ICRS(), name="celestial") + celestial_frame = cf.CelestialFrame( + axes_order=(0, 1), + unit=(u.arcsec, u.deg), + reference_frame=coord.ICRS(), + name="celestial", + ) - custom = models.Shift(1*u.deg) & models.Shift(2*u.deg) + custom = models.Shift(1 * u.deg) & models.Shift(2 * u.deg) - output_frame = cf.CoordinateFrame(2, ["CUSTOM"]*2, - axes_order=list(range(2)), unit=[u.arcsec]*2, name="output") + output_frame = cf.CoordinateFrame( + 2, ["CUSTOM"] * 2, axes_order=list(range(2)), unit=[u.arcsec] * 2, name="output" + ) pipeline = [ (input_frame, spatial), diff --git a/gwcs/extension.py b/gwcs/extension.py index 3d746955..1001a843 100644 --- a/gwcs/extension.py +++ b/gwcs/extension.py @@ -3,20 +3,24 @@ from asdf.extension import Extension, ManifestExtension from .converters.wcs import ( - CelestialFrameConverter, CompositeFrameConverter, FrameConverter, - Frame2DConverter, SpectralFrameConverter, StepConverter, - StokesFrameConverter, TemporalFrameConverter, WCSConverter, -) -from .converters.selector import ( - LabelMapperConverter, RegionsSelectorConverter + CelestialFrameConverter, + CompositeFrameConverter, + FrameConverter, + Frame2DConverter, + SpectralFrameConverter, + StepConverter, + StokesFrameConverter, + TemporalFrameConverter, + WCSConverter, ) +from .converters.selector import LabelMapperConverter, RegionsSelectorConverter from .converters.spectroscopy import ( - GratingEquationConverter, SellmeierGlassConverter, SellmeierZemaxConverter, - Snell3DConverter -) -from .converters.geometry import ( - DirectionCosinesConverter, SphericalCartesianConverter + GratingEquationConverter, + SellmeierGlassConverter, + SellmeierZemaxConverter, + Snell3DConverter, ) +from .converters.geometry import DirectionCosinesConverter, SphericalCartesianConverter WCS_MODEL_CONVERTERS = [ @@ -43,7 +47,12 @@ # that occur earlier in the list. WCS_MANIFEST_URIS = [ f"asdf://asdf-format.org/astronomy/gwcs/manifests/{path.stem}" - for path in sorted((importlib.resources.files("asdf_wcs_schemas.resources") / "manifests").iterdir(), reverse=True) + for path in sorted( + ( + importlib.resources.files("asdf_wcs_schemas.resources") / "manifests" + ).iterdir(), + reverse=True, + ) ] # 1.0.0 contains multiple versions of the same tag, a bug fixed in @@ -55,7 +64,7 @@ converters=WCS_MODEL_CONVERTERS, ) for uri in WCS_MANIFEST_URIS - if len(WCS_MANIFEST_URIS) == 1 or '1.0.0' not in uri + if len(WCS_MANIFEST_URIS) == 1 or "1.0.0" not in uri ] # if we don't register something for the 1.0.0 extension/manifest @@ -66,9 +75,10 @@ # extension for 1.0.0 which doesn't support any tags or types # but will be installed into asdf preventing the warning if len(TRANSFORM_EXTENSIONS) > 1: + class _EmptyExtension(Extension): - extension_uri = 'asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.0.0' - legacy_class_names=["gwcs.extension.GWCSExtension"] + extension_uri = "asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.0.0" + legacy_class_names = ["gwcs.extension.GWCSExtension"] TRANSFORM_EXTENSIONS.append(_EmptyExtension()) diff --git a/gwcs/geometry.py b/gwcs/geometry.py index 0cd61f3b..2fdada0e 100644 --- a/gwcs/geometry.py +++ b/gwcs/geometry.py @@ -9,14 +9,19 @@ from astropy import units as u -__all__ = ['ToDirectionCosines', 'FromDirectionCosines', - 'SphericalToCartesian', 'CartesianToSpherical'] +__all__ = [ + "ToDirectionCosines", + "FromDirectionCosines", + "SphericalToCartesian", + "CartesianToSpherical", +] class ToDirectionCosines(Model): """ Transform a vector to direction cosines. """ + _separable = False n_inputs = 3 @@ -24,14 +29,14 @@ class ToDirectionCosines(Model): def __init__(self, **kwargs): super().__init__(**kwargs) - self.inputs = ('x', 'y', 'z') - self.outputs = ('cosa', 'cosb', 'cosc', 'length') + self.inputs = ("x", "y", "z") + self.outputs = ("cosa", "cosb", "cosc", "length") def evaluate(self, x, y, z): - vabs = np.sqrt(1. + x**2 + y**2) + vabs = np.sqrt(1.0 + x**2 + y**2) cosa = x / vabs cosb = y / vabs - cosc = 1. / vabs + cosc = 1.0 / vabs return cosa, cosb, cosc, vabs def inverse(self): @@ -42,6 +47,7 @@ class FromDirectionCosines(Model): """ Transform directional cosines to vector. """ + _separable = False n_inputs = 4 @@ -49,8 +55,8 @@ class FromDirectionCosines(Model): def __init__(self, **kwargs): super().__init__(**kwargs) - self.inputs = ('cosa', 'cosb', 'cosc', 'length') - self.outputs = ('x', 'y', 'z') + self.inputs = ("cosa", "cosb", "cosc", "length") + self.outputs = ("x", "y", "z") def evaluate(self, cosa, cosb, cosc, length): return cosa * length, cosb * length, cosc * length @@ -68,6 +74,7 @@ class SphericalToCartesian(Model): (or *elevation angle*) in range ``[-90, 90]``. """ + _separable = False _input_units_allow_dimensionless = True @@ -88,13 +95,13 @@ def __init__(self, wrap_lon_at=360, **kwargs): """ super().__init__(**kwargs) - self.inputs = ('lon', 'lat') - self.outputs = ('x', 'y', 'z') + self.inputs = ("lon", "lat") + self.outputs = ("x", "y", "z") self.wrap_lon_at = wrap_lon_at @property def wrap_lon_at(self): - """ An **integer number** that specifies the range of the longitude + """An **integer number** that specifies the range of the longitude (azimuthal) angle. Allowed values are 180 and 360. When ``wrap_lon_at`` @@ -113,8 +120,9 @@ def wrap_lon_at(self, wrap_angle): def evaluate(self, lon, lat): if isinstance(lon, u.Quantity) != isinstance(lat, u.Quantity): - raise TypeError("All arguments must be of the same type " - "(i.e., quantity or not).") + raise TypeError( + "All arguments must be of the same type " "(i.e., quantity or not)." + ) lon = np.deg2rad(lon) lat = np.deg2rad(lat) @@ -131,7 +139,7 @@ def inverse(self): @property def input_units(self): - return {'lon': u.deg, 'lat': u.deg} + return {"lon": u.deg, "lat": u.deg} class CartesianToSpherical(Model): @@ -145,6 +153,7 @@ class CartesianToSpherical(Model): range ``[-90, 90]``. """ + _separable = False _input_units_allow_dimensionless = True @@ -165,13 +174,13 @@ def __init__(self, wrap_lon_at=360, **kwargs): """ super().__init__(**kwargs) - self.inputs = ('x', 'y', 'z') - self.outputs = ('lon', 'lat') + self.inputs = ("x", "y", "z") + self.outputs = ("lon", "lat") self.wrap_lon_at = wrap_lon_at @property def wrap_lon_at(self): - """ An **integer number** that specifies the range of the longitude + """An **integer number** that specifies the range of the longitude (azimuthal) angle. Allowed values are 180 and 360. When ``wrap_lon_at`` @@ -191,8 +200,9 @@ def wrap_lon_at(self, wrap_angle): def evaluate(self, x, y, z): nquant = [isinstance(i, u.Quantity) for i in (x, y, z)].count(True) if nquant in [1, 2]: - raise TypeError("All arguments must be of the same type " - "(i.e., quantity or not).") + raise TypeError( + "All arguments must be of the same type " "(i.e., quantity or not)." + ) h = np.hypot(x, y) lat = np.rad2deg(np.arctan2(z, h)) @@ -200,7 +210,9 @@ def evaluate(self, x, y, z): lon[h == 0] *= 0 if self._wrap_lon_at != 180: - lon = np.mod(lon, 360.0 * u.deg if nquant else 360.0, where=np.isfinite(lon), out=lon) + lon = np.mod( + lon, 360.0 * u.deg if nquant else 360.0, where=np.isfinite(lon), out=lon + ) return lon, lat diff --git a/gwcs/region.py b/gwcs/region.py index e6013355..ea71a9b4 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -12,10 +12,11 @@ from collections import OrderedDict import numpy as np -__all__ = ['Region', 'Edge', 'Polygon'] +__all__ = ["Region", "Edge", "Polygon"] _INTERSECT_ATOL = 1e2 * np.finfo(float).eps + class Region: """ Base class for regions. diff --git a/gwcs/selector.py b/gwcs/selector.py index d20cca95..54bcad03 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -65,6 +65,7 @@ """ + import warnings import numpy as np from astropy.modeling.core import Model @@ -74,8 +75,13 @@ from .utils import RegionError, _toindex -__all__ = ['LabelMapperArray', 'LabelMapperDict', 'LabelMapperRange', 'RegionsSelector', - 'LabelMapper'] +__all__ = [ + "LabelMapperArray", + "LabelMapperDict", + "LabelMapperRange", + "RegionsSelector", + "LabelMapper", +] def get_unique_regions(regions): @@ -85,7 +91,7 @@ def get_unique_regions(regions): try: unique_regions.remove(0) - unique_regions.remove('') + unique_regions.remove("") except ValueError: pass try: @@ -127,6 +133,7 @@ class _LabelMapper(Model): name : str The name of this transform. """ + def __init__(self, mapper, no_label, inputs_mapping=None, name=None, **kwargs): self._no_label = no_label self._inputs_mapping = inputs_mapping @@ -150,7 +157,6 @@ def evaluate(self, *args): class LabelMapperArray(_LabelMapper): - """ Maps array locations to labels. @@ -187,8 +193,8 @@ def __init__(self, mapper, inputs_mapping=None, name=None, **kwargs): else: _no_label = "" super(LabelMapperArray, self).__init__(mapper, _no_label, name=name, **kwargs) - self.inputs = ('x', 'y') - self.outputs = ('label',) + self.inputs = ("x", "y") + self.outputs = ("label",) def evaluate(self, *args): args = tuple([_toindex(a) for a in args]) @@ -245,7 +251,6 @@ def from_vertices(cls, shape, regions): class LabelMapperDict(_LabelMapper): - """ Maps a number to a transform, which when evaluated returns a label. @@ -269,6 +274,7 @@ class LabelMapperDict(_LabelMapper): name : str The name of this transform. """ + standard_broadcasting = False linear = False @@ -276,7 +282,9 @@ class LabelMapperDict(_LabelMapper): n_outputs = 1 - def __init__(self, inputs, mapper, inputs_mapping=None, atol=10**-8, name=None, **kwargs): + def __init__( + self, inputs, mapper, inputs_mapping=None, atol=10**-8, name=None, **kwargs + ): self._atol = atol _no_label = 0 self._inputs = inputs @@ -285,9 +293,10 @@ def __init__(self, inputs, mapper, inputs_mapping=None, atol=10**-8, name=None, raise TypeError("All transforms in mapper must have one output.") self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} - super(LabelMapperDict, self).__init__(mapper, _no_label, inputs_mapping, - name=name, **kwargs) - self.outputs = ('labels',) + super(LabelMapperDict, self).__init__( + mapper, _no_label, inputs_mapping, name=name, **kwargs + ) + self.outputs = ("labels",) @property def n_inputs(self): @@ -342,7 +351,6 @@ def evaluate(self, *args): class LabelMapperRange(_LabelMapper): - """ The structure this class uses maps a range of values to a transform. Given an input value it finds the range the value falls in and returns @@ -367,6 +375,7 @@ class LabelMapperRange(_LabelMapper): name : str The name of this transform. """ + standard_broadcasting = False n_outputs = 1 @@ -384,9 +393,10 @@ def __init__(self, inputs, mapper, inputs_mapping=None, name=None, **kwargs): raise TypeError("All transforms in mapper must have one output.") self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} - super(LabelMapperRange, self).__init__(mapper, _no_label, inputs_mapping, - name=name, **kwargs) - self.outputs = ('labels',) + super(LabelMapperRange, self).__init__( + mapper, _no_label, inputs_mapping, name=name, **kwargs + ) + self.outputs = ("labels",) @property def n_inputs(self): @@ -471,9 +481,9 @@ def evaluate(self, *args): for val_range in value_ranges: temp = keys.copy() temp[nan_ind] = np.nan - temp = np.where(np.logical_or(temp <= val_range[0], - temp >= val_range[1]), - np.nan, temp) + temp = np.where( + np.logical_or(temp <= val_range[0], temp >= val_range[1]), np.nan, temp + ) ind = ~np.isnan(temp) if ind.any(): @@ -483,12 +493,13 @@ def evaluate(self, *args): continue res.shape = shape if len(np.nonzero(res)[0]) == 0: - warnings.warn("All data is outside the valid range - {0}.".format(self.name)) + warnings.warn( + "All data is outside the valid range - {0}.".format(self.name) + ) return res class RegionsSelector(Model): - """ This model defines discontinuous transforms. It maps inputs to their corresponding transforms. @@ -512,13 +523,22 @@ class RegionsSelector(Model): name : str The name of this transform. """ + standard_broadcasting = False linear = False fittable = False - def __init__(self, inputs, outputs, selector, label_mapper, undefined_transform_value=np.nan, - name=None, **kwargs): + def __init__( + self, + inputs, + outputs, + selector, + label_mapper, + undefined_transform_value=np.nan, + name=None, + **kwargs, + ): self._inputs = inputs self._outputs = outputs self._n_inputs = len(inputs) @@ -544,7 +564,9 @@ def uses_quantity(self): elif not_all_uses_quantity: return False else: - raise ValueError("You can not mix models which use quantity and do not use quantity inside a RegionSelector") + raise ValueError( + "You can not mix models which use quantity and do not use quantity inside a RegionSelector" + ) def set_input(self, rid): """ @@ -562,13 +584,18 @@ def inverse(self): for rid in self._selector: transforms_inv[rid] = self._selector[rid].inverse except AttributeError: - raise NotImplementedError("The inverse of all regions must be defined" - "for RegionsSelector to have an inverse.") - return self.__class__(self.outputs, self.inputs, transforms_inv, - self.label_mapper.inverse) + raise NotImplementedError( + "The inverse of all regions must be defined" + "for RegionsSelector to have an inverse." + ) + return self.__class__( + self.outputs, self.inputs, transforms_inv, self.label_mapper.inverse + ) else: - raise NotImplementedError("The label mapper must have an inverse " - "for RegionsSelector to have an inverse.") + raise NotImplementedError( + "The label mapper must have an inverse " + "for RegionsSelector to have an inverse." + ) def evaluate(self, *args): """ @@ -596,14 +623,16 @@ def evaluate(self, *args): uniq = get_unique_regions(rids) for rid in uniq: - ind = (rids == rid) + ind = rids == rid inputs = [a[ind] for a in args] if rid in self._selector: result = self._selector[rid](*inputs) else: # If there's no transform for a label, return np.nan - result = [np.empty(inputs[0].shape) + - self._undefined_transform_value for i in range(self.n_outputs)] + result = [ + np.empty(inputs[0].shape) + self._undefined_transform_value + for i in range(self.n_outputs) + ] for j in range(self.n_outputs): outputs[j][ind] = result[j] return outputs @@ -679,22 +708,30 @@ class LabelMapper(_LabelMapper): n_outputs = 1 - def __init__(self, inputs, mapper, no_label=np.nan, inputs_mapping=None, name=None, **kwargs): + def __init__( + self, inputs, mapper, no_label=np.nan, inputs_mapping=None, name=None, **kwargs + ): self._no_label = no_label self._inputs = inputs self._n_inputs = len(inputs) - self._outputs = tuple(['x{0}'.format(ind) for ind in list(range(mapper.n_outputs))]) + self._outputs = tuple( + ["x{0}".format(ind) for ind in list(range(mapper.n_outputs))] + ) if isinstance(inputs_mapping, tuple): inputs_mapping = astmodels.Mapping(inputs_mapping) - elif inputs_mapping is not None and not isinstance(inputs_mapping, astmodels.Mapping): - raise TypeError("inputs_mapping must be an instance of astropy.modeling.Mapping.") + elif inputs_mapping is not None and not isinstance( + inputs_mapping, astmodels.Mapping + ): + raise TypeError( + "inputs_mapping must be an instance of astropy.modeling.Mapping." + ) self._inputs_mapping = inputs_mapping self._mapper = mapper self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} super(_LabelMapper, self).__init__(name=name, **kwargs) - self.outputs = ('label',) + self.outputs = ("label",) @property def inputs(self): diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index 2d8725f4..b963f9de 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -9,12 +9,17 @@ import astropy.units as u -__all__ = ['WavelengthFromGratingEquation', 'AnglesFromGratingEquation3D', - 'Snell3D', 'SellmeierGlass', 'SellmeierZemax'] +__all__ = [ + "WavelengthFromGratingEquation", + "AnglesFromGratingEquation3D", + "Snell3D", + "SellmeierGlass", + "SellmeierZemax", +] class WavelengthFromGratingEquation(Model): - r""" Solve the Grating Dispersion Law for the wavelength. + r"""Solve the Grating Dispersion Law for the wavelength. .. Note:: This form of the equation can be used for paraxial (small angle approximation) as well as oblique incident angles. @@ -59,8 +64,9 @@ class WavelengthFromGratingEquation(Model): """ Spectral order.""" def __init__(self, groove_density, spectral_order, **kwargs): - super().__init__(groove_density=groove_density, - spectral_order=spectral_order, **kwargs) + super().__init__( + groove_density=groove_density, spectral_order=spectral_order, **kwargs + ) self.inputs = ("alpha_in", "alpha_out") """ Sine function of the angles or the direction cosines.""" self.outputs = ("wavelength",) @@ -73,7 +79,7 @@ def evaluate(self, alpha_in, alpha_out, groove_density, spectral_order): def return_units(self): if self.groove_density.unit is None: return None - return {'wavelength': u.Unit(1 / self.groove_density.unit)} + return {"wavelength": u.Unit(1 / self.groove_density.unit)} class AnglesFromGratingEquation3D(Model): @@ -114,16 +120,16 @@ class AnglesFromGratingEquation3D(Model): """ Spectral order.""" def __init__(self, groove_density, spectral_order, **kwargs): - super().__init__(groove_density=groove_density, - spectral_order=spectral_order, **kwargs) + super().__init__( + groove_density=groove_density, spectral_order=spectral_order, **kwargs + ) self.inputs = ("wavelength", "alpha_in", "beta_in") """ Wavelength and 2 angle coordinates going into the grating.""" self.outputs = ("alpha_out", "beta_out", "gamma_out") """ Two angles coming out of the grating. """ - def evaluate(self, wavelength, alpha_in, beta_in, - groove_density, spectral_order): + def evaluate(self, wavelength, alpha_in, beta_in, groove_density, spectral_order): if alpha_in.shape != beta_in.shape: raise ValueError("Expected input arrays to have the same shape.") @@ -132,8 +138,8 @@ def evaluate(self, wavelength, alpha_in, beta_in, beta_in = u.Quantity(beta_in) alpha_out = -groove_density * spectral_order * wavelength + alpha_in - beta_out = - beta_in - gamma_out = np.sqrt(1 - alpha_out ** 2 - beta_out ** 2) + beta_out = -beta_in + gamma_out = np.sqrt(1 - alpha_out**2 - beta_out**2) return alpha_out, beta_out, gamma_out @property @@ -141,9 +147,9 @@ def input_units(self): if self.groove_density.unit is None: return None return { - 'wavelength': 1 / self.groove_density.unit, - 'alpha_in': u.Unit(1), - 'beta_in': u.Unit(1) + "wavelength": 1 / self.groove_density.unit, + "alpha_in": u.Unit(1), + "beta_in": u.Unit(1), } @@ -158,6 +164,7 @@ class Snell3D(Model): alpha_out, beta_out, gamma_out : float Direction cosines. """ + _separable = False linear = False @@ -166,8 +173,8 @@ class Snell3D(Model): def __init__(self, **kwargs): super().__init__(**kwargs) - self.inputs = ('n', 'alpha_in', 'beta_in', 'gamma_in') - self.outputs = ('alpha_out', 'beta_out', 'gamma_out') + self.inputs = ("n", "alpha_in", "beta_in", "gamma_in") + self.outputs = ("alpha_out", "beta_out", "gamma_out") @staticmethod def evaluate(n, alpha_in, beta_in, gamma_in): @@ -221,6 +228,7 @@ class SellmeierGlass(Model): \\frac{(B3 * \\lambda^2 )}{(\\lambda^2 - C3)} """ + _separable = False standard_broadcasting = False linear = False @@ -235,26 +243,27 @@ class SellmeierGlass(Model): def __init__(self, B_coef, C_coef, **kwargs): super().__init__(B_coef, C_coef) - self.inputs = ('wavelength',) - self.outputs = ('n',) + self.inputs = ("wavelength",) + self.outputs = ("n",) @staticmethod def evaluate(wavelength, B_coef, C_coef): B1, B2, B3 = B_coef[0] C1, C2, C3 = C_coef[0] - n = np.sqrt(1. + - B1 * wavelength ** 2 / (wavelength ** 2 - C1) + - B2 * wavelength ** 2 / (wavelength ** 2 - C2) + - B3 * wavelength ** 2 / (wavelength ** 2 - C3) - ) + n = np.sqrt( + 1.0 + + B1 * wavelength**2 / (wavelength**2 - C1) + + B2 * wavelength**2 / (wavelength**2 - C2) + + B3 * wavelength**2 / (wavelength**2 - C3) + ) return n @property def input_units(self): if self.C_coef.unit is None: return None - return {'wavelength': u.um} + return {"wavelength": u.um} class SellmeierZemax(Model): @@ -289,6 +298,7 @@ class SellmeierZemax(Model): Refractive index. """ + _separable = False standard_broadcasting = False linear = False @@ -305,18 +315,44 @@ class SellmeierZemax(Model): D_coef = Parameter(default=[0, 0, 0]) E_coef = Parameter(default=[1, 1, 1]) - def __init__(self, temperature=temperature, ref_temperature=ref_temperature, - ref_pressure=ref_pressure, pressure=pressure, B_coef=B_coef, - C_coef=C_coef, D_coef=D_coef, E_coef=E_coef, **kwargs): - - super().__init__(temperature=temperature, ref_temperature=ref_temperature, - ref_pressure=ref_pressure, pressure=pressure, B_coef=B_coef, - C_coef=C_coef, D_coef=D_coef, E_coef=E_coef, **kwargs) - self.inputs = ('wavelength',) - self.outputs = ('n',) - - def evaluate(self, wavelength, temp, ref_temp, ref_pressure, - pressure, B_coef, C_coef, D_coef, E_coef): + def __init__( + self, + temperature=temperature, + ref_temperature=ref_temperature, + ref_pressure=ref_pressure, + pressure=pressure, + B_coef=B_coef, + C_coef=C_coef, + D_coef=D_coef, + E_coef=E_coef, + **kwargs, + ): + super().__init__( + temperature=temperature, + ref_temperature=ref_temperature, + ref_pressure=ref_pressure, + pressure=pressure, + B_coef=B_coef, + C_coef=C_coef, + D_coef=D_coef, + E_coef=E_coef, + **kwargs, + ) + self.inputs = ("wavelength",) + self.outputs = ("n",) + + def evaluate( + self, + wavelength, + temp, + ref_temp, + ref_pressure, + pressure, + B_coef, + C_coef, + D_coef, + E_coef, + ): """ Input ``wavelength`` is in units of microns. """ @@ -331,12 +367,20 @@ def evaluate(self, wavelength, temp, ref_temp, ref_pressure, D0, D1, D2 = D_coef[0] E0, E1, lam_tk = E_coef[0] - nref = 1. + (6432.8 + 2949810. * wavelength ** 2 / - (146.0 * wavelength ** 2 - 1.) + (5540.0 * wavelength ** 2) / - (41.0 * wavelength ** 2 - 1.)) * 1e-8 + nref = ( + 1.0 + + ( + 6432.8 + + 2949810.0 * wavelength**2 / (146.0 * wavelength**2 - 1.0) + + (5540.0 * wavelength**2) / (41.0 * wavelength**2 - 1.0) + ) + * 1e-8 + ) # T should be in C, P should be in ATM - nair_obs = 1.0 + ((nref - 1.0) * pressure) / (1.0 + (temp - 15.) * 3.4785e-3) - nair_ref = 1.0 + ((nref - 1.0) * ref_pressure) / (1.0 + (ref_temp - 15) * 3.4785e-3) + nair_obs = 1.0 + ((nref - 1.0) * pressure) / (1.0 + (temp - 15.0) * 3.4785e-3) + nair_ref = 1.0 + ((nref - 1.0) * ref_pressure) / ( + 1.0 + (ref_temp - 15) * 3.4785e-3 + ) # Compute the relative index of the glass at Tref and Pref using Sellmeier equation I. lamrel = wavelength * nair_obs / nair_ref @@ -346,9 +390,12 @@ def evaluate(self, wavelength, temp, ref_temp, ref_pressure, nabs_ref = nrel * nair_ref # Compute the absolute index of the glass - delnabs = (0.5 * (nrel ** 2 - 1.) / nrel) * \ - (D0 * delta + D1 * delta ** 2 + D2 * delta ** 3 + \ - (E0 * delta + E1 * delta ** 2) / (lamrel ** 2 - lam_tk ** 2)) + delnabs = (0.5 * (nrel**2 - 1.0) / nrel) * ( + D0 * delta + + D1 * delta**2 + + D2 * delta**3 + + (E0 * delta + E1 * delta**2) / (lamrel**2 - lam_tk**2) + ) nabs_obs = nabs_ref + delnabs # Define the relative index at the system's operating T and P. diff --git a/gwcs/tests/conftest.py b/gwcs/tests/conftest.py index 48d09272..e21ebce6 100644 --- a/gwcs/tests/conftest.py +++ b/gwcs/tests/conftest.py @@ -1,6 +1,7 @@ """ This file contains a set of pytest fixtures which are different gwcses for testing. """ + import pytest from .. import examples @@ -31,6 +32,7 @@ def gwcs_1d_freq(): def gwcs_3d_spatial_wave(): return examples.gwcs_3d_spatial_wave() + @pytest.fixture def gwcs_2d_shift_scale(): return examples.gwcs_2d_shift_scale() @@ -93,9 +95,9 @@ def sellmeier_zemax(): @pytest.fixture(scope="function") def gwcs_3d_galactic_spectral(): - return examples.gwcs_3d_galactic_spectral() + @pytest.fixture(scope="function") def gwcs_1d_spectral(): return examples.gwcs_1d_spectral() @@ -111,8 +113,10 @@ def gwcs_spec_cel_time_4d(): params=[ (2, 1, 0), (2, 0, 1), - pytest.param((1, 0, 2), marks=pytest.mark.skip(reason="Fails round-trip for -TAB axis 3")), - ] + pytest.param( + (1, 0, 2), marks=pytest.mark.skip(reason="Fails round-trip for -TAB axis 3") + ), + ], ) def gwcs_cube_with_separable_spectral(request): axes_order = request.param @@ -124,9 +128,13 @@ def gwcs_cube_with_separable_spectral(request): params=[ (2, 0, 1), (2, 1, 0), - pytest.param((0, 2, 1), marks=pytest.mark.skip(reason="Fails round-trip for -TAB axis 2")), - pytest.param((1, 0, 2), marks=pytest.mark.skip(reason="Fails round-trip for -TAB axis 3")), - ] + pytest.param( + (0, 2, 1), marks=pytest.mark.skip(reason="Fails round-trip for -TAB axis 2") + ), + pytest.param( + (1, 0, 2), marks=pytest.mark.skip(reason="Fails round-trip for -TAB axis 3") + ), + ], ) def gwcs_cube_with_separable_time(request): axes_order = request.param diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index dffc1dea..b1a818ae 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -2,6 +2,7 @@ """ Tests the API defined in astropy APE 14 (https://doi.org/10.5281/zenodo.1188875). """ + import numpy as np import pytest from numpy.testing import assert_allclose, assert_array_equal @@ -21,7 +22,7 @@ def wcsobj(request): return request.getfixturevalue(request.param) -wcs_objs = pytest.mark.parametrize("wcsobj", ['gwcs_2d_spatial_shift'], indirect=True) +wcs_objs = pytest.mark.parametrize("wcsobj", ["gwcs_2d_spatial_shift"], indirect=True) @pytest.fixture @@ -29,35 +30,55 @@ def wcs_ndim_types_units(request): """ Generate a wcs and the expected ndim, types, and units. """ - ndim = {'gwcs_2d_spatial_shift': (2, 2), - 'gwcs_2d_spatial_reordered': (2, 2), - 'gwcs_1d_freq': (1, 1), - 'gwcs_3d_spatial_wave': (3, 3), - 'gwcs_4d_identity_units': (4, 4)} - types = {'gwcs_2d_spatial_shift': ("pos.eq.ra", "pos.eq.dec"), - 'gwcs_2d_spatial_reordered': ("pos.eq.dec", "pos.eq.ra"), - 'gwcs_1d_freq': ("em.freq",), - 'gwcs_3d_spatial_wave': ("pos.eq.ra", "pos.eq.dec", "em.wl"), - 'gwcs_4d_identity_units': ("pos.eq.ra", "pos.eq.dec", "em.wl", "time")} - units = {'gwcs_2d_spatial_shift': ("deg", "deg"), - 'gwcs_2d_spatial_reordered': ("deg", "deg"), - 'gwcs_1d_freq': ("Hz",), - 'gwcs_3d_spatial_wave': ("deg", "deg", "m"), - 'gwcs_4d_identity_units': ("deg", "deg", "nm", "s")} - - return (request.getfixturevalue(request.param), - ndim[request.param], - types[request.param], - units[request.param]) + ndim = { + "gwcs_2d_spatial_shift": (2, 2), + "gwcs_2d_spatial_reordered": (2, 2), + "gwcs_1d_freq": (1, 1), + "gwcs_3d_spatial_wave": (3, 3), + "gwcs_4d_identity_units": (4, 4), + } + types = { + "gwcs_2d_spatial_shift": ("pos.eq.ra", "pos.eq.dec"), + "gwcs_2d_spatial_reordered": ("pos.eq.dec", "pos.eq.ra"), + "gwcs_1d_freq": ("em.freq",), + "gwcs_3d_spatial_wave": ("pos.eq.ra", "pos.eq.dec", "em.wl"), + "gwcs_4d_identity_units": ("pos.eq.ra", "pos.eq.dec", "em.wl", "time"), + } + units = { + "gwcs_2d_spatial_shift": ("deg", "deg"), + "gwcs_2d_spatial_reordered": ("deg", "deg"), + "gwcs_1d_freq": ("Hz",), + "gwcs_3d_spatial_wave": ("deg", "deg", "m"), + "gwcs_4d_identity_units": ("deg", "deg", "nm", "s"), + } + + return ( + request.getfixturevalue(request.param), + ndim[request.param], + types[request.param], + units[request.param], + ) # # x, y inputs - scalar and array x, y = 1, 2 xarr, yarr = np.ones((3, 4)), np.ones((3, 4)) + 1 -fixture_names = ['gwcs_2d_spatial_shift', 'gwcs_2d_spatial_reordered', 'gwcs_1d_freq', 'gwcs_3d_spatial_wave', 'gwcs_4d_identity_units'] -fixture_wcs_ndim_types_units = pytest.mark.parametrize("wcs_ndim_types_units", fixture_names, indirect=True) -all_wcses_names = fixture_names + ['gwcs_3d_identity_units', 'gwcs_stokes_lookup', 'gwcs_3d_galactic_spectral'] +fixture_names = [ + "gwcs_2d_spatial_shift", + "gwcs_2d_spatial_reordered", + "gwcs_1d_freq", + "gwcs_3d_spatial_wave", + "gwcs_4d_identity_units", +] +fixture_wcs_ndim_types_units = pytest.mark.parametrize( + "wcs_ndim_types_units", fixture_names, indirect=True +) +all_wcses_names = fixture_names + [ + "gwcs_3d_identity_units", + "gwcs_stokes_lookup", + "gwcs_3d_galactic_spectral", +] fixture_all_wcses = pytest.mark.parametrize("wcsobj", all_wcses_names, indirect=True) @@ -80,7 +101,11 @@ def test_names(wcsobj): def test_names_split(gwcs_3d_galactic_spectral): wcs = gwcs_3d_galactic_spectral - assert wcs.world_axis_names == wcs.output_frame.axes_names == ("Latitude", "Frequency", "Longitude") + assert ( + wcs.world_axis_names + == wcs.output_frame.axes_names + == ("Latitude", "Frequency", "Longitude") + ) @fixture_wcs_ndim_types_units @@ -117,7 +142,7 @@ def test_pixel_to_world_values(gwcs_2d_spatial_shift, x, y): def test_pixel_to_world_values_units_2d(gwcs_2d_shift_scale_quantity, x, y): wcsobj = gwcs_2d_shift_scale_quantity - call_pixel = x*u.pix, y*u.pix + call_pixel = x * u.pix, y * u.pix api_pixel = x, y call_world = wcsobj(*call_pixel) @@ -169,21 +194,20 @@ def test_array_index_to_world_values(gwcs_2d_spatial_shift, x, y): def test_world_axis_object_components_2d(gwcs_2d_spatial_shift): waoc = gwcs_2d_spatial_shift.world_axis_object_components - assert waoc[0][:2] == ('celestial', 0) + assert waoc[0][:2] == ("celestial", 0) assert callable(waoc[0][2]) - assert waoc[1][:2] == ('celestial', 1) + assert waoc[1][:2] == ("celestial", 1) assert callable(waoc[1][2]) def test_world_axis_object_components_2d_generic(gwcs_2d_quantity_shift): waoc = gwcs_2d_quantity_shift.world_axis_object_components - assert waoc == [('SPATIAL', 0, 'value'), - ('SPATIAL1', 0, 'value')] + assert waoc == [("SPATIAL", 0, "value"), ("SPATIAL1", 0, "value")] def test_world_axis_object_components_1d(gwcs_1d_freq): waoc = gwcs_1d_freq.world_axis_object_components - assert [c[:2] for c in waoc] == [('spectral', 0)] + assert [c[:2] for c in waoc] == [("spectral", 0)] assert callable(waoc[0][2]) @@ -191,64 +215,74 @@ def test_world_axis_object_components_4d(gwcs_4d_identity_units): waoc = gwcs_4d_identity_units.world_axis_object_components first_two = [c[:2] for c in waoc] last_one = [c[2] for c in waoc] - assert first_two == [('celestial', 0), - ('celestial', 1), - ('spectral', 0), - ('temporal', 0)] + assert first_two == [ + ("celestial", 0), + ("celestial", 1), + ("spectral", 0), + ("temporal", 0), + ] assert all([callable(last) for last in last_one]) def test_world_axis_object_classes_2d(gwcs_2d_spatial_shift): waoc = gwcs_2d_spatial_shift.world_axis_object_classes - assert waoc['celestial'][0] is coord.SkyCoord - assert waoc['celestial'][1] == tuple() - assert 'frame' in waoc['celestial'][2] - assert 'unit' in waoc['celestial'][2] - assert isinstance(waoc['celestial'][2]['frame'], coord.ICRS) - assert tuple(waoc['celestial'][2]['unit']) == (u.deg, u.deg) + assert waoc["celestial"][0] is coord.SkyCoord + assert waoc["celestial"][1] == tuple() + assert "frame" in waoc["celestial"][2] + assert "unit" in waoc["celestial"][2] + assert isinstance(waoc["celestial"][2]["frame"], coord.ICRS) + assert tuple(waoc["celestial"][2]["unit"]) == (u.deg, u.deg) def test_world_axis_object_classes_2d_generic(gwcs_2d_quantity_shift): waoc = gwcs_2d_quantity_shift.world_axis_object_classes - assert waoc['SPATIAL'][0] is u.Quantity - assert waoc['SPATIAL1'][0] is u.Quantity - assert waoc['SPATIAL'][1] == tuple() - assert waoc['SPATIAL1'][1] == tuple() - assert 'unit' in waoc['SPATIAL'][2] - assert 'unit' in waoc['SPATIAL1'][2] - assert waoc['SPATIAL'][2]['unit'] == u.km - assert waoc['SPATIAL1'][2]['unit'] == u.km + assert waoc["SPATIAL"][0] is u.Quantity + assert waoc["SPATIAL1"][0] is u.Quantity + assert waoc["SPATIAL"][1] == tuple() + assert waoc["SPATIAL1"][1] == tuple() + assert "unit" in waoc["SPATIAL"][2] + assert "unit" in waoc["SPATIAL1"][2] + assert waoc["SPATIAL"][2]["unit"] == u.km + assert waoc["SPATIAL1"][2]["unit"] == u.km def test_world_axis_object_classes_4d(gwcs_4d_identity_units): waoc = gwcs_4d_identity_units.world_axis_object_classes - assert waoc['celestial'][0] is coord.SkyCoord - assert waoc['celestial'][1] == tuple() - assert 'frame' in waoc['celestial'][2] - assert 'unit' in waoc['celestial'][2] - assert isinstance(waoc['celestial'][2]['frame'], coord.ICRS) - assert tuple(waoc['celestial'][2]['unit']) == (u.deg, u.deg) - - temporal = waoc['temporal'] + assert waoc["celestial"][0] is coord.SkyCoord + assert waoc["celestial"][1] == tuple() + assert "frame" in waoc["celestial"][2] + assert "unit" in waoc["celestial"][2] + assert isinstance(waoc["celestial"][2]["frame"], coord.ICRS) + assert tuple(waoc["celestial"][2]["unit"]) == (u.deg, u.deg) + + temporal = waoc["temporal"] assert temporal[0] is time.Time assert temporal[1] == tuple() - assert temporal[2] == {'unit': u.s, - 'format': 'isot', 'scale': 'utc', 'precision': 3, - 'in_subfmt': '*', 'out_subfmt': '*', 'location': None} + assert temporal[2] == { + "unit": u.s, + "format": "isot", + "scale": "utc", + "precision": 3, + "in_subfmt": "*", + "out_subfmt": "*", + "location": None, + } def _compare_frame_output(wc1, wc2): if isinstance(wc1, coord.SkyCoord): assert isinstance(wc1.frame, type(wc2.frame)) assert u.allclose(wc1.spherical.lon, wc2.spherical.lon, equal_nan=True) - assert u.allclose(wc1.spherical.lat, wc2.spherical.lat, equal_nan=True) - assert u.allclose(wc1.spherical.distance, wc2.spherical.distance, equal_nan=True) + assert u.allclose(wc1.spherical.lat, wc2.spherical.lat, equal_nan=True) + assert u.allclose( + wc1.spherical.distance, wc2.spherical.distance, equal_nan=True + ) elif isinstance(wc1, u.Quantity): assert u.allclose(wc1, wc2, equal_nan=True) elif isinstance(wc1, time.Time): - assert u.allclose((wc1 - wc2).to(u.s), 0*u.s) + assert u.allclose((wc1 - wc2).to(u.s), 0 * u.s) elif isinstance(wc1, str): assert wc1 == wc2 @@ -262,7 +296,6 @@ def _compare_frame_output(wc1, wc2): @fixture_all_wcses def test_high_level_wrapper(wcsobj, request): - hlvl = HighLevelWCSWrapper(wcsobj) pixel_input = [3] * wcsobj.pixel_n_dim @@ -295,7 +328,9 @@ def test_high_level_wrapper(wcsobj, request): pix_out2 = (pix_out2,) if wcsobj.forward_transform.uses_quantity: - pix_out2 = tuple(p.to_value(unit) for p, unit in zip(pix_out2, wcsobj.input_frame.unit)) + pix_out2 = tuple( + p.to_value(unit) for p, unit in zip(pix_out2, wcsobj.input_frame.unit) + ) np.testing.assert_allclose(pix_out1, pixel_input) np.testing.assert_allclose(pix_out2, pixel_input) @@ -306,42 +341,47 @@ def test_stokes_wrapper(gwcs_stokes_lookup): pixel_input = [0, 1, 2, 3] - out = hlvl.pixel_to_world(pixel_input*u.pix) + out = hlvl.pixel_to_world(pixel_input * u.pix) - assert list(out) == ['I', 'Q', 'U', 'V'] + assert list(out) == ["I", "Q", "U", "V"] - pixel_input = [[0, 1, 2, 3], - [0, 1, 2, 3], - [0, 1, 2, 3], - [0, 1, 2, 3],] + pixel_input = [ + [0, 1, 2, 3], + [0, 1, 2, 3], + [0, 1, 2, 3], + [0, 1, 2, 3], + ] - out = hlvl.pixel_to_world(pixel_input*u.pix) + out = hlvl.pixel_to_world(pixel_input * u.pix) - expected = coord.StokesCoord([['I', 'Q', 'U', 'V'], - ['I', 'Q', 'U', 'V'], - ['I', 'Q', 'U', 'V'], - ['I', 'Q', 'U', 'V']]) + expected = coord.StokesCoord( + [ + ["I", "Q", "U", "V"], + ["I", "Q", "U", "V"], + ["I", "Q", "U", "V"], + ["I", "Q", "U", "V"], + ] + ) assert (out == expected).all() pixel_input = [-1, 4] - out = hlvl.pixel_to_world(pixel_input*u.pix) + out = hlvl.pixel_to_world(pixel_input * u.pix) assert np.isnan(out.value).all() - pixel_input = [[-1, 4], - [1, 2]] + pixel_input = [[-1, 4], [1, 2]] - out = hlvl.pixel_to_world(pixel_input*u.pix) + out = hlvl.pixel_to_world(pixel_input * u.pix) assert np.isnan(out[0].value).all() - assert (out[1] == ['Q', 'U']).all() + assert (out[1] == ["Q", "U"]).all() - out = hlvl.pixel_to_world(1*u.pix) + out = hlvl.pixel_to_world(1 * u.pix) assert isinstance(out, coord.StokesCoord) - assert out == 'Q' + assert out == "Q" @wcs_objs @@ -417,7 +457,9 @@ def test_pixel_to_world_quantity(gwcs_2d_shift_scale, gwcs_2d_shift_scale_quanti gwcs_2d_shift_scale.pixel_to_world(x * u.Jy, y * u.Jy) -def test_array_index_to_world_quantity(gwcs_2d_shift_scale, gwcs_2d_shift_scale_quantity): +def test_array_index_to_world_quantity( + gwcs_2d_shift_scale, gwcs_2d_shift_scale_quantity +): result0 = gwcs_2d_shift_scale.pixel_to_world(x, y) result1 = gwcs_2d_shift_scale.array_index_to_world(y, x) result2 = gwcs_2d_shift_scale_quantity.array_index_to_world(y, x) @@ -450,7 +492,9 @@ def test_world_to_pixel_quantity(gwcs_2d_shift_scale, gwcs_2d_shift_scale_quanti assert_allclose(result2, (x, y)) -def test_world_to_array_index_quantity(gwcs_2d_shift_scale, gwcs_2d_shift_scale_quantity): +def test_world_to_array_index_quantity( + gwcs_2d_shift_scale, gwcs_2d_shift_scale_quantity +): skycoord = gwcs_2d_shift_scale.pixel_to_world(x, y) result0 = gwcs_2d_shift_scale.world_to_pixel(skycoord) result1 = gwcs_2d_shift_scale.world_to_array_index(skycoord) @@ -483,7 +527,10 @@ def test_world_to_array_index(gwcs_simple_imaging, sky_ra_dec): wcsobj = gwcs_simple_imaging sky, ra, dec = sky_ra_dec - assert_allclose(wcsobj.world_to_array_index(sky), wcsobj.invert(ra * u.deg, dec * u.deg, with_units=False)[::-1]) + assert_allclose( + wcsobj.world_to_array_index(sky), + wcsobj.invert(ra * u.deg, dec * u.deg, with_units=False)[::-1], + ) def test_world_to_pixel_values(gwcs_2d_spatial_shift, sky_ra_dec): @@ -497,8 +544,10 @@ def test_world_to_array_index_values(gwcs_simple_imaging, sky_ra_dec): wcsobj = gwcs_simple_imaging sky, ra, dec = sky_ra_dec - assert_allclose(wcsobj.world_to_array_index_values(ra, dec), - wcsobj.invert(ra * u.deg, dec * u.deg, with_units=False)[::-1]) + assert_allclose( + wcsobj.world_to_array_index_values(ra, dec), + wcsobj.invert(ra * u.deg, dec * u.deg, with_units=False)[::-1], + ) def test_ndim_str_frames(gwcs_with_frames_strings): @@ -506,9 +555,14 @@ def test_ndim_str_frames(gwcs_with_frames_strings): assert wcsobj.pixel_n_dim == 4 assert wcsobj.world_n_dim == 3 + def test_composite_many_base_frame(): - q_frame_1 = cf.CoordinateFrame(name='distance', axes_order=(0,), naxes=1, axes_type="SPATIAL", unit=(u.m,)) - q_frame_2 = cf.CoordinateFrame(name='distance', axes_order=(1,), naxes=1, axes_type="SPATIAL", unit=(u.m,)) + q_frame_1 = cf.CoordinateFrame( + name="distance", axes_order=(0,), naxes=1, axes_type="SPATIAL", unit=(u.m,) + ) + q_frame_2 = cf.CoordinateFrame( + name="distance", axes_order=(1,), naxes=1, axes_type="SPATIAL", unit=(u.m,) + ) frame = cf.CompositeFrame([q_frame_1, q_frame_2]) wao_classes = frame.world_axis_object_classes @@ -523,12 +577,16 @@ def test_composite_many_base_frame(): def test_coordinate_frame_api(): - forward = m.Linear1D(slope=0.1*u.deg/u.pix, intercept=0*u.deg) + forward = m.Linear1D(slope=0.1 * u.deg / u.pix, intercept=0 * u.deg) - output_frame = cf.CoordinateFrame(1, "SPATIAL", (0,), unit=(u.deg,), name="sepframe") + output_frame = cf.CoordinateFrame( + 1, "SPATIAL", (0,), unit=(u.deg,), name="sepframe" + ) input_frame = cf.CoordinateFrame(1, "PIXEL", (0,), unit=(u.pix,)) - wcs = gwcs.WCS(forward_transform=forward, input_frame=input_frame, output_frame=output_frame) + wcs = gwcs.WCS( + forward_transform=forward, input_frame=input_frame, output_frame=output_frame + ) world = wcs.pixel_to_world(0) assert isinstance(world, u.Quantity) @@ -537,7 +595,7 @@ def test_coordinate_frame_api(): assert isinstance(pixel, float) pixel2 = wcs.invert(world) - assert u.allclose(pixel2, 0*u.pix) + assert u.allclose(pixel2, 0 * u.pix) def test_world_axis_object_components_units(gwcs_3d_identity_units): @@ -548,9 +606,11 @@ def test_world_axis_object_components_units(gwcs_3d_identity_units): values = high_level_objects_to_values(*world, low_level_wcs=wcs) - expected_values = [world[0].spherical.lon.to_value(wcs.output_frame.unit[0]), - world[0].spherical.lon.to_value(wcs.output_frame.unit[1]), - world[1].to_value(wcs.output_frame.unit[2])] + expected_values = [ + world[0].spherical.lon.to_value(wcs.output_frame.unit[0]), + world[0].spherical.lon.to_value(wcs.output_frame.unit[1]), + world[1].to_value(wcs.output_frame.unit[2]), + ] assert not any([isinstance(o, u.Quantity) for o in values]) np.testing.assert_allclose(values, expected_values) @@ -559,9 +619,15 @@ def test_world_axis_object_components_units(gwcs_3d_identity_units): def test_mismatched_high_level_types(gwcs_3d_identity_units): wcs = gwcs_3d_identity_units - with pytest.raises(TypeError, match="Invalid types were passed.*(tuple, SpectralCoord).*(SkyCoord, SpectralCoord).*"): - wcs.invert((1*u.deg, 2*u.deg), coord.SpectralCoord(10*u.nm)) + with pytest.raises( + TypeError, + match="Invalid types were passed.*(tuple, SpectralCoord).*(SkyCoord, SpectralCoord).*", + ): + wcs.invert((1 * u.deg, 2 * u.deg), coord.SpectralCoord(10 * u.nm)) # Oh astropy why do you make us do this - with pytest.raises(TypeError, match="Invalid types were passed.*got.*Quantity.*expected.*SpectralCoord.*"): - wcs.invert(coord.SkyCoord(1*u.deg, 2*u.deg), 10*u.nm) + with pytest.raises( + TypeError, + match="Invalid types were passed.*got.*Quantity.*expected.*SpectralCoord.*", + ): + wcs.invert(coord.SkyCoord(1 * u.deg, 2 * u.deg), 10 * u.nm) diff --git a/gwcs/tests/test_api_slicing.py b/gwcs/tests/test_api_slicing.py index b32723b6..71e306ea 100644 --- a/gwcs/tests/test_api_slicing.py +++ b/gwcs/tests/test_api_slicing.py @@ -1,4 +1,3 @@ - import astropy.units as u from astropy.coordinates import Galactic, SkyCoord, SpectralCoord from astropy.wcs.wcsapi import wcs_info_str @@ -38,39 +37,46 @@ def test_no_ellipsis(gwcs_3d_galactic_spectral): def test_ellipsis(gwcs_3d_galactic_spectral): - wcs = SlicedLowLevelWCS(gwcs_3d_galactic_spectral, Ellipsis) assert wcs.pixel_n_dim == 3 assert wcs.world_n_dim == 3 assert wcs.array_shape == (30, 20, 10) assert wcs.pixel_shape == (10, 20, 30) - assert wcs.world_axis_physical_types == ['pos.galactic.lat', 'em.freq', 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'Hz', 'deg'] - - assert_equal(wcs.axis_correlation_matrix, [[True, False, True], [False, True, False], [True, False, True]]) + assert wcs.world_axis_physical_types == [ + "pos.galactic.lat", + "em.freq", + "pos.galactic.lon", + ] + assert wcs.world_axis_units == ["deg", "Hz", "deg"] + + assert_equal( + wcs.axis_correlation_matrix, + [[True, False, True], [False, True, False], [True, False, True]], + ) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('spectral', 0), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) - assert wcs.world_axis_object_classes['spectral'][0] is SpectralCoord - assert wcs.world_axis_object_classes['spectral'][1] == () - assert wcs.world_axis_object_classes['spectral'][2] == {'unit': 'Hz'} + assert wcs.world_axis_object_classes["spectral"][0] is SpectralCoord + assert wcs.world_axis_object_classes["spectral"][1] == () + assert wcs.world_axis_object_classes["spectral"][2] == {"unit": "Hz"} assert_allclose(wcs.pixel_to_world_values(29, 39, 44), (80, 20, 205)) assert_allclose(wcs.array_index_to_world_values(44, 39, 29), (80, 20, 205)) - assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29., 39., 44.)) + assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29.0, 39.0, 44.0)) assert_equal(wcs.world_to_array_index_values(80, 20, 205), (44, 39, 29)) assert str(wcs) == EXPECTED_ELLIPSIS_REPR.strip() @@ -104,34 +110,35 @@ def test_ellipsis(gwcs_3d_galactic_spectral): def test_spectral_slice(gwcs_3d_galactic_spectral): - wcs = SlicedLowLevelWCS(gwcs_3d_galactic_spectral, [slice(None), 10]) assert wcs.pixel_n_dim == 2 assert wcs.world_n_dim == 2 assert wcs.array_shape == (30, 10) assert wcs.pixel_shape == (10, 30) - assert wcs.world_axis_physical_types == ['pos.galactic.lat', 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'deg'] + assert wcs.world_axis_physical_types == ["pos.galactic.lat", "pos.galactic.lon"] + assert wcs.world_axis_units == ["deg", "deg"] assert_equal(wcs.axis_correlation_matrix, [[True, True], [True, True]]) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) assert_allclose(wcs.pixel_to_world_values(29, 44), (80, 205)) assert_allclose(wcs.array_index_to_world_values(44, 29), (80, 205)) - assert_allclose(wcs.world_to_pixel_values(80, 205), (29., 44.)) + assert_allclose(wcs.world_to_pixel_values(80, 205), (29.0, 44.0)) assert_equal(wcs.world_to_array_index_values(80, 205), (44, 29)) assert_equal(wcs.pixel_bounds, [(-1, 35), (5, 50)]) @@ -168,39 +175,46 @@ def test_spectral_slice(gwcs_3d_galactic_spectral): def test_spectral_range(gwcs_3d_galactic_spectral): - wcs = SlicedLowLevelWCS(gwcs_3d_galactic_spectral, [slice(None), slice(4, 10)]) assert wcs.pixel_n_dim == 3 assert wcs.world_n_dim == 3 assert wcs.array_shape == (30, 6, 10) assert wcs.pixel_shape == (10, 6, 30) - assert wcs.world_axis_physical_types == ['pos.galactic.lat', 'em.freq', 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'Hz', 'deg'] - - assert_equal(wcs.axis_correlation_matrix, [[True, False, True], [False, True, False], [True, False, True]]) + assert wcs.world_axis_physical_types == [ + "pos.galactic.lat", + "em.freq", + "pos.galactic.lon", + ] + assert wcs.world_axis_units == ["deg", "Hz", "deg"] + + assert_equal( + wcs.axis_correlation_matrix, + [[True, False, True], [False, True, False], [True, False, True]], + ) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('spectral', 0), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) - assert wcs.world_axis_object_classes['spectral'][0] is SpectralCoord - assert wcs.world_axis_object_classes['spectral'][1] == () - assert wcs.world_axis_object_classes['spectral'][2] == {'unit': 'Hz'} + assert wcs.world_axis_object_classes["spectral"][0] is SpectralCoord + assert wcs.world_axis_object_classes["spectral"][1] == () + assert wcs.world_axis_object_classes["spectral"][2] == {"unit": "Hz"} assert_allclose(wcs.pixel_to_world_values(29, 35, 44), (80, 20, 205)) assert_allclose(wcs.array_index_to_world_values(44, 35, 29), (80, 20, 205)) - assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29., 35., 44.)) + assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29.0, 35.0, 44.0)) assert_equal(wcs.world_to_array_index_values(80, 20, 205), (44, 35, 29)) assert_equal(wcs.pixel_bounds, [(-1, 35), (-6, 41), (5, 50)]) @@ -236,39 +250,45 @@ def test_spectral_range(gwcs_3d_galactic_spectral): def test_celestial_slice(gwcs_3d_galactic_spectral): - wcs = SlicedLowLevelWCS(gwcs_3d_galactic_spectral, [Ellipsis, 5]) assert wcs.pixel_n_dim == 2 assert wcs.world_n_dim == 3 assert wcs.array_shape == (30, 20) assert wcs.pixel_shape == (20, 30) - assert wcs.world_axis_physical_types == ['pos.galactic.lat', 'em.freq', 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'Hz', 'deg'] + assert wcs.world_axis_physical_types == [ + "pos.galactic.lat", + "em.freq", + "pos.galactic.lon", + ] + assert wcs.world_axis_units == ["deg", "Hz", "deg"] - assert_equal(wcs.axis_correlation_matrix, [[False, True], [True, False], [False, True]]) + assert_equal( + wcs.axis_correlation_matrix, [[False, True], [True, False], [False, True]] + ) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('spectral', 0), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) - assert wcs.world_axis_object_classes['spectral'][0] is SpectralCoord - assert wcs.world_axis_object_classes['spectral'][1] == () - assert wcs.world_axis_object_classes['spectral'][2] == {'unit': 'Hz'} + assert wcs.world_axis_object_classes["spectral"][0] is SpectralCoord + assert wcs.world_axis_object_classes["spectral"][1] == () + assert wcs.world_axis_object_classes["spectral"][2] == {"unit": "Hz"} assert_allclose(wcs.pixel_to_world_values(39, 44), (79.76, 20, 205)) assert_allclose(wcs.array_index_to_world_values(44, 39), (79.76, 20, 205)) - assert_allclose(wcs.world_to_pixel_values(79.76, 20, 205), (39., 44.)) + assert_allclose(wcs.world_to_pixel_values(79.76, 20, 205), (39.0, 44.0)) assert_equal(wcs.world_to_array_index_values(79.76, 20, 205), (44, 39)) assert_equal(wcs.pixel_bounds, [(-2, 45), (5, 50)]) @@ -305,39 +325,46 @@ def test_celestial_slice(gwcs_3d_galactic_spectral): def test_celestial_range(gwcs_3d_galactic_spectral): - wcs = SlicedLowLevelWCS(gwcs_3d_galactic_spectral, [Ellipsis, slice(5, 10)]) assert wcs.pixel_n_dim == 3 assert wcs.world_n_dim == 3 assert wcs.array_shape == (30, 20, 5) assert wcs.pixel_shape == (5, 20, 30) - assert wcs.world_axis_physical_types == ['pos.galactic.lat', 'em.freq', 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'Hz', 'deg'] - - assert_equal(wcs.axis_correlation_matrix, [[True, False, True], [False, True, False], [True, False, True]]) + assert wcs.world_axis_physical_types == [ + "pos.galactic.lat", + "em.freq", + "pos.galactic.lon", + ] + assert wcs.world_axis_units == ["deg", "Hz", "deg"] + + assert_equal( + wcs.axis_correlation_matrix, + [[True, False, True], [False, True, False], [True, False, True]], + ) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('spectral', 0), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) - assert wcs.world_axis_object_classes['spectral'][0] is SpectralCoord - assert wcs.world_axis_object_classes['spectral'][1] == () - assert wcs.world_axis_object_classes['spectral'][2] == {'unit': 'Hz'} + assert wcs.world_axis_object_classes["spectral"][0] is SpectralCoord + assert wcs.world_axis_object_classes["spectral"][1] == () + assert wcs.world_axis_object_classes["spectral"][2] == {"unit": "Hz"} assert_allclose(wcs.pixel_to_world_values(24, 39, 44), (80, 20, 205)) assert_allclose(wcs.array_index_to_world_values(44, 39, 24), (80, 20, 205)) - assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (24., 39., 44.)) + assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (24.0, 39.0, 44.0)) assert_equal(wcs.world_to_array_index_values(80, 20, 205), (44, 39, 24)) assert_equal(wcs.pixel_bounds, [(-6, 30), (-2, 45), (5, 50)]) @@ -384,32 +411,40 @@ def test_no_array_shape(gwcs_3d_galactic_spectral): assert wcs.world_n_dim == 3 assert wcs.array_shape is None assert wcs.pixel_shape is None - assert wcs.world_axis_physical_types == ['pos.galactic.lat', 'em.freq', 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'Hz', 'deg'] - - assert_equal(wcs.axis_correlation_matrix, [[True, False, True], [False, True, False], [True, False, True]]) + assert wcs.world_axis_physical_types == [ + "pos.galactic.lat", + "em.freq", + "pos.galactic.lon", + ] + assert wcs.world_axis_units == ["deg", "Hz", "deg"] + + assert_equal( + wcs.axis_correlation_matrix, + [[True, False, True], [False, True, False], [True, False, True]], + ) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('spectral', 0), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) - assert wcs.world_axis_object_classes['spectral'][0] is SpectralCoord - assert wcs.world_axis_object_classes['spectral'][1] == () - assert wcs.world_axis_object_classes['spectral'][2] == {'unit': 'Hz'} + assert wcs.world_axis_object_classes["spectral"][0] is SpectralCoord + assert wcs.world_axis_object_classes["spectral"][1] == () + assert wcs.world_axis_object_classes["spectral"][2] == {"unit": "Hz"} assert_allclose(wcs.pixel_to_world_values(29, 39, 44), (80, 20, 205)) assert_allclose(wcs.array_index_to_world_values(44, 39, 29), (80, 20, 205)) - assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29., 39., 44.)) + assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29.0, 39.0, 44.0)) assert_equal(wcs.world_to_array_index_values(80, 20, 205), (44, 39, 29)) assert str(wcs) == EXPECTED_NO_SHAPE_REPR.strip() @@ -456,29 +491,36 @@ def test_ellipsis_none_types(gwcs_3d_galactic_spectral): assert wcs.world_n_dim == 3 assert wcs.array_shape == (30, 20, 10) assert wcs.pixel_shape == (10, 20, 30) - assert wcs.world_axis_physical_types == ['pos.galactic.lat', None, 'pos.galactic.lon'] - assert wcs.world_axis_units == ['deg', 'Hz', 'deg'] - - assert_equal(wcs.axis_correlation_matrix, [[True, False, True], - [False, True, False], [True, False, True]]) + assert wcs.world_axis_physical_types == [ + "pos.galactic.lat", + None, + "pos.galactic.lon", + ] + assert wcs.world_axis_units == ["deg", "Hz", "deg"] + + assert_equal( + wcs.axis_correlation_matrix, + [[True, False, True], [False, True, False], [True, False, True]], + ) first_two = [c[:2] for c in wcs.world_axis_object_components] last_one = [c[2] for c in wcs.world_axis_object_components] - assert first_two == [('celestial', 1), - ('spectral', 0), - ('celestial', 0)] + assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] assert all([callable(last) for last in last_one]) - assert wcs.world_axis_object_classes['celestial'][0] is SkyCoord - assert wcs.world_axis_object_classes['celestial'][1] == () - assert isinstance(wcs.world_axis_object_classes['celestial'][2]['frame'], Galactic) - assert tuple(wcs.world_axis_object_classes['celestial'][2]['unit']) == (u.deg, u.deg) + assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord + assert wcs.world_axis_object_classes["celestial"][1] == () + assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic) + assert tuple(wcs.world_axis_object_classes["celestial"][2]["unit"]) == ( + u.deg, + u.deg, + ) assert_allclose(wcs.pixel_to_world_values(29, 39, 44), (80, 20, 205)) assert_allclose(wcs.array_index_to_world_values(44, 39, 29), (80, 20, 205)) - assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29., 39., 44.)) + assert_allclose(wcs.world_to_pixel_values(80, 20, 205), (29.0, 39.0, 44.0)) assert_equal(wcs.world_to_array_index_values(80, 20, 205), (44, 39, 29)) assert_equal(wcs.pixel_bounds, [(-1, 35), (-2, 45), (5, 50)]) diff --git a/gwcs/tests/test_bounding_box.py b/gwcs/tests/test_bounding_box.py index 1cc46404..44ab0db0 100644 --- a/gwcs/tests/test_bounding_box.py +++ b/gwcs/tests/test_bounding_box.py @@ -9,78 +9,98 @@ y1 = [np.nan, np.nan, 4, np.nan] -@pytest.mark.parametrize((("input", "output")), [((2, 4), (2, 4)), - ((100, 200), (np.nan, np.nan)), - ((x, x),(y, y)) - ]) +@pytest.mark.parametrize( + (("input", "output")), + [((2, 4), (2, 4)), ((100, 200), (np.nan, np.nan)), ((x, x), (y, y))], +) def test_2d_spatial(gwcs_2d_spatial_shift, input, output): w = gwcs_2d_spatial_shift - w.bounding_box = ((-.5, 21), (4, 12)) + w.bounding_box = ((-0.5, 21), (4, 12)) assert_array_equal(w.invert(*w(*input)), output) - assert_array_equal(w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output) + assert_array_equal( + w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + ) assert_array_equal(w.world_to_pixel(w.pixel_to_world(*input)), output) -@pytest.mark.parametrize((("input", "output")), [((2, 4), (2, 4)), - ((100, 200), (np.nan, np.nan)), - ((x, x), (y, y)) - ]) +@pytest.mark.parametrize( + (("input", "output")), + [((2, 4), (2, 4)), ((100, 200), (np.nan, np.nan)), ((x, x), (y, y))], +) def test_2d_spatial_coordinate(gwcs_2d_quantity_shift, input, output): w = gwcs_2d_quantity_shift - w.bounding_box = ((-.5, 21), (4, 12)) + w.bounding_box = ((-0.5, 21), (4, 12)) assert_array_equal(w.invert(*w(*input)), output) - assert_array_equal(w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output) + assert_array_equal( + w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + ) assert_array_equal(w.world_to_pixel(*w.pixel_to_world(*input)), output) -@pytest.mark.parametrize((("input", "output")), [((2, 4), (2, 4)), - ((100, 200), (np.nan, np.nan)), - ((x, x), (y, y)) - ]) +@pytest.mark.parametrize( + (("input", "output")), + [((2, 4), (2, 4)), ((100, 200), (np.nan, np.nan)), ((x, x), (y, y))], +) def test_2d_spatial_coordinate_reordered(gwcs_2d_spatial_reordered, input, output): w = gwcs_2d_spatial_reordered - w.bounding_box = ((-.5, 21), (4, 12)) + w.bounding_box = ((-0.5, 21), (4, 12)) assert_array_equal(w.invert(*w(*input)), output) - assert_array_equal(w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output) + assert_array_equal( + w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + ) assert_array_equal(w.world_to_pixel(w.pixel_to_world(*input)), output) -@pytest.mark.parametrize((("input", "output")), [(2, 2), - ((10, 200), (10, np.nan)), - (x, (np.nan, 2, 4, 13)) - ]) +@pytest.mark.parametrize( + (("input", "output")), [(2, 2), ((10, 200), (10, np.nan)), (x, (np.nan, 2, 4, 13))] +) def test_1d_freq(gwcs_1d_freq, input, output): w = gwcs_1d_freq - w.bounding_box = (-.5, 21) + w.bounding_box = (-0.5, 21) print(f"input {input}, {output}") assert_array_equal(w.invert(w(input)), output) assert_array_equal(w.world_to_pixel_values(w.pixel_to_world_values(input)), output) assert_array_equal(w.world_to_pixel(w.pixel_to_world(input)), output) -@pytest.mark.parametrize((("input", "output")), [((2, 4, 5), (2, 4, 5)), - ((100, 200, 5), (np.nan, np.nan, np.nan)), - ((x, x, x), (y1, y1, y1)) - ]) +@pytest.mark.parametrize( + (("input", "output")), + [ + ((2, 4, 5), (2, 4, 5)), + ((100, 200, 5), (np.nan, np.nan, np.nan)), + ((x, x, x), (y1, y1, y1)), + ], +) def test_3d_spatial_wave(gwcs_3d_spatial_wave, input, output): w = gwcs_3d_spatial_wave - w.bounding_box = ((-.5, 21), (4, 12), (3, 21)) + w.bounding_box = ((-0.5, 21), (4, 12), (3, 21)) assert_array_equal(w.invert(*w(*input)), output) - assert_array_equal(w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output) + assert_array_equal( + w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + ) assert_array_equal(w.world_to_pixel(*w.pixel_to_world(*input)), output) -@pytest.mark.parametrize((("input", "output")), [((1, 2, 3, 4), (1., 2., 3., 4.)), - ((100, 3, 3, 3), (np.nan, 3, 3, 3)), - ((x, x, x, x), [[np.nan, 2., 4., 13.], - [np.nan, 2., 4., 13.], - [np.nan, 2., 4., 13.], - [np.nan, 2., 4., np.nan]]) - ]) +@pytest.mark.parametrize( + (("input", "output")), + [ + ((1, 2, 3, 4), (1.0, 2.0, 3.0, 4.0)), + ((100, 3, 3, 3), (np.nan, 3, 3, 3)), + ( + (x, x, x, x), + [ + [np.nan, 2.0, 4.0, 13.0], + [np.nan, 2.0, 4.0, 13.0], + [np.nan, 2.0, 4.0, 13.0], + [np.nan, 2.0, 4.0, np.nan], + ], + ), + ], +) def test_gwcs_spec_cel_time_4d(gwcs_spec_cel_time_4d, input, output): w = gwcs_spec_cel_time_4d diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index f65339ee..f7e4f18c 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -16,6 +16,7 @@ from .. import coordinate_frames as cf import astropy + astropy_version = astropy.__version__ coord_frames = coord.builtin_frames.__all__[:] @@ -29,14 +30,45 @@ icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_order=(0, 1)) -detector = cf.Frame2D(name='detector', axes_order=(0, 1)) -focal = cf.Frame2D(name='focal', axes_order=(0, 1), unit=(u.m, u.m)) - -spec1 = cf.SpectralFrame(name='freq', unit=[u.Hz, ], axes_order=(2, )) -spec2 = cf.SpectralFrame(name='wave', unit=[u.m, ], axes_order=(2, ), axes_names=('lambda',)) -spec3 = cf.SpectralFrame(name='energy', unit=[u.J, ], axes_order=(2, )) -spec4 = cf.SpectralFrame(name='pixel', unit=[u.pix, ], axes_order=(2, )) -spec5 = cf.SpectralFrame(name='speed', unit=[u.m/u.s, ], axes_order=(2, )) +detector = cf.Frame2D(name="detector", axes_order=(0, 1)) +focal = cf.Frame2D(name="focal", axes_order=(0, 1), unit=(u.m, u.m)) + +spec1 = cf.SpectralFrame( + name="freq", + unit=[ + u.Hz, + ], + axes_order=(2,), +) +spec2 = cf.SpectralFrame( + name="wave", + unit=[ + u.m, + ], + axes_order=(2,), + axes_names=("lambda",), +) +spec3 = cf.SpectralFrame( + name="energy", + unit=[ + u.J, + ], + axes_order=(2,), +) +spec4 = cf.SpectralFrame( + name="pixel", + unit=[ + u.pix, + ], + axes_order=(2,), +) +spec5 = cf.SpectralFrame( + name="speed", + unit=[ + u.m / u.s, + ], + axes_order=(2,), +) comp1 = cf.CompositeFrame([icrs, spec1]) comp2 = cf.CompositeFrame([focal, spec2]) @@ -56,12 +88,12 @@ def test_units(): - assert(comp1.unit == (u.deg, u.deg, u.Hz)) - assert(comp2.unit == (u.m, u.m, u.m)) - assert(comp3.unit == (u.deg, u.deg, u.J)) - assert(comp4.unit == (u.deg, u.deg, u.pix)) - assert(comp5.unit == (u.deg, u.deg, u.m/u.s)) - assert(comp.unit == (u.deg, u.deg, u.Hz, u.m)) + assert comp1.unit == (u.deg, u.deg, u.Hz) + assert comp2.unit == (u.m, u.m, u.m) + assert comp3.unit == (u.deg, u.deg, u.J) + assert comp4.unit == (u.deg, u.deg, u.pix) + assert comp5.unit == (u.deg, u.deg, u.m / u.s) + assert comp.unit == (u.deg, u.deg, u.Hz, u.m) # These two functions fake the old methods on CoordinateFrame to reduce the @@ -78,7 +110,7 @@ def coordinate_to_quantity(*inputs, frame): return results -@pytest.mark.parametrize('inputs', inputs2) +@pytest.mark.parametrize("inputs", inputs2) def test_coordinates_spatial(inputs): sky_coord = coordinates(*inputs, frame=icrs) assert isinstance(sky_coord, coord.SkyCoord) @@ -88,15 +120,15 @@ def test_coordinates_spatial(inputs): assert [coord.unit for coord in focal_coord] == [u.m, u.m] -@pytest.mark.parametrize('inputs', inputs1) +@pytest.mark.parametrize("inputs", inputs1) def test_coordinates_spectral(inputs): wave = coordinates(inputs, frame=spec2) assert_allclose(wave.value, inputs) - assert wave.unit == 'meter' + assert wave.unit == "meter" assert isinstance(wave, u.Quantity) -@pytest.mark.parametrize('inputs', inputs3) +@pytest.mark.parametrize("inputs", inputs3) def test_coordinates_composite(inputs): frame = cf.CompositeFrame([icrs, spec2]) result = coordinates(*inputs, frame=frame) @@ -106,31 +138,49 @@ def test_coordinates_composite(inputs): def test_coordinates_composite_order(): - time = cf.TemporalFrame(Time("2011-01-01T00:00:00"), name='time', unit=[u.s, ], axes_order=(0, )) - dist = cf.CoordinateFrame(name='distance', naxes=1, - axes_type=["SPATIAL"], unit=[u.m, ], axes_order=(1, )) + time = cf.TemporalFrame( + Time("2011-01-01T00:00:00"), + name="time", + unit=[ + u.s, + ], + axes_order=(0,), + ) + dist = cf.CoordinateFrame( + name="distance", + naxes=1, + axes_type=["SPATIAL"], + unit=[ + u.m, + ], + axes_order=(1,), + ) frame = cf.CompositeFrame([time, dist]) result = coordinates(0, 0, frame=frame) assert result[0] == Time("2011-01-01T00:00:00") - assert u.allclose(result[1], 0*u.m) + assert u.allclose(result[1], 0 * u.m) def test_bare_baseframe(): # This is a regression test for the following call: frame = cf.CoordinateFrame(1, "SPATIAL", (0,), unit=(u.km,)) - quantity = coordinate_to_quantity(1*u.m, frame=frame) - assert u.allclose(quantity, 1*u.m) + quantity = coordinate_to_quantity(1 * u.m, frame=frame) + assert u.allclose(quantity, 1 * u.m) # Now also setup the same situation through the whole call stack to be safe. - w = WCS(forward_transform=m.Tabular1D(points=np.arange(10)*u.pix, - lookup_table=np.arange(10)*u.km), - output_frame=frame, - input_frame=cf.CoordinateFrame(1, "PIXEL", (0,), unit=(u.pix,), name="detector_frame") - ) - assert u.allclose(w.world_to_pixel(0*u.km), 0) + w = WCS( + forward_transform=m.Tabular1D( + points=np.arange(10) * u.pix, lookup_table=np.arange(10) * u.km + ), + output_frame=frame, + input_frame=cf.CoordinateFrame( + 1, "PIXEL", (0,), unit=(u.pix,), name="detector_frame" + ), + ) + assert u.allclose(w.world_to_pixel(0 * u.km), 0) -@pytest.mark.parametrize(('frame'), coord_frames) +@pytest.mark.parametrize(("frame"), coord_frames) def test_celestial_attributes_length(frame): """ Test getting default values for @@ -139,45 +189,57 @@ def test_celestial_attributes_length(frame): fr = getattr(coord, frame) if issubclass(fr.__class__, coord.BaseCoordinateFrame): cel = cf.CelestialFrame(reference_frame=fr()) - assert(len(cel.axes_names) == len(cel.axes_type) == len(cel.unit) == \ - len(cel.axes_order) == cel.naxes) + assert ( + len(cel.axes_names) + == len(cel.axes_type) + == len(cel.unit) + == len(cel.axes_order) + == cel.naxes + ) def test_axes_type(): - assert(icrs.axes_type == ('SPATIAL', 'SPATIAL')) - assert(spec1.axes_type == ('SPECTRAL',)) - assert(detector.axes_type == ('SPATIAL', 'SPATIAL')) - assert(focal.axes_type == ('SPATIAL', 'SPATIAL')) + assert icrs.axes_type == ("SPATIAL", "SPATIAL") + assert spec1.axes_type == ("SPECTRAL",) + assert detector.axes_type == ("SPATIAL", "SPATIAL") + assert focal.axes_type == ("SPATIAL", "SPATIAL") def test_length_attributes(): with pytest.raises(ValueError): - cf.CoordinateFrame(naxes=2, unit=(u.deg), - axes_type=("SPATIAL", "SPATIAL"), - axes_order=(0, 1)) + cf.CoordinateFrame( + naxes=2, unit=(u.deg), axes_type=("SPATIAL", "SPATIAL"), axes_order=(0, 1) + ) with pytest.raises(ValueError): - cf.CoordinateFrame(naxes=2, unit=(u.deg, u.deg), - axes_type=("SPATIAL",), - axes_order=(0, 1)) + cf.CoordinateFrame( + naxes=2, unit=(u.deg, u.deg), axes_type=("SPATIAL",), axes_order=(0, 1) + ) with pytest.raises(ValueError): - cf.CoordinateFrame(naxes=2, unit=(u.deg, u.deg), - axes_type=("SPATIAL", "SPATIAL"), - axes_order=(0,)) + cf.CoordinateFrame( + naxes=2, + unit=(u.deg, u.deg), + axes_type=("SPATIAL", "SPATIAL"), + axes_order=(0,), + ) def test_base_coordinate(): - frame = cf.CoordinateFrame(naxes=2, axes_type=("SPATIAL", "SPATIAL"), - axes_order=(0, 1)) - assert frame.name == 'CoordinateFrame' - frame = cf.CoordinateFrame(name="CustomFrame", naxes=2, - axes_type=("SPATIAL", "SPATIAL"), - axes_order=(0, 1), - unit=(u.deg, u.arcsec)) - assert frame.name == 'CustomFrame' + frame = cf.CoordinateFrame( + naxes=2, axes_type=("SPATIAL", "SPATIAL"), axes_order=(0, 1) + ) + assert frame.name == "CoordinateFrame" + frame = cf.CoordinateFrame( + name="CustomFrame", + naxes=2, + axes_type=("SPATIAL", "SPATIAL"), + axes_order=(0, 1), + unit=(u.deg, u.arcsec), + ) + assert frame.name == "CustomFrame" frame.name = "DeLorean" - assert frame.name == 'DeLorean' + assert frame.name == "DeLorean" q1, q2 = coordinate_to_quantity(12 * u.deg, 3 * u.arcsec, frame=frame) assert_quantity_allclose(q1, 12 * u.deg) @@ -199,18 +261,24 @@ def test_temporal_relative(): t = cf.TemporalFrame(reference_frame=Time("2018-01-01T00:00:00")) assert coordinates(10 * u.s, frame=t) == Time("2018-01-01T00:00:00") + 10 * u.s - assert coordinates(TimeDelta(10, format='sec'), frame=t) == Time("2018-01-01T00:00:00") + 10 * u.s + assert ( + coordinates(TimeDelta(10, format="sec"), frame=t) + == Time("2018-01-01T00:00:00") + 10 * u.s + ) a = coordinates(np.array((10, 20)) * u.s, frame=t) assert a[0] == Time("2018-01-01T00:00:00") + 10 * u.s assert a[1] == Time("2018-01-01T00:00:00") + 20 * u.s -@pytest.mark.parametrize('inp', [ - (coord.SkyCoord(10 * u.deg, 20 * u.deg, frame=coord.ICRS),), - # This is the same as 10,20 in ICRS - (coord.SkyCoord(119.26936774, -42.79039286, unit=u.deg, frame='galactic'),) -]) +@pytest.mark.parametrize( + "inp", + [ + (coord.SkyCoord(10 * u.deg, 20 * u.deg, frame=coord.ICRS),), + # This is the same as 10,20 in ICRS + (coord.SkyCoord(119.26936774, -42.79039286, unit=u.deg, frame="galactic"),), + ], +) def test_coordinate_to_quantity_celestial(inp): cel = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_order=(0, 1)) @@ -225,19 +293,25 @@ def test_coordinate_to_quantity_celestial(inp): coordinate_to_quantity((1, 2), frame=cel) -@pytest.mark.parametrize('inp', [ - (SpectralCoord(100 * u.nm),), - (SpectralCoord(0.1 * u.um),), -]) +@pytest.mark.parametrize( + "inp", + [ + (SpectralCoord(100 * u.nm),), + (SpectralCoord(0.1 * u.um),), + ], +) def test_coordinate_to_quantity_spectral(inp): - spec = cf.SpectralFrame(unit=u.nm, axes_order=(1, )) + spec = cf.SpectralFrame(unit=u.nm, axes_order=(1,)) wav = coordinate_to_quantity(*inp, frame=spec) assert_quantity_allclose(wav, 100 * u.nm) -@pytest.mark.parametrize('inp', [ - (Time("2011-01-01T00:00:10"),), -]) +@pytest.mark.parametrize( + "inp", + [ + (Time("2011-01-01T00:00:10"),), + ], +) def test_coordinate_to_quantity_temporal(inp): temp = cf.TemporalFrame(reference_frame=Time("2011-01-01T00:00:00"), unit=u.s) @@ -246,14 +320,22 @@ def test_coordinate_to_quantity_temporal(inp): assert_quantity_allclose(t, 10 * u.s) -@pytest.mark.parametrize('inp', [ - (SpectralCoord(211 * u.AA), Time("2011-01-01T00:00:00"), coord.SkyCoord(0, 0, unit=u.arcsec)), -]) +@pytest.mark.parametrize( + "inp", + [ + ( + SpectralCoord(211 * u.AA), + Time("2011-01-01T00:00:00"), + coord.SkyCoord(0, 0, unit=u.arcsec), + ), + ], +) def test_coordinate_to_quantity_composite(inp): # Composite - wave_frame = cf.SpectralFrame(axes_order=(0, ), unit=u.AA) + wave_frame = cf.SpectralFrame(axes_order=(0,), unit=u.AA) time_frame = cf.TemporalFrame( - axes_order=(1, ), unit=u.s, reference_frame=Time("2011-01-01T00:00:00")) + axes_order=(1,), unit=u.s, reference_frame=Time("2011-01-01T00:00:00") + ) sky_frame = cf.CelestialFrame(axes_order=(2, 3), reference_frame=coord.ICRS()) comp = cf.CompositeFrame([wave_frame, time_frame, sky_frame]) @@ -273,10 +355,11 @@ def test_coordinate_to_quantity_composite_split(): ) # Composite - wave_frame = cf.SpectralFrame(axes_order=(1, ), unit=u.AA) + wave_frame = cf.SpectralFrame(axes_order=(1,), unit=u.AA) sky_frame = cf.CelestialFrame(axes_order=(2, 0), reference_frame=coord.ICRS()) time_frame = cf.TemporalFrame( - axes_order=(3,), unit=u.s, reference_frame=Time("2011-01-01T00:00:00")) + axes_order=(3,), unit=u.s, reference_frame=Time("2011-01-01T00:00:00") + ) comp = cf.CompositeFrame([wave_frame, sky_frame, time_frame]) @@ -290,17 +373,18 @@ def test_coordinate_to_quantity_composite_split(): def test_stokes_frame(): sf = cf.StokesFrame() - assert coordinates(1, frame=sf) == 'I' - assert coordinates(1 * u.one, frame=sf) == 'I' - assert coordinate_to_quantity(StokesCoord('I'), frame=sf) == 1 * u.one + assert coordinates(1, frame=sf) == "I" + assert coordinates(1 * u.one, frame=sf) == "I" + assert coordinate_to_quantity(StokesCoord("I"), frame=sf) == 1 * u.one assert coordinate_to_quantity(StokesCoord(1), frame=sf) == 1 * u.one def test_coordinate_to_quantity_frame2d_composite(): inp = (SpectralCoord(211 * u.AA), Time("2011-01-01T00:00:00"), 0 * u.one, 0 * u.one) - wave_frame = cf.SpectralFrame(axes_order=(0, ), unit=u.AA) + wave_frame = cf.SpectralFrame(axes_order=(0,), unit=u.AA) time_frame = cf.TemporalFrame( - axes_order=(1, ), unit=u.s, reference_frame=Time("2011-01-01T00:00:00")) + axes_order=(1,), unit=u.s, reference_frame=Time("2011-01-01T00:00:00") + ) frame2d = cf.Frame2D(name="intermediate", axes_order=(2, 3), unit=(u.one, u.one)) @@ -330,7 +414,7 @@ def test_coordinate_to_quantity_error(): with pytest.raises(ValueError): coordinate_to_quantity((1, 1), 2, frame=frame) - frame = cf.TemporalFrame(reference_frame=Time([], format='isot'), unit=u.s) + frame = cf.TemporalFrame(reference_frame=Time([], format="isot"), unit=u.s) with pytest.raises(ValueError): coordinate_to_quantity(1, frame=frame) @@ -345,64 +429,89 @@ def test_axis_physical_types(): assert comp1.axis_physical_types == ("pos.eq.ra", "pos.eq.dec", "em.freq") assert comp2.axis_physical_types == ("custom:x", "custom:y", "em.wl") assert comp3.axis_physical_types == ("pos.eq.ra", "pos.eq.dec", "em.energy") - assert comp.axis_physical_types == ('pos.eq.ra', 'pos.eq.dec', 'em.freq', 'em.wl') + assert comp.axis_physical_types == ("pos.eq.ra", "pos.eq.dec", "em.freq", "em.wl") - spec6 = cf.SpectralFrame(name='waven', axes_order=(1,), - axis_physical_types='em.wavenumber', unit=u.Unit(1)) - assert spec6.axis_physical_types == ('em.wavenumber',) + spec6 = cf.SpectralFrame( + name="waven", + axes_order=(1,), + axis_physical_types="em.wavenumber", + unit=u.Unit(1), + ) + assert spec6.axis_physical_types == ("em.wavenumber",) t = cf.TemporalFrame(reference_frame=Time("2018-01-01T00:00:00"), unit=u.s) - assert t.axis_physical_types == ('time',) + assert t.axis_physical_types == ("time",) - fr2d = cf.Frame2D(name='d', axes_names=("x", "y")) - assert fr2d.axis_physical_types == ('custom:x', 'custom:y') + fr2d = cf.Frame2D(name="d", axes_names=("x", "y")) + assert fr2d.axis_physical_types == ("custom:x", "custom:y") - fr2d = cf.Frame2D(name='d', axes_names=None) - assert fr2d.axis_physical_types == ('custom:SPATIAL', 'custom:SPATIAL') + fr2d = cf.Frame2D(name="d", axes_names=None) + assert fr2d.axis_physical_types == ("custom:SPATIAL", "custom:SPATIAL") - fr2d = cf.Frame2D(name='d', axis_physical_types=("pos.x", "pos.y")) - assert fr2d.axis_physical_types == ('custom:pos.x', 'custom:pos.y') + fr2d = cf.Frame2D(name="d", axis_physical_types=("pos.x", "pos.y")) + assert fr2d.axis_physical_types == ("custom:pos.x", "custom:pos.y") with pytest.raises(ValueError): - cf.CelestialFrame(reference_frame=coord.ICRS(), axis_physical_types=("pos.eq.ra",)) + cf.CelestialFrame( + reference_frame=coord.ICRS(), axis_physical_types=("pos.eq.ra",) + ) - fr = cf.CelestialFrame(reference_frame=coord.ICRS(), axis_physical_types=("ra", "dec")) + fr = cf.CelestialFrame( + reference_frame=coord.ICRS(), axis_physical_types=("ra", "dec") + ) assert fr.axis_physical_types == ("custom:ra", "custom:dec") fr = cf.CelestialFrame(reference_frame=coord.BarycentricTrueEcliptic()) - assert fr.axis_physical_types == ('pos.ecliptic.lon', 'pos.ecliptic.lat') + assert fr.axis_physical_types == ("pos.ecliptic.lon", "pos.ecliptic.lat") - frame = cf.CoordinateFrame(name='custom_frame', axes_type=("SPATIAL",), - axes_order=(0,), axis_physical_types="length", - axes_names="x", naxes=1) + frame = cf.CoordinateFrame( + name="custom_frame", + axes_type=("SPATIAL",), + axes_order=(0,), + axis_physical_types="length", + axes_names="x", + naxes=1, + ) assert frame.axis_physical_types == ("custom:length",) - frame = cf.CoordinateFrame(name='custom_frame', axes_type=("SPATIAL",), - axes_order=(0,), axis_physical_types=("length",), - axes_names="x", naxes=1) + frame = cf.CoordinateFrame( + name="custom_frame", + axes_type=("SPATIAL",), + axes_order=(0,), + axis_physical_types=("length",), + axes_names="x", + naxes=1, + ) assert frame.axis_physical_types == ("custom:length",) with pytest.raises(ValueError): - cf.CoordinateFrame(name='custom_frame', axes_type=("SPATIAL",), - axes_order=(0,), - axis_physical_types=("length", "length"), naxes=1) + cf.CoordinateFrame( + name="custom_frame", + axes_type=("SPATIAL",), + axes_order=(0,), + axis_physical_types=("length", "length"), + naxes=1, + ) def test_base_frame(): with pytest.raises(ValueError): - cf.CoordinateFrame(name='custom_frame', - axes_type=("SPATIAL",), - naxes=1, axes_order=(0,), - axes_names=("x", "y")) + cf.CoordinateFrame( + name="custom_frame", + axes_type=("SPATIAL",), + naxes=1, + axes_order=(0,), + axes_names=("x", "y"), + ) frame = cf.CoordinateFrame( - name='custom_frame', + name="custom_frame", axes_type=("SPATIAL",), axes_order=(0,), axes_names="x", - naxes=1 + naxes=1, ) assert frame.naxes == 1 assert frame.axes_names == ("x",) - coordinate_to_quantity(1*u.one, frame=frame) + coordinate_to_quantity(1 * u.one, frame=frame) def test_ucd1_to_ctype_not_out_of_sync(caplog): @@ -415,8 +524,7 @@ def test_ucd1_to_ctype_not_out_of_sync(caplog): """ cf._ucd1_to_ctype_name_mapping( - ctype_to_ucd=CTYPE_TO_UCD1, - allowed_ucd_duplicates=cf._ALLOWED_UCD_DUPLICATES + ctype_to_ucd=CTYPE_TO_UCD1, allowed_ucd_duplicates=cf._ALLOWED_UCD_DUPLICATES ) assert len(caplog.record_tuples) == 0 @@ -424,30 +532,28 @@ def test_ucd1_to_ctype_not_out_of_sync(caplog): def test_ucd1_to_ctype(caplog): new_ctype_to_ucd = { - 'RPT1': 'new.repeated.type', - 'RPT2': 'new.repeated.type', - 'RPT3': 'new.repeated.type', + "RPT1": "new.repeated.type", + "RPT2": "new.repeated.type", + "RPT3": "new.repeated.type", } ctype_to_ucd = dict(**CTYPE_TO_UCD1, **new_ctype_to_ucd) inv_map = cf._ucd1_to_ctype_name_mapping( - ctype_to_ucd=ctype_to_ucd, - allowed_ucd_duplicates=cf._ALLOWED_UCD_DUPLICATES + ctype_to_ucd=ctype_to_ucd, allowed_ucd_duplicates=cf._ALLOWED_UCD_DUPLICATES ) - assert caplog.record_tuples[-1][1] == logging.WARNING and \ - caplog.record_tuples[-1][2].startswith( - "Found unsupported duplicate physical type" - ) + assert caplog.record_tuples[-1][1] == logging.WARNING and caplog.record_tuples[-1][ + 2 + ].startswith("Found unsupported duplicate physical type") for k, v in cf._ALLOWED_UCD_DUPLICATES.items(): - assert inv_map.get(k, '') == v + assert inv_map.get(k, "") == v for k, v in inv_map.items(): assert ctype_to_ucd[v] == k - assert inv_map['new.repeated.type'] in new_ctype_to_ucd + assert inv_map["new.repeated.type"] in new_ctype_to_ucd def test_celestial_ordering(): diff --git a/gwcs/tests/test_extension.py b/gwcs/tests/test_extension.py index 34a5057f..b440ccc3 100644 --- a/gwcs/tests/test_extension.py +++ b/gwcs/tests/test_extension.py @@ -8,7 +8,10 @@ import pytest -@pytest.mark.skipif(asdf_wcs_schemas.__version__ < "0.2.0", reason="version 0.2 provides the new manifests") +@pytest.mark.skipif( + asdf_wcs_schemas.__version__ < "0.2.0", + reason="version 0.2 provides the new manifests", +) def test_empty_extension(): """ Test that an empty extension was installed for gwcs 1.0.0 @@ -23,11 +26,16 @@ def test_empty_extension(): assert len(extensions_by_uri) == len(extensions) # check that all 3 versions are installed - for version in ('1.0.0', '1.0.1', '1.1.0'): - assert f"asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-{version}" in extensions_by_uri + for version in ("1.0.0", "1.0.1", "1.1.0"): + assert ( + f"asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-{version}" + in extensions_by_uri + ) # the 1.0.0 extension should support no tags or types - legacy = extensions_by_uri["asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.0.0"] + legacy = extensions_by_uri[ + "asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.0.0" + ] assert len(legacy.tags) == 0 assert len(legacy.converters) == 0 @@ -55,4 +63,4 @@ def test_open_legacy_without_warning(): with warnings.catch_warnings(): warnings.simplefilter("error") with asdf.open(io.BytesIO(asdf_bytes)) as af: - assert af['foo'] == 1 + assert af["foo"] == 1 diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index 0806083f..16149ef9 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -11,7 +11,9 @@ try: from asdf_astropy.testing.helpers import assert_model_roundtrip except ImportError: - from asdf_astropy.converters.transform.tests.test_transform import assert_model_roundtrip + from asdf_astropy.converters.transform.tests.test_transform import ( + assert_model_roundtrip, + ) from .. import geometry @@ -28,7 +30,7 @@ def test_spherical_cartesian_inverse(): @pytest.mark.parametrize( - 'testval, unit, wrap_at', + "testval, unit, wrap_at", product( [ (45.0, -90.0, (0.0, 0.0, -1.0)), @@ -54,7 +56,7 @@ def test_spherical_cartesian_inverse(): ], [1, 1 * u.deg, 3600.0 * u.arcsec, np.pi / 180.0 * u.rad], [180, 360], - ) + ), ) def test_spherical_to_cartesian(testval, unit, wrap_at): s2c = geometry.SphericalToCartesian(wrap_lon_at=wrap_at) @@ -71,13 +73,15 @@ def test_spherical_to_cartesian(testval, unit, wrap_at): @pytest.mark.parametrize( - 'lon, lat, unit, wrap_at', - list(product( - [0, 45, 90, 135, 180, 225, 270, 315, 360], - [-90, -89, -55, 0, 25, 89, 90], - [1, 1 * u.deg, 3600.0 * u.arcsec, np.pi / 180.0 * u.rad], - [180, 360], - )) + "lon, lat, unit, wrap_at", + list( + product( + [0, 45, 90, 135, 180, 225, 270, 315, 360], + [-90, -89, -55, 0, 25, 89, 90], + [1, 1 * u.deg, 3600.0 * u.arcsec, np.pi / 180.0 * u.rad], + [180, 360], + ) + ), ) def test_spher2cart_roundrip(lon, lat, unit, wrap_at): s2c = geometry.SphericalToCartesian(wrap_lon_at=wrap_at) @@ -93,7 +97,7 @@ def test_spher2cart_roundrip(lon, lat, unit, wrap_at): assert u.allclose( c2s(*s2c(lon * unit, lat * unit)), (lon * ounit, lat * ounit), - atol=1e-15 * ounit + atol=1e-15 * ounit, ) @@ -102,17 +106,22 @@ def test_cart2spher_at_pole(cart_to_spher): @pytest.mark.parametrize( - 'lonlat, unit, wrap_at', - list(product( - [ - [[1], [-80]], - [[325], [-89]], - [[0, 1, 120, 180, 225, 325, 359], [-89, 0, 89, 10, -15, 45, -30]], - [np.array([0.0, 1, 120, 180, 225, 325, 359]), np.array([-89, 0.0, 89, 10, -15, 45, -30])] - ], - [None, 1 * u.deg], - [180, 360], - )) + "lonlat, unit, wrap_at", + list( + product( + [ + [[1], [-80]], + [[325], [-89]], + [[0, 1, 120, 180, 225, 325, 359], [-89, 0, 89, 10, -15, 45, -30]], + [ + np.array([0.0, 1, 120, 180, 225, 325, 359]), + np.array([-89, 0.0, 89, 10, -15, 45, -30]), + ], + ], + [None, 1 * u.deg], + [180, 360], + ) + ), ) def test_spher2cart_roundrip_arr(lonlat, unit, wrap_at): lon, lat = lonlat @@ -136,35 +145,41 @@ def test_spher2cart_roundrip_arr(lonlat, unit, wrap_at): lat = lat * unit atol = atol * u.deg - assert u.allclose( - c2s(*s2c(lon, lat)), - (olon, olat), - atol=atol - ) + assert u.allclose(c2s(*s2c(lon, lat)), (olon, olat), atol=atol) -@pytest.mark.parametrize('unit1, unit2', [(u.deg, 1), (1, u.deg)]) +@pytest.mark.parametrize("unit1, unit2", [(u.deg, 1), (1, u.deg)]) def test_spherical_to_cartesian_mixed_Q(spher_to_cart, unit1, unit2): with pytest.raises(TypeError) as arg_err: spher_to_cart(135.0 * unit1, 45.0 * unit2) - assert (arg_err.value.args[0] == "All arguments must be of the same type " - "(i.e., quantity or not).") + assert ( + arg_err.value.args[0] == "All arguments must be of the same type " + "(i.e., quantity or not)." + ) @pytest.mark.parametrize( - 'x, y, z', - sorted(list(set( - tuple(permutations([1 * u.m, 1, 1])) + tuple(permutations([1 * u.m, 1 * u.m, 1])) - )), key=str) + "x, y, z", + sorted( + list( + set( + tuple(permutations([1 * u.m, 1, 1])) + + tuple(permutations([1 * u.m, 1 * u.m, 1])) + ) + ), + key=str, + ), ) def test_cartesian_to_spherical_mixed_Q(cart_to_spher, x, y, z): with pytest.raises(TypeError) as arg_err: cart_to_spher(x, y, z) - assert (arg_err.value.args[0] == "All arguments must be of the same type " - "(i.e., quantity or not).") + assert ( + arg_err.value.args[0] == "All arguments must be of the same type " + "(i.e., quantity or not)." + ) -@pytest.mark.parametrize('wrap_at', ['1', 180., True, 180j, [180], -180, 0]) +@pytest.mark.parametrize("wrap_at", ["1", 180.0, True, 180j, [180], -180, 0]) def test_c2s2c_wrong_wrap_type(spher_to_cart, cart_to_spher, wrap_at): err_msg = "'wrap_lon_at' must be an integer number: 180 or 360" with pytest.raises(ValueError) as arg_err: @@ -193,7 +208,7 @@ def test_cartesian_spherical_asdf(tmpdir): assert_model_roundtrip(s2c0, tmpdir) # create file object - f = asdf.AsdfFile({'c2s': c2s0, 's2c': s2c0}) + f = asdf.AsdfFile({"c2s": c2s0, "s2c": s2c0}) # write to... buf = io.BytesIO() @@ -204,21 +219,33 @@ def test_cartesian_spherical_asdf(tmpdir): f = asdf.open(buf) # retrieve transformations: - c2s = f['c2s'] - s2c = f['s2c'] + c2s = f["c2s"] + s2c = f["s2c"] pcoords = [ - (45.0, -90.0), (45.0, -45.0), (45, 0.0), - (45.0, 45), (45.0, 90.0), (135.0, -90.0), - (135.0, -45.0), (135.0, 0.0), (135.0, 45.0), - (135.0, 90.0) + (45.0, -90.0), + (45.0, -45.0), + (45, 0.0), + (45.0, 45), + (45.0, 90.0), + (135.0, -90.0), + (135.0, -45.0), + (135.0, 0.0), + (135.0, 45.0), + (135.0, 90.0), ] ncoords = [ - (225.0, -90.0), (225.0, -45.0), - (225.0, 0.0), (225.0, 45.0), (225.0, 90.0), - (315.0, -90.0), (315.0, -45.0), (315.0, 0.0), - (315.0, 45.0), (315.0, 90.0) + (225.0, -90.0), + (225.0, -45.0), + (225.0, 0.0), + (225.0, 45.0), + (225.0, 90.0), + (315.0, -90.0), + (315.0, -45.0), + (315.0, 0.0), + (315.0, 45.0), + (315.0, 90.0), ] for lon, lat in pcoords: diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 37779292..e725d1f5 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -1,8 +1,8 @@ - # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Test regions """ + import warnings import numpy as np from numpy.testing import assert_equal, assert_allclose @@ -14,29 +14,31 @@ def test_LabelMapperArray_from_vertices_int(): - regions = {1: [[795, 970], [2047, 970], [2047, 999], [795, 999], [795, 970]], - 2: [[844, 1067], [2047, 1067], [2047, 1113], [844, 1113], [844, 1067]], - 3: [[654, 1029], [2047, 1029], [2047, 1078], [654, 1078], [654, 1029]], - 4: [[772, 990], [2047, 990], [2047, 1042], [772, 1042], [772, 990]] - } + regions = { + 1: [[795, 970], [2047, 970], [2047, 999], [795, 999], [795, 970]], + 2: [[844, 1067], [2047, 1067], [2047, 1113], [844, 1113], [844, 1067]], + 3: [[654, 1029], [2047, 1029], [2047, 1078], [654, 1078], [654, 1029]], + 4: [[772, 990], [2047, 990], [2047, 1042], [772, 1042], [772, 990]], + } mask = selector.LabelMapperArray.from_vertices((2400, 2400), regions) labels = list(regions.keys()) labels.append(0) mask_labels = np.unique(mask.mapper).tolist() - assert(np.sort(labels) == np.sort(mask_labels)).all() + assert (np.sort(labels) == np.sort(mask_labels)).all() def test_LabelMapperArray_from_vertices_string(): - regions = {'S1600A1': [[795, 970], [2047, 970], [2047, 999], [795, 999], [795, 970]], - 'S200A1': [[844, 1067], [2047, 1067], [2047, 1113], [844, 1113], [844, 1067]], - 'S200A2': [[654, 1029], [2047, 1029], [2047, 1078], [654, 1078], [654, 1029]], - 'S400A1': [[772, 990], [2047, 990], [2047, 1042], [772, 1042], [772, 990]] - } + regions = { + "S1600A1": [[795, 970], [2047, 970], [2047, 999], [795, 999], [795, 970]], + "S200A1": [[844, 1067], [2047, 1067], [2047, 1113], [844, 1113], [844, 1067]], + "S200A2": [[654, 1029], [2047, 1029], [2047, 1078], [654, 1078], [654, 1029]], + "S400A1": [[772, 990], [2047, 990], [2047, 1042], [772, 1042], [772, 990]], + } mask = selector.LabelMapperArray.from_vertices((1400, 1400), regions) labels = list(regions.keys()) - labels.append('') + labels.append("") mask_labels = np.unique(mask.mapper).tolist() - assert(np.sort(labels) == np.sort(mask_labels)).all() + assert (np.sort(labels) == np.sort(mask_labels)).all() # These tests below check the scanning algorithm for two shapes @@ -69,7 +71,7 @@ def two_polygons(): def test_polygon1(): vert = [(2, 1), (3, 5), (6, 6), (3, 8), (0, 4), (2, 1)] - pol = region.Polygon('1', vert) + pol = region.Polygon("1", vert) mask = np.zeros((9, 9), dtype=int) mask = pol.scan(mask) pol1 = polygon1() @@ -78,15 +80,17 @@ def test_polygon1(): def test_polygon_zero_width_bbox(): vert = [(1, 1), (1, 3), (1, 6), (1, 1)] - pol = region.Polygon('1', vert) + pol = region.Polygon("1", vert) mask = np.zeros((9, 9), dtype=int) mask = pol.scan(mask) assert not np.any(mask) def test_create_mask_two_polygons(): - vertices = {1: [[2, 1], [3, 5], [6, 6], [3, 8], [0, 4], [2, 1]], - 2: [[10, 0], [30, 0], [30, 30], [10, 30], [10, 0]]} + vertices = { + 1: [[2, 1], [3, 5], [6, 6], [3, 8], [0, 4], [2, 1]], + 2: [[10, 0], [30, 0], [30, 30], [10, 30], [10, 0]], + } mask = selector.LabelMapperArray.from_vertices((301, 301), vertices) pol2 = two_polygons() assert_equal(mask.mapper, pol2) @@ -94,37 +98,40 @@ def test_create_mask_two_polygons(): def create_range_mapper(): m = [] - for i in np.arange(1, 10) * .1: + for i in np.arange(1, 10) * 0.1: c0_0, c1_0, c0_1, c1_1 = np.ones((4,)) * i m.append(models.Polynomial2D(2, c0_0=c0_0, c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) - keys = np.array([[4.88, 5.64], - [5.75, 6.5], - [6.67, 7.47], - [7.7, 8.63], - [8.83, 9.96], - [10.19, 11.49], - [11.77, 13.28], - [13.33, 15.34], - [15.56, 18.09]]) + keys = np.array( + [ + [4.88, 5.64], + [5.75, 6.5], + [6.67, 7.47], + [7.7, 8.63], + [8.83, 9.96], + [10.19, 11.49], + [11.77, 13.28], + [13.33, 15.34], + [15.56, 18.09], + ] + ) rmapper = {} for k, v in zip(keys, m): rmapper[tuple(k)] = v - sel = selector.LabelMapperRange(('x', 'y'), rmapper, - inputs_mapping=models.Mapping((0,), n_inputs=2)) + sel = selector.LabelMapperRange( + ("x", "y"), rmapper, inputs_mapping=models.Mapping((0,), n_inputs=2) + ) return sel def create_scalar_mapper(): m = [] - for i in np.arange(5) * .1: + for i in np.arange(5) * 0.1: c0_0, c1_0, c0_1, c1_1 = np.ones((4,)) * i - m.append(models.Polynomial2D(2, c0_0=c0_0, - c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) - keys = [-1.95805483, -1.67833272, -1.39861060, - -1.11888848, -8.39166358] + m.append(models.Polynomial2D(2, c0_0=c0_0, c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) + keys = [-1.95805483, -1.67833272, -1.39861060, -1.11888848, -8.39166358] dmapper = {} for k, v in zip(keys, m): @@ -134,33 +141,33 @@ def create_scalar_mapper(): def test_LabelMapperDict(): dmapper = create_scalar_mapper() - sel = selector.LabelMapperDict(('x', 'y'), dmapper, atol=10**-3, - inputs_mapping=models.Mapping((0,), n_inputs=2)) - assert(sel(-1.9580, 2) == dmapper[-1.95805483](-1.95805483, 2)) + sel = selector.LabelMapperDict( + ("x", "y"), + dmapper, + atol=10**-3, + inputs_mapping=models.Mapping((0,), n_inputs=2), + ) + assert sel(-1.9580, 2) == dmapper[-1.95805483](-1.95805483, 2) with pytest.raises(TypeError): - selector.LabelMapperDict(('x', 'y'), - mapper={1: models.Rotation2D(23), - 2: models.Shift(1) - } - ) + selector.LabelMapperDict( + ("x", "y"), mapper={1: models.Rotation2D(23), 2: models.Shift(1)} + ) def test_LabelMapperRange(): sel = create_range_mapper() - assert(sel(6, 2) == 4.2) + assert sel(6, 2) == 4.2 with pytest.raises(TypeError): - selector.LabelMapperRange(('x', 'y'), - mapper={(1, 5): models.Rotation2D(23), - (7, 10): models.Shift(1) - } - ) + selector.LabelMapperRange( + ("x", "y"), mapper={(1, 5): models.Rotation2D(23), (7, 10): models.Shift(1)} + ) def test_LabelMapper(): transform = models.Const1D(12.3) - lm = selector.LabelMapper(inputs=('x', 'y'), mapper=transform, inputs_mapping=(1,)) + lm = selector.LabelMapper(inputs=("x", "y"), mapper=transform, inputs_mapping=(1,)) x = np.linspace(3, 11, 20) assert_allclose(lm(x, x), transform(x)) @@ -180,32 +187,32 @@ def test_LabelMapperArray(): def test_RegionsSelector(): labels = np.zeros((10, 10)) labels[1, 2] = 1 - labels[2][2: 4] = 1 - labels[3][1: 4] = 1 - labels[4][: 4] = 1 - labels[5][1: 4] = 1 - labels[6][2: 7] = 1 - labels[7][3: 6] = 1 + labels[2][2:4] = 1 + labels[3][1:4] = 1 + labels[4][:4] = 1 + labels[5][1:4] = 1 + labels[6][2:7] = 1 + labels[7][3:6] = 1 labels[:, -2:] = 2 mapper = selector.LabelMapperArray(labels) - sel = {1: models.Shift(1) & models.Scale(1), - 2: models.Shift(2) & models.Scale(2) - } + sel = {1: models.Shift(1) & models.Scale(1), 2: models.Shift(2) & models.Scale(2)} with pytest.raises(ValueError): # 0 can't be a key in ``selector`` - selector.RegionsSelector(inputs=('x', 'y'), outputs=('x', 'y'), - label_mapper=mapper, - selector={0: models.Shift(1) & models.Scale(1), - 2: models.Shift(2) & models.Scale(2) - } - ) - - reg_selector = selector.RegionsSelector(inputs=('x', 'y'), outputs=('x', 'y'), - label_mapper=mapper, - selector=sel - ) + selector.RegionsSelector( + inputs=("x", "y"), + outputs=("x", "y"), + label_mapper=mapper, + selector={ + 0: models.Shift(1) & models.Scale(1), + 2: models.Shift(2) & models.Scale(2), + }, + ) + + reg_selector = selector.RegionsSelector( + inputs=("x", "y"), outputs=("x", "y"), label_mapper=mapper, selector=sel + ) with pytest.raises(NotImplementedError): reg_selector.inverse @@ -228,10 +235,8 @@ def test_RegionsSelector(): # the transforms of the inverse ``RegionsSelector`` should be the inverse of the # transforms of the ``RegionsSelector`` model. x = np.linspace(-5, 5, 100) - assert_allclose(reg_selector.selector[1].inverse(x, x), - rsinv.selector[1](x, x)) - assert_allclose(reg_selector.selector[2].inverse(x, x), - rsinv.selector[2](x, x)) + assert_allclose(reg_selector.selector[1].inverse(x, x), rsinv.selector[1](x, x)) + assert_allclose(reg_selector.selector[2].inverse(x, x), rsinv.selector[2](x, x)) assert np.isnan(reg_selector(0, 0)).all() # Test setting ``undefined_transform_value`` to a non-default value. @@ -247,15 +252,13 @@ def test_overalpping_ranges(): """ Initializing a ``LabelMapperRange`` with overlapping ranges should raise an error. """ - keys = np.array([[4.88, 5.75], - [5.64, 6.5], - [6.67, 7.47]]) + keys = np.array([[4.88, 5.75], [5.64, 6.5], [6.67, 7.47]]) rmapper = {} for key in keys: rmapper[tuple(key)] = models.Const1D(4) with pytest.raises(ValueError): - selector.LabelMapperRange(('x', 'y'), rmapper, inputs_mapping=((0,))) + selector.LabelMapperRange(("x", "y"), rmapper, inputs_mapping=((0,))) def test_outside_range(): @@ -277,9 +280,16 @@ def test_unique_labels(): assert 0 not in result - labels = ["S100A1", "S200A2", "S400A1", "S1600", "S200B1", "", ] * 1000 + labels = [ + "S100A1", + "S200A2", + "S400A1", + "S1600", + "S200B1", + "", + ] * 1000 np.random.shuffle(labels) - expected = ['S100A1', 'S1600', 'S200A2', 'S200B1', 'S400A1'] + expected = ["S100A1", "S1600", "S200A2", "S200B1", "S400A1"] result = selector.get_unique_regions(labels) assert_equal(expected, result) diff --git a/gwcs/tests/test_spectroscopy_models.py b/gwcs/tests/test_spectroscopy_models.py index 802b75fc..b7e0013e 100644 --- a/gwcs/tests/test_spectroscopy_models.py +++ b/gwcs/tests/test_spectroscopy_models.py @@ -3,14 +3,14 @@ from astropy.modeling.models import Identity import numpy as np from numpy.testing import assert_allclose -from .. import spectroscopy as sp# noqa -from .. import geometry# noqa +from .. import spectroscopy as sp # noqa +from .. import geometry # noqa def test_angles_grating_equation(): - """ Test agaibst the Nispec implementation.""" + """Test agaibst the Nispec implementation.""" lam = np.array([2e-6] * 4) - alpha_in = np.linspace(.01, .05, 4) + alpha_in = np.linspace(0.01, 0.05, 4) model = sp.AnglesFromGratingEquation3D(20000, -1) @@ -20,22 +20,24 @@ def test_angles_grating_equation(): alpha_out, beta_out, gamma_out = model(lam, -alpha_in, alpha_in) assert_allclose(alpha_out, xout) assert_allclose(beta_out, -alpha_in) - assert_allclose(gamma_out, np.sqrt(1 - alpha_out**2 - beta_out**2)) + assert_allclose(gamma_out, np.sqrt(1 - alpha_out**2 - beta_out**2)) # Now with units - model = sp.AnglesFromGratingEquation3D(20000 * 1/u.m, -1) + model = sp.AnglesFromGratingEquation3D(20000 * 1 / u.m, -1) # Eq. from Nirspec model. xout = -alpha_in - (model.groove_density * model.spectral_order * lam * u.m) - alpha_out, beta_out, gamma_out = model(lam * u.m, -u.Quantity(alpha_in), u.Quantity(alpha_in)) + alpha_out, beta_out, gamma_out = model( + lam * u.m, -u.Quantity(alpha_in), u.Quantity(alpha_in) + ) assert_allclose(alpha_out, xout) assert_allclose(beta_out, -alpha_in) - assert_allclose(gamma_out, np.sqrt(1 - alpha_out**2 - beta_out**2)) + assert_allclose(gamma_out, np.sqrt(1 - alpha_out**2 - beta_out**2)) def test_wavelength_grating_equation_units(): - alpha_in = np.linspace(.01, .05, 4) + alpha_in = np.linspace(0.01, 0.05, 4) model = sp.WavelengthFromGratingEquation(20000, -1) # Eq. from Nirspec model. @@ -44,21 +46,19 @@ def test_wavelength_grating_equation_units(): assert_allclose(result, wave) # Now with units - model = sp.WavelengthFromGratingEquation(20000 * 1/u.m, -1) + model = sp.WavelengthFromGratingEquation(20000 * 1 / u.m, -1) # Eq. from Nirspec model. - wave = -(u.Quantity(alpha_in) + u.Quantity(alpha_in)) / (20000 * 1/u.m * -1) + wave = -(u.Quantity(alpha_in) + u.Quantity(alpha_in)) / (20000 * 1 / u.m * -1) result = model(-u.Quantity(alpha_in), -u.Quantity(alpha_in)) assert_allclose(result, wave) -@pytest.mark.parametrize(('wavelength', 'n'), - [(1, 1.43079543), - (2, 1.42575377), - (5, 1.40061966) - ]) +@pytest.mark.parametrize( + ("wavelength", "n"), [(1, 1.43079543), (2, 1.42575377), (5, 1.40061966)] +) def test_SellmeierGlass(wavelength, n, sellmeier_glass): - """ Test from Nirspec team. + """Test from Nirspec team. Wavelength is in microns. """ @@ -67,17 +67,17 @@ def test_SellmeierGlass(wavelength, n, sellmeier_glass): def test_SellmeierZemax(sellmeier_zemax): - """ The data for this test come from Nirspec.""" + """The data for this test come from Nirspec.""" n = 1.4254647475849418 assert_allclose(sellmeier_zemax(2), n) def test_Snell3D(sellmeier_glass): - """ Test from Nirspec.""" + """Test from Nirspec.""" expected = (0.07015255913513296, 0.07015255913513296, 0.9950664484814988) model = sp.Snell3D() n = 1.4254647475849418 - assert_allclose(model(n, .1, .1, .9), expected) + assert_allclose(model(n, 0.1, 0.1, 0.9), expected) def test_snell_sellmeier_combined(sellmeier_glass): @@ -86,4 +86,4 @@ def test_snell_sellmeier_combined(sellmeier_glass): model = sellmeier_glass & todircos | sp.Snell3D() & Identity(1) | fromdircos expected = (0.07013833805527926, 0.07013833805527926, 1.0050677723764139) - assert_allclose(model(2, .1, .1, .9), expected) + assert_allclose(model(2, 0.1, 0.1, 0.9), expected) diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index b33d742b..f8d7cf08 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -35,7 +35,7 @@ def test_fits_transform(): hdr = fits.Header.fromfile(os.path.join(data_path, "simple_wcs2.hdr")) gw1 = gwutils.make_fitswcs_transform(hdr) w1 = fitswcs.WCS(hdr) - assert_allclose(gw1(1, 2), w1.wcs_pix2world(1, 2, 0), atol=10 ** -8) + assert_allclose(gw1(1, 2), w1.wcs_pix2world(1, 2, 0), atol=10**-8) def test_lon_pole(): @@ -44,47 +44,83 @@ def test_lon_pole(): azp = models.Pix2Sky_AZP(mu=-1.35, gamma=25.8458) sky_positive_lat = coord.SkyCoord(3 * u.deg, 1 * u.deg) sky_negative_lat = coord.SkyCoord(3 * u.deg, -1 * u.deg) - assert_quantity_allclose(gwutils._compute_lon_pole(sky_positive_lat, tan), 180 * u.deg) - assert_quantity_allclose(gwutils._compute_lon_pole(sky_negative_lat, tan), 180 * u.deg) - assert_quantity_allclose(gwutils._compute_lon_pole(sky_positive_lat, car), 0 * u.deg) - assert_quantity_allclose(gwutils._compute_lon_pole(sky_negative_lat, car), 180 * u.deg) - assert_quantity_allclose(gwutils._compute_lon_pole((0, 0.34 * u.rad), tan), 180 * u.deg) - assert_quantity_allclose(gwutils._compute_lon_pole((1 * u.rad, 0.34 * u.rad), azp), 180 * u.deg) + assert_quantity_allclose( + gwutils._compute_lon_pole(sky_positive_lat, tan), 180 * u.deg + ) + assert_quantity_allclose( + gwutils._compute_lon_pole(sky_negative_lat, tan), 180 * u.deg + ) + assert_quantity_allclose( + gwutils._compute_lon_pole(sky_positive_lat, car), 0 * u.deg + ) + assert_quantity_allclose( + gwutils._compute_lon_pole(sky_negative_lat, car), 180 * u.deg + ) + assert_quantity_allclose( + gwutils._compute_lon_pole((0, 0.34 * u.rad), tan), 180 * u.deg + ) + assert_quantity_allclose( + gwutils._compute_lon_pole((1 * u.rad, 0.34 * u.rad), azp), 180 * u.deg + ) assert_allclose(gwutils._compute_lon_pole((1, -34), tan), 180) def test_unknown_ctype(): - wcsinfo = {'CDELT': np.array([3.61111098e-05, 3.61111098e-05, 2.49999994e-03]), - 'CRPIX': np.array([17., 16., 1.]), - 'CRVAL': np.array([4.49999564e+01, 1.72786731e-04, 4.84631542e+00]), - 'CTYPE': np.array(['MRSAL1A', 'MRSBE1A', 'WAVE']), - 'CUNIT': np.array([u.Unit("deg"), u.Unit("deg"), u.Unit("um")], dtype=object), - 'PC': np.array([[1., 0., 0.], - [0., 1., 0.], - [0., 0., 1.]]), - 'WCSAXES': 3, - 'has_cd': False - } + wcsinfo = { + "CDELT": np.array([3.61111098e-05, 3.61111098e-05, 2.49999994e-03]), + "CRPIX": np.array([17.0, 16.0, 1.0]), + "CRVAL": np.array([4.49999564e01, 1.72786731e-04, 4.84631542e00]), + "CTYPE": np.array(["MRSAL1A", "MRSBE1A", "WAVE"]), + "CUNIT": np.array([u.Unit("deg"), u.Unit("deg"), u.Unit("um")], dtype=object), + "PC": np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]), + "WCSAXES": 3, + "has_cd": False, + } transform = gwutils.make_fitswcs_transform(wcsinfo) x = np.linspace(-5, 7, 10) y = np.linspace(-5, 7, 10) - expected = (np.array([-0.00075833, -0.00071019, -0.00066204, -0.00061389, -0.00056574, - -0.00051759, -0.00046944, -0.0004213 , -0.00037315, -0.000325]), - np.array([-0.00072222, -0.00067407, -0.00062593, -0.00057778, -0.00052963, - -0.00048148, -0.00043333, -0.00038519, -0.00033704, -0.00028889]) - ) + expected = ( + np.array( + [ + -0.00075833, + -0.00071019, + -0.00066204, + -0.00061389, + -0.00056574, + -0.00051759, + -0.00046944, + -0.0004213, + -0.00037315, + -0.000325, + ] + ), + np.array( + [ + -0.00072222, + -0.00067407, + -0.00062593, + -0.00057778, + -0.00052963, + -0.00048148, + -0.00043333, + -0.00038519, + -0.00033704, + -0.00028889, + ] + ), + ) a, b = transform(x, y) assert_allclose(a, expected[0], atol=10**-8) assert_allclose(b, expected[1], atol=10**-8) def test_get_axes(): - wcsinfo = {'CTYPE': np.array(['MRSAL1A', 'MRSBE1A', 'WAVE'])} + wcsinfo = {"CTYPE": np.array(["MRSAL1A", "MRSBE1A", "WAVE"])} cel, spec, other = gwutils.get_axes(wcsinfo) assert not cel assert spec == [2] assert other == [0, 1] - wcsinfo = {'CTYPE': np.array(['RA---TAN', 'WAVE', 'DEC--TAN'])} + wcsinfo = {"CTYPE": np.array(["RA---TAN", "WAVE", "DEC--TAN"])} cel, spec, other = gwutils.get_axes(wcsinfo) assert cel == [0, 2] assert spec == [1] @@ -93,9 +129,9 @@ def test_get_axes(): def test_get_values(): args = 2 * u.cm - units=(u.m, ) + units = (u.m,) res = gwutils.get_values(units, args) - assert res == [.02] + assert res == [0.02] res = gwutils.get_values(None, args) assert res == [2] diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 86476350..c4f6fa03 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -19,7 +19,7 @@ import asdf from gwcs import wcs -from gwcs.wcstools import (wcs_from_fiducial, grid_from_bounding_box, wcs_from_points) +from gwcs.wcstools import wcs_from_fiducial, grid_from_bounding_box, wcs_from_points from gwcs import coordinate_frames as cf from gwcs.utils import CoordinateFrameError from gwcs.tests.utils import _gwcs_from_hst_fits_wcs @@ -34,17 +34,29 @@ m2 = models.Scale(2) & models.Scale(-2) m = m1 | m2 -icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='icrs', unit=(u.deg, u.deg)) -detector = cf.Frame2D(name='detector', axes_order=(0, 1)) -focal = cf.Frame2D(name='focal', axes_order=(0, 1), unit=(u.m, u.m)) -spec = cf.SpectralFrame(name='wave', unit=[u.m, ], axes_order=(2, ), axes_names=('lambda', )) -time = cf.TemporalFrame(name='time', unit=[u.s, ], axes_order=(3, ), axes_names=('time', ), reference_frame=Time("2020-01-01")) +icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name="icrs", unit=(u.deg, u.deg)) +detector = cf.Frame2D(name="detector", axes_order=(0, 1)) +focal = cf.Frame2D(name="focal", axes_order=(0, 1), unit=(u.m, u.m)) +spec = cf.SpectralFrame( + name="wave", + unit=[ + u.m, + ], + axes_order=(2,), + axes_names=("lambda",), +) +time = cf.TemporalFrame( + name="time", + unit=[ + u.s, + ], + axes_order=(3,), + axes_names=("time",), + reference_frame=Time("2020-01-01"), +) stokes = cf.StokesFrame(axes_order=(2,)) -pipe = [wcs.Step(detector, m1), - wcs.Step(focal, m2), - wcs.Step(icrs, None) - ] +pipe = [wcs.Step(detector, m1), wcs.Step(focal, m2), wcs.Step(icrs, None)] # Create some data. nx, ny = (5, 2) @@ -56,7 +68,7 @@ def asdf_open_memory_mapping_kwarg(memmap: bool) -> dict: if minversion("asdf", "3.1.0"): return {"memmap": memmap} - else : + else: return {"copy_arrays": not memmap} @@ -68,16 +80,21 @@ def test_create_wcs(): Test initializing a WCS object. """ # use only frame names - gw1 = wcs.WCS(output_frame='icrs', input_frame='detector', forward_transform=m) + gw1 = wcs.WCS(output_frame="icrs", input_frame="detector", forward_transform=m) # omit input_frame - gw2 = wcs.WCS(output_frame='icrs', forward_transform=m) + gw2 = wcs.WCS(output_frame="icrs", forward_transform=m) # use CoordinateFrame objects gw3 = wcs.WCS(output_frame=icrs, input_frame=detector, forward_transform=m) # use a pipeline to initialize pipe = [(detector, m1), (icrs, None)] gw4 = wcs.WCS(forward_transform=pipe) - assert(gw1.available_frames == gw2.available_frames == \ - gw3.available_frames == gw4.available_frames == ['detector', 'icrs']) + assert ( + gw1.available_frames + == gw2.available_frames + == gw3.available_frames + == gw4.available_frames + == ["detector", "icrs"] + ) res = m(1, 2) assert_allclose(gw1(1, 2), res) assert_allclose(gw2(1, 2), res) @@ -89,23 +106,31 @@ def test_init_no_transform(): """ Test initializing a WCS object without a forward_transform. """ - gw = wcs.WCS(output_frame='icrs') + gw = wcs.WCS(output_frame="icrs") assert len(gw._pipeline) == 2 assert gw.pipeline[0].frame == "detector" - with pytest.warns(DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated."): + with pytest.warns( + DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated." + ): assert gw.pipeline[0][0] == "detector" assert gw.pipeline[1].frame == "icrs" - with pytest.warns(DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated."): + with pytest.warns( + DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated." + ): assert gw.pipeline[1][0] == "icrs" - assert np.isin(gw.available_frames, ['detector', 'icrs']).all() + assert np.isin(gw.available_frames, ["detector", "icrs"]).all() gw = wcs.WCS(output_frame=icrs, input_frame=detector) assert gw._pipeline[0].frame == "detector" - with pytest.warns(DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated."): + with pytest.warns( + DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated." + ): assert gw._pipeline[0][0] == "detector" assert gw._pipeline[1].frame == "icrs" - with pytest.warns(DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated."): + with pytest.warns( + DeprecationWarning, match="Indexing a WCS.pipeline step is deprecated." + ): assert gw._pipeline[1][0] == "icrs" - assert np.isin(gw.available_frames, ['detector', 'icrs']).all() + assert np.isin(gw.available_frames, ["detector", "icrs"]).all() with pytest.raises(NotImplementedError): gw(1, 2) @@ -119,65 +144,65 @@ def test_init_no_output_frame(): def test_insert_transform(): - """ Test inserting a transform.""" - gw = wcs.WCS(output_frame='icrs', forward_transform=m1) + """Test inserting a transform.""" + gw = wcs.WCS(output_frame="icrs", forward_transform=m1) assert_allclose(gw.forward_transform(1, 2), m1(1, 2)) - gw.insert_transform(frame='icrs', transform=m2) + gw.insert_transform(frame="icrs", transform=m2) assert_allclose(gw.forward_transform(1, 2), (m1 | m2)(1, 2)) def test_insert_frame(): - """ Test inserting a frame into an existing pipeline """ + """Test inserting a frame into an existing pipeline""" w = wcs.WCS(pipe[:]) original_result = w(1, 2) mnew = models.Shift(1) & models.Shift(1) - new_frame = cf.Frame2D(name='new') + new_frame = cf.Frame2D(name="new") # Insert at the beginning w.insert_frame(new_frame, mnew, w.input_frame) assert_allclose(w(0, 1), original_result) - tr = w.get_transform('detector', w.output_frame) + tr = w.get_transform("detector", w.output_frame) assert_allclose(tr(1, 2), original_result) # Insert at the end w = wcs.WCS(pipe[:]) with pytest.raises(ValueError, match=r"New coordinate frame.*"): - w.insert_frame('not a frame', mnew, new_frame) + w.insert_frame("not a frame", mnew, new_frame) - w.insert_frame('icrs', mnew, new_frame) + w.insert_frame("icrs", mnew, new_frame) assert_allclose([x - 1 for x in w(1, 2)], original_result) - tr = w.get_transform('detector', 'icrs') + tr = w.get_transform("detector", "icrs") assert_allclose(tr(1, 2), original_result) # Force error by trying same operation with pytest.raises(ValueError, match=r".*both frames.*"): - w.insert_frame('icrs', mnew, new_frame) + w.insert_frame("icrs", mnew, new_frame) def test_set_transform(): - """ Test setting a transform between two frames in the pipeline.""" + """Test setting a transform between two frames in the pipeline.""" w = wcs.WCS(forward_transform=pipe[:]) - w.set_transform('detector', 'focal', models.Identity(2)) + w.set_transform("detector", "focal", models.Identity(2)) assert_allclose(w(1, 1), (2, -2)) with pytest.raises(CoordinateFrameError): - w.set_transform('detector1', 'focal', models.Identity(2)) + w.set_transform("detector1", "focal", models.Identity(2)) with pytest.raises(CoordinateFrameError): - w.set_transform('detector', 'focal1', models.Identity(2)) + w.set_transform("detector", "focal1", models.Identity(2)) def test_get_transform(): - """ Test getting a transform between two frames in the pipeline.""" + """Test getting a transform between two frames in the pipeline.""" w = wcs.WCS(pipe[:]) - tr_forward = w.get_transform('detector', 'focal') - tr_back = w.get_transform('icrs', 'detector') + tr_forward = w.get_transform("detector", "focal") + tr_back = w.get_transform("icrs", "detector") x, y = 1, 2 fx, fy = tr_forward(1, 2) assert_allclose(w.pipeline[0].transform(x, y), (fx, fy)) assert_allclose(w.pipeline[0].transform(x, y), (fx, fy)) assert_allclose((x, y), tr_back(*w(x, y))) - assert(w.get_transform('detector', 'detector') is None) + assert w.get_transform("detector", "detector") is None def test_backward_transform(): @@ -187,13 +212,13 @@ def test_backward_transform(): """ # Test that an error is raised when one of the models has not inverse. poly = models.Polynomial1D(1, c0=4) - w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame='sky') + w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame="sky") with pytest.raises(NotImplementedError): w.backward_transform # test backward transform poly.inverse = models.Shift(-4) - w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame='sky') + w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame="sky") assert_allclose(w.backward_transform(1, 2), (-3, 1)) @@ -202,27 +227,32 @@ def test_backward_transform_has_inverse(): Test that backward transform has an inverse, which is the forward transform """ poly = models.Polynomial1D(1, c0=4) - poly.inverse = models.Polynomial1D(1, c0=-3) # this is NOT the actual inverse of poly - w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame='sky') + poly.inverse = models.Polynomial1D( + 1, c0=-3 + ) # this is NOT the actual inverse of poly + w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame="sky") assert_allclose(w.backward_transform.inverse(1, 2), w(1, 2)) def test_from_fiducial_sky(): - sky = coord.SkyCoord(1.63 * u.radian, -72.4 * u.deg, frame='fk5') + sky = coord.SkyCoord(1.63 * u.radian, -72.4 * u.deg, frame="fk5") tan = models.Pix2Sky_TAN() w = wcs_from_fiducial(sky, projection=tan) assert isinstance(w.CelestialFrame.reference_frame, coord.FK5) - assert_allclose(w(.1, .1), (93.7210280925364, -72.29972666307474)) + assert_allclose(w(0.1, 0.1), (93.7210280925364, -72.29972666307474)) def test_from_fiducial_composite(): - sky = coord.SkyCoord(1.63 * u.radian, -72.4 * u.deg, frame='fk5') + sky = coord.SkyCoord(1.63 * u.radian, -72.4 * u.deg, frame="fk5") tan = models.Pix2Sky_TAN() spec = cf.SpectralFrame(unit=(u.micron,), axes_order=(0,)) - celestial = cf.CelestialFrame(reference_frame=sky.frame, unit=(sky.spherical.lon.unit, - sky.spherical.lat.unit), axes_order=(1, 2)) - coord_frame = cf.CompositeFrame([spec, celestial], name='cube_frame') - w = wcs_from_fiducial([.5, sky], coord_frame, projection=tan) + celestial = cf.CelestialFrame( + reference_frame=sky.frame, + unit=(sky.spherical.lon.unit, sky.spherical.lat.unit), + axes_order=(1, 2), + ) + coord_frame = cf.CompositeFrame([spec, celestial], name="cube_frame") + w = wcs_from_fiducial([0.5, sky], coord_frame, projection=tan) assert isinstance(w.cube_frame.frames[1].reference_frame, coord.FK5) assert_allclose(w(1, 1, 1), (1.5, 96.52373368309931, -71.37420187296995)) # test returning coordinate objects with composite output_frame @@ -233,8 +263,7 @@ def test_from_fiducial_composite(): assert_allclose(res[1].dec.value, -70.30322020351122) trans = models.Shift(10) & models.Scale(2) & models.Shift(-1) - w = wcs_from_fiducial([.5, sky], coord_frame, projection=tan, - transform=trans) + w = wcs_from_fiducial([0.5, sky], coord_frame, projection=tan, transform=trans) assert_allclose(w(1, 1, 1), (11.5, 99.97738475762152, -72.29039139739766)) # test coordinate object output @@ -245,19 +274,19 @@ def test_from_fiducial_composite(): def test_from_fiducial_frame2d(): fiducial = (34.5, 12.3) w = wcs_from_fiducial(fiducial, coordinate_frame=cf.Frame2D()) - assert (w.output_frame.name == 'Frame2D') + assert w.output_frame.name == "Frame2D" assert_allclose(w(1, 1), (35.5, 13.3)) def test_bounding_box(): trans3 = models.Shift(10) & models.Scale(2) & models.Shift(-1) - pipeline = [('detector', trans3), ('sky', None)] + pipeline = [("detector", trans3), ("sky", None)] w = wcs.WCS(pipeline) bb = ((-1, 10), (6, 15)) with pytest.raises(ValueError): w.bounding_box = bb trans2 = models.Shift(10) & models.Scale(2) - pipeline = [('detector', trans2), ('sky', None)] + pipeline = [("detector", trans2), ("sky", None)] w = wcs.WCS(pipeline) w.bounding_box = bb assert w.bounding_box == w.forward_transform.bounding_box @@ -273,17 +302,17 @@ def test_bounding_box(): def test_bounding_box_units(): # Test that bounding_box with quantities can be assigned and evaluates bb = ((1 * u.pix, 5 * u.pix), (2 * u.pix, 6 * u.pix)) - trans = models.Shift(10 * u .pix) & models.Shift(2 * u.pix) - pipeline = [('detector', trans), ('sky', None)] + trans = models.Shift(10 * u.pix) & models.Shift(2 * u.pix) + pipeline = [("detector", trans), ("sky", None)] w = wcs.WCS(pipeline) w.bounding_box = bb - world = w(-1*u.pix, -1*u.pix) + world = w(-1 * u.pix, -1 * u.pix) assert_allclose(world, (np.nan, np.nan)) def test_compound_bounding_box(): trans3 = models.Shift(10) & models.Scale(2) & models.Shift(-1) - pipeline = [('detector', trans3), ('sky', None)] + pipeline = [("detector", trans3), ("sky", None)] w = wcs.WCS(pipeline) cbb = { 1: ((-1, 10), (6, 15)), @@ -291,9 +320,10 @@ def test_compound_bounding_box(): 3: ((-3, 7), (1, 27)), } # Test attaching a valid bounding box (ignoring input 'x') - w.attach_compound_bounding_box(cbb, [('x',)]) + w.attach_compound_bounding_box(cbb, [("x",)]) from astropy.modeling.bounding_box import CompoundBoundingBox - cbb = CompoundBoundingBox.validate(trans3, cbb, selector_args=[('x',)], order='F') + + cbb = CompoundBoundingBox.validate(trans3, cbb, selector_args=[("x",)], order="F") assert w.bounding_box == cbb assert w.bounding_box is trans3.bounding_box @@ -307,30 +337,28 @@ def test_compound_bounding_box(): # Test attaching a invalid bounding box (not ignoring input 'x') with pytest.raises(ValueError): - w.attach_compound_bounding_box(cbb, [('x', False)]) + w.attach_compound_bounding_box(cbb, [("x", False)]) # Test that bounding_box with quantities can be assigned and evaluates - trans = models.Shift(10 * u .pix) & models.Shift(2 * u.pix) - pipeline = [('detector', trans), ('sky', None)] + trans = models.Shift(10 * u.pix) & models.Shift(2 * u.pix) + pipeline = [("detector", trans), ("sky", None)] w = wcs.WCS(pipeline) - cbb = { - 1 * u.pix: (1 * u.pix, 5 * u.pix), - 2 * u.pix: (2 * u.pix, 6 * u.pix) - } - w.attach_compound_bounding_box(cbb, [('x1',)]) + cbb = {1 * u.pix: (1 * u.pix, 5 * u.pix), 2 * u.pix: (2 * u.pix, 6 * u.pix)} + w.attach_compound_bounding_box(cbb, [("x1",)]) from astropy.modeling.bounding_box import CompoundBoundingBox - cbb = CompoundBoundingBox.validate(trans, cbb, selector_args=[('x1',)], order='F') + + cbb = CompoundBoundingBox.validate(trans, cbb, selector_args=[("x1",)], order="F") assert w.bounding_box == cbb assert w.bounding_box is trans.bounding_box - assert_allclose(w(-1*u.pix, 1*u.pix), (np.nan, np.nan)) - assert_allclose(w(7*u.pix, 2*u.pix), (np.nan, np.nan)) + assert_allclose(w(-1 * u.pix, 1 * u.pix), (np.nan, np.nan)) + assert_allclose(w(7 * u.pix, 2 * u.pix), (np.nan, np.nan)) def test_grid_from_bounding_box(): bb = ((-1, 9.9), (6.5, 15)) - x, y = grid_from_bounding_box(bb, step=[.1, .5], center=False) + x, y = grid_from_bounding_box(bb, step=[0.1, 0.5], center=False) assert_allclose(x[:, 0], -1) assert_allclose(x[:, -1], 9.9) assert_allclose(y[0], 6.5) @@ -339,8 +367,8 @@ def test_grid_from_bounding_box(): def test_grid_from_bounding_box_1d(): # Test 1D case - x = grid_from_bounding_box((-.5, 4.5)) - assert_allclose(x, [0., 1., 2., 3., 4.]) + x = grid_from_bounding_box((-0.5, 4.5)) + assert_allclose(x, [0.0, 1.0, 2.0, 3.0, 4.0]) def test_grid_from_bounding_box_step(): @@ -353,6 +381,7 @@ def test_grid_from_bounding_box_step(): with pytest.raises(ValueError): grid_from_bounding_box(bb, step=(1, 2, 1)) + def test_grid_from_model_bounding_box(): bbox = ((-1, 1), (0, 1)) # Truth grid @@ -386,14 +415,14 @@ def test_grid_from_compound_bounding_box(): bind_compound_bounding_box( model, { - (200,) : { + (200,): { "x": bbox[0], "y": bbox[1], }, - (300,) :{ + (300,): { "x": (-2, 2), "y": (0, 2), - } + }, }, [("slit_name",)], order="F", @@ -403,9 +432,14 @@ def test_grid_from_compound_bounding_box(): assert np.all(grid == grid_truth) # Capture errors - with pytest.raises(ValueError, match=r"Cannot use selector with a non-CompoundBoundingBox"): + with pytest.raises( + ValueError, match=r"Cannot use selector with a non-CompoundBoundingBox" + ): grid_from_bounding_box(model.bounding_box[(300,)], selector=(300,)) - with pytest.raises(ValueError, match=r"selector must be set when bounding_box is a CompoundBoundingBox"): + with pytest.raises( + ValueError, + match=r"selector must be set when bounding_box is a CompoundBoundingBox", + ): grid_from_bounding_box(model.bounding_box) @@ -422,8 +456,8 @@ def test_wcs_from_points(): assert len(caught_warnings) == 2 y, x = np.mgrid[:2046:20j, :4023:10j] ra, dec = w.wcs_pix2world(x, y, 1) - fiducial = coord.SkyCoord(ra.mean()*u.deg, dec.mean()*u.deg, frame="icrs") - world_coords = coord.SkyCoord(ra, dec, unit = (u.deg, u.deg)) + fiducial = coord.SkyCoord(ra.mean() * u.deg, dec.mean() * u.deg, frame="icrs") + world_coords = coord.SkyCoord(ra, dec, unit=(u.deg, u.deg)) w = wcs_from_points(xy=(x, y), world_coords=world_coords, proj_point=fiducial) newra, newdec = w(x, y) assert_allclose(newra, ra) @@ -431,11 +465,11 @@ def test_wcs_from_points(): n = np.random.randn(ra.size) n.shape = ra.shape - nra = n * 10 ** -2 - ndec = n * 10 ** -2 - w = wcs_from_points(xy=(x + nra, y + ndec), - world_coords=world_coords, - proj_point=fiducial) + nra = n * 10**-2 + ndec = n * 10**-2 + w = wcs_from_points( + xy=(x + nra, y + ndec), world_coords=world_coords, proj_point=fiducial + ) newra, newdec = w(x, y) assert_allclose(newra, ra, atol=10**-6) assert_allclose(newdec, dec, atol=10**-6) @@ -466,8 +500,20 @@ def test_bounding_box_eval(): Tests evaluation with and without respecting the bounding_box. """ trans3 = models.Shift(10) & models.Scale(2) & models.Shift(-1) - pipeline = [(cf.CoordinateFrame(naxes=1, axes_type=("PIXEL",), axes_order=(0,), name='detector'), trans3), - (cf.CoordinateFrame(naxes=1, axes_type=("SPATIAL",), axes_order=(0,), name='sky'), None)] + pipeline = [ + ( + cf.CoordinateFrame( + naxes=1, axes_type=("PIXEL",), axes_order=(0,), name="detector" + ), + trans3, + ), + ( + cf.CoordinateFrame( + naxes=1, axes_type=("SPATIAL",), axes_order=(0,), name="sky" + ), + None, + ), + ] w = wcs.WCS(pipeline) w.bounding_box = ((-1, 10), (6, 15), (4.3, 6.9)) @@ -479,17 +525,19 @@ def test_bounding_box_eval(): # test scalar inside bbox assert_allclose(w(1, 7, 5), [11, 14, 4]) # test arrays - assert_allclose(w([1, 1], [7, 7], [3, 5]), [[np.nan, 11], [np.nan, 14], [np.nan, 4]]) + assert_allclose( + w([1, 1], [7, 7], [3, 5]), [[np.nan, 11], [np.nan, 14], [np.nan, 4]] + ) # test ``transform`` method - assert_allclose(w.transform('detector', 'sky', 1, 7, 3), [np.nan, np.nan, np.nan]) + assert_allclose(w.transform("detector", "sky", 1, 7, 3), [np.nan, np.nan, np.nan]) def test_format_output(): points = np.arange(5) values = np.array([1.5, 3.4, 6.7, 7, 32]) t = models.Tabular1D(points, values) - pipe = [('detector', t), ('world', None)] + pipe = [("detector", t), ("world", None)] w = wcs.WCS(pipe) assert_allclose(w(1), 3.4) assert_allclose(w([1, 2]), [3.4, 6.7]) @@ -498,37 +546,51 @@ def test_format_output(): def test_available_frames(): w = wcs.WCS(pipe) - assert w.available_frames == ['detector', 'focal', 'icrs'] + assert w.available_frames == ["detector", "focal", "icrs"] def test_footprint(): - icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS(), - axes_order=(0, 1)) - spec = cf.SpectralFrame(name='freq', unit=[u.Hz, ], axes_order=(2, )) + icrs = cf.CelestialFrame( + name="icrs", reference_frame=coord.ICRS(), axes_order=(0, 1) + ) + spec = cf.SpectralFrame( + name="freq", + unit=[ + u.Hz, + ], + axes_order=(2,), + ) world = cf.CompositeFrame([icrs, spec]) transform = (models.Shift(10) & models.Shift(-1)) & models.Scale(2) - pipe = [('det', transform), (world, None)] + pipe = [("det", transform), (world, None)] w = wcs.WCS(pipe) with pytest.raises(TypeError): w.footprint() - w.bounding_box = ((1,5), (1,3), (1, 6)) - - assert_equal(w.footprint(), np.array([[11, 0, 2], - [11, 0, 12], - [11, 2, 2], - [11, 2, 12], - [15, 0, 2], - [15, 0, 12], - [15, 2, 2], - [15, 2, 12]])) - assert_equal(w.footprint(axis_type='spatial'), np.array([[11., 0.], - [11., 2.], - [15., 2.], - [15., 0.]])) + w.bounding_box = ((1, 5), (1, 3), (1, 6)) + + assert_equal( + w.footprint(), + np.array( + [ + [11, 0, 2], + [11, 0, 12], + [11, 2, 2], + [11, 2, 12], + [15, 0, 2], + [15, 0, 12], + [15, 2, 2], + [15, 2, 12], + ] + ), + ) + assert_equal( + w.footprint(axis_type="spatial"), + np.array([[11.0, 0.0], [11.0, 2.0], [15.0, 2.0], [15.0, 0.0]]), + ) - assert_equal(w.footprint(axis_type='spectral'), np.array([2, 12])) + assert_equal(w.footprint(axis_type="spectral"), np.array([2, 12])) def test_high_level_api(): @@ -537,9 +599,12 @@ def test_high_level_api(): """ output_frame = cf.CompositeFrame(frames=[icrs, spec, time]) transform = m1 & models.Scale(1.5) & models.Scale(2) - det = cf.CoordinateFrame(naxes=4, unit=(u.pix, u.pix, u.pix, u.pix), - axes_order=(0, 1, 2, 3), - axes_type=('length', 'length', 'length', 'length')) + det = cf.CoordinateFrame( + naxes=4, + unit=(u.pix, u.pix, u.pix, u.pix), + axes_order=(0, 1, 2, 3), + axes_type=("length", "length", "length", "length"), + ) w = wcs.WCS(forward_transform=transform, output_frame=output_frame, input_frame=det) wrapped = wcsapi.HighLevelWCSWrapper(w) @@ -574,7 +639,9 @@ def test_high_level_api(): class TestImaging(object): def setup_class(self): - hdr = fits.Header.fromtextfile(os.path.join(data_path, "acs.hdr"), endcard=False) + hdr = fits.Header.fromtextfile( + os.path.join(data_path, "acs.hdr"), endcard=False + ) with pytest.warns(astwcs.FITSFixedWarning) as caught_warnings: # this raises a warning unimportant for this testing the pix2world # FITSFixedWarning(u'The WCS transformation has more axes (2) than @@ -583,42 +650,43 @@ def setup_class(self): # 'Set MJD-OBS to 53436.000000 from DATE-OBS'. [astropy.wcs.wcs] self.fitsw = astwcs.WCS(hdr) assert len(caught_warnings) == 2 - a_coeff = hdr['A_*'] - a_order = a_coeff.pop('A_ORDER') - b_coeff = hdr['B_*'] - b_order = b_coeff.pop('B_ORDER') + a_coeff = hdr["A_*"] + a_order = a_coeff.pop("A_ORDER") + b_coeff = hdr["B_*"] + b_order = b_coeff.pop("B_ORDER") - crpix = [hdr['CRPIX1'], hdr['CRPIX2']] + crpix = [hdr["CRPIX1"], hdr["CRPIX2"]] distortion = models.SIP( - crpix, a_order, b_order, a_coeff, b_coeff, name='sip_distorion') + models.Identity(2) + crpix, a_order, b_order, a_coeff, b_coeff, name="sip_distorion" + ) + models.Identity(2) - cdmat = np.array([[hdr['CD1_1'], hdr['CD1_2']], [hdr['CD2_1'], hdr['CD2_2']]]) - aff = models.AffineTransformation2D(matrix=cdmat, name='rotation') + cdmat = np.array([[hdr["CD1_1"], hdr["CD1_2"]], [hdr["CD2_1"], hdr["CD2_2"]]]) + aff = models.AffineTransformation2D(matrix=cdmat, name="rotation") - offx = models.Shift(-hdr['CRPIX1'], name='x_translation') - offy = models.Shift(-hdr['CRPIX2'], name='y_translation') + offx = models.Shift(-hdr["CRPIX1"], name="x_translation") + offy = models.Shift(-hdr["CRPIX2"], name="y_translation") wcslin = (offx & offy) | aff - phi = hdr['CRVAL1'] - lon = hdr['CRVAL2'] + phi = hdr["CRVAL1"] + lon = hdr["CRVAL2"] theta = 180 - n2c = models.RotateNative2Celestial(phi, lon, theta, name='sky_rotation') + n2c = models.RotateNative2Celestial(phi, lon, theta, name="sky_rotation") - tan = models.Pix2Sky_TAN(name='tangent_projection') - sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky') - det = cf.Frame2D(name='detector') - focal = cf.Frame2D(name='focal') + tan = models.Pix2Sky_TAN(name="tangent_projection") + sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name="sky") + det = cf.Frame2D(name="detector") + focal = cf.Frame2D(name="focal") wcs_forward = wcslin | tan | n2c pipeline = [ wcs.Step(det, distortion), wcs.Step(focal, wcs_forward), - wcs.Step(sky_cs, None) + wcs.Step(sky_cs, None), ] - self.wcs = wcs.WCS(input_frame=det, - output_frame=sky_cs, - forward_transform=pipeline) + self.wcs = wcs.WCS( + input_frame=det, output_frame=sky_cs, forward_transform=pipeline + ) self.xv, self.yv = xv, yv @@ -626,13 +694,13 @@ def test_distortion(self): sipx, sipy = self.fitsw.sip_pix2foc(self.xv, self.yv, 1) sipx = np.array(sipx) + 2048 sipy = np.array(sipy) + 1024 - sip_coord = self.wcs.get_transform('detector', 'focal')(self.xv, self.yv) + sip_coord = self.wcs.get_transform("detector", "focal")(self.xv, self.yv) assert_allclose(sipx, sip_coord[0]) assert_allclose(sipy, sip_coord[1]) def test_wcslinear(self): ra, dec = self.fitsw.wcs_pix2world(self.xv, self.yv, 1) - sky = self.wcs.get_transform('focal', 'sky')(self.xv, self.yv) + sky = self.wcs.get_transform("focal", "sky")(self.xv, self.yv) assert_allclose(ra, sky[0]) assert_allclose(dec, sky[1]) @@ -643,15 +711,17 @@ def test_forward(self): assert_allclose(sky_coord[1], dec) def test_backward(self): - transform = self.wcs.get_transform(from_frame='focal', to_frame=self.wcs.output_frame) - sky_coord = self.wcs.transform('focal', self.wcs.output_frame, self.xv, self.yv) + transform = self.wcs.get_transform( + from_frame="focal", to_frame=self.wcs.output_frame + ) + sky_coord = self.wcs.transform("focal", self.wcs.output_frame, self.xv, self.yv) px_coord = transform.inverse(*sky_coord) assert_allclose(px_coord[0], self.xv, atol=10**-6) assert_allclose(px_coord[1], self.yv, atol=10**-6) def test_footprint(self): bb = ((1, 4096), (1, 2048)) - footprint = (self.wcs.footprint(bb)) + footprint = self.wcs.footprint(bb) fits_footprint = self.fitsw.calc_footprint(axes=(4096, 2048)) assert_allclose(footprint, fits_footprint) @@ -661,16 +731,18 @@ def test_inverse(self): def test_back_coordinates(self): sky_coord = self.wcs(1, 2, with_units=True) - res = self.wcs.transform('sky', 'focal', sky_coord, with_units=False) - assert_allclose(res, self.wcs.get_transform('detector', 'focal')(1, 2)) + res = self.wcs.transform("sky", "focal", sky_coord, with_units=False) + assert_allclose(res, self.wcs.get_transform("detector", "focal")(1, 2)) def test_units(self): - assert(self.wcs.unit == (u.degree, u.degree)) + assert self.wcs.unit == (u.degree, u.degree) def test_get_transform(self): with pytest.raises(wcs.CoordinateFrameError): - assert(self.wcs.get_transform('x_translation', 'sky_rotation').submodel_names == \ - self.wcs.forward_transform[1:].submodel_names) + assert ( + self.wcs.get_transform("x_translation", "sky_rotation").submodel_names + == self.wcs.forward_transform[1:].submodel_names + ) def test_pixel_to_world(self): sky_coord = self.wcs.pixel_to_world(self.xv, self.yv) @@ -684,9 +756,14 @@ def test_to_fits_sip(): y, x = np.mgrid[:1024:10, :1024:10] xflat = np.ravel(x[1:-1, 1:-1]) yflat = np.ravel(y[1:-1, 1:-1]) - fn = os.path.join(data_path, 'miriwcs.asdf') - with asdf.open(fn, lazy_load=False, ignore_missing_extensions=True, **asdf_open_memory_mapping_kwarg(memmap=False)) as af: - miriwcs = af.tree['wcs'] + fn = os.path.join(data_path, "miriwcs.asdf") + with asdf.open( + fn, + lazy_load=False, + ignore_missing_extensions=True, + **asdf_open_memory_mapping_kwarg(memmap=False), + ) as af: + miriwcs = af.tree["wcs"] bounding_box = ((0, 1024), (0, 1024)) mirisip = miriwcs.to_fits_sip(bounding_box, max_inv_pix_error=0.1, verbose=True) fitssip = astwcs.WCS(mirisip) @@ -708,7 +785,10 @@ def test_to_fits_sip(): miriwcs.bounding_box = None mirisip = miriwcs.to_fits_sip(bounding_box=None, max_inv_pix_error=0.1) -@pytest.mark.parametrize('matrix_type', ['CD', 'PC-CDELT1', 'PC-SUM1', 'PC-DET1', 'PC-SCALE']) + +@pytest.mark.parametrize( + "matrix_type", ["CD", "PC-CDELT1", "PC-SUM1", "PC-DET1", "PC-SCALE"] +) def test_to_fits_sip_pc_normalization(gwcs_simple_imaging_units, matrix_type): y, x = np.mgrid[:1024:10, :1024:10] xflat = np.ravel(x[1:-1, 1:-1]) @@ -717,25 +797,25 @@ def test_to_fits_sip_pc_normalization(gwcs_simple_imaging_units, matrix_type): # create a simple imaging WCS without distortions: cdmat = np.array([[1.29e-5, 5.95e-6], [5.02e-6, -1.26e-5]]) - aff = models.AffineTransformation2D(matrix=cdmat, name='rotation') + aff = models.AffineTransformation2D(matrix=cdmat, name="rotation") - offx = models.Shift(-501, name='x_translation') - offy = models.Shift(-501, name='y_translation') + offx = models.Shift(-501, name="x_translation") + offy = models.Shift(-501, name="y_translation") wcslin = (offx & offy) | aff - n2c = models.RotateNative2Celestial(5.63, -72.05, 180, name='sky_rotation') - tan = models.Pix2Sky_TAN(name='tangent_projection') + n2c = models.RotateNative2Celestial(5.63, -72.05, 180, name="sky_rotation") + tan = models.Pix2Sky_TAN(name="tangent_projection") wcs_forward = wcslin | tan | n2c - sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky') - pipeline = [('detector', wcs_forward), (sky_cs, None)] + sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name="sky") + pipeline = [("detector", wcs_forward), (sky_cs, None)] wcs_lin = wcs.WCS( - input_frame=cf.Frame2D(name='detector'), + input_frame=cf.Frame2D(name="detector"), output_frame=sky_cs, - forward_transform=pipeline + forward_transform=pipeline, ) _, _, celestial_group = wcs_lin._separable_groups(detect_celestial=True) @@ -749,9 +829,9 @@ def test_to_fits_sip_pc_normalization(gwcs_simple_imaging_units, matrix_type): inv_degree=None, npoints=32, crpix=None, - projection='TAN', + projection="TAN", matrix_type=matrix_type, - verbose=True + verbose=True, ) fitssip = astwcs.WCS(fits_wcs) @@ -773,12 +853,12 @@ def test_to_fits_sip_composite_frame(gwcs_cube_with_separable_spectral): ra_axis = 3 - dec_axis fw_hdr = w.to_fits_sip() - assert fw_hdr[f'CTYPE{dec_axis}'] == 'DEC--TAN' - assert fw_hdr[f'CTYPE{ra_axis}'] == 'RA---TAN' - assert fw_hdr['WCSAXES'] == 2 - assert fw_hdr['NAXIS'] == 2 - assert fw_hdr['NAXIS1'] == 128 - assert fw_hdr['NAXIS2'] == 64 + assert fw_hdr[f"CTYPE{dec_axis}"] == "DEC--TAN" + assert fw_hdr[f"CTYPE{ra_axis}"] == "RA---TAN" + assert fw_hdr["WCSAXES"] == 2 + assert fw_hdr["NAXIS"] == 2 + assert fw_hdr["NAXIS1"] == 128 + assert fw_hdr["NAXIS2"] == 64 fw = astwcs.WCS(fw_hdr) gskyval = w.pixel_to_world(1, 60, 55)[1] @@ -791,42 +871,42 @@ def test_to_fits_sip_composite_frame_galactic(gwcs_3d_galactic_spectral): w = gwcs_3d_galactic_spectral fw_hdr = w.to_fits_sip() - assert fw_hdr['CTYPE1'] == 'GLAT-TAN' + assert fw_hdr["CTYPE1"] == "GLAT-TAN" fw = astwcs.WCS(fw_hdr) gskyval = w.pixel_to_world(7, 8, 9)[0] - assert np.allclose([gskyval.b.value, gskyval.l.value], - fw.all_pix2world(7, 9, 0), atol=1e-3) + assert np.allclose( + [gskyval.b.value, gskyval.l.value], fw.all_pix2world(7, 9, 0), atol=1e-3 + ) def test_to_fits_sip_composite_frame_keep_axis(gwcs_cube_with_separable_spectral): from inspect import signature, Parameter + w, axes_order = gwcs_cube_with_separable_spectral _, _, celestial_group = w._separable_groups(detect_celestial=True) pars = signature(w.to_fits_sip).parameters - kwargs = { - k: v.default for k, v in pars.items() if v.default is not Parameter.empty - } - kwargs['matrix_type'] = 'CD' + kwargs = {k: v.default for k, v in pars.items() if v.default is not Parameter.empty} + kwargs["matrix_type"] = "CD" fw_hdr = w._to_fits_sip( - celestial_group=celestial_group, - keep_axis_position=True, - **kwargs + celestial_group=celestial_group, keep_axis_position=True, **kwargs ) ra_axis = axes_order.index(0) + 1 dec_axis = axes_order.index(1) + 1 - fw_hdr['CD1_3'] = 1 - fw_hdr['CRPIX3'] = 1 + fw_hdr["CD1_3"] = 1 + fw_hdr["CRPIX3"] = 1 - assert fw_hdr[f'CTYPE{dec_axis}'] == 'DEC--TAN' - assert fw_hdr[f'CTYPE{ra_axis}'] == 'RA---TAN' - assert fw_hdr['WCSAXES'] == 2 + assert fw_hdr[f"CTYPE{dec_axis}"] == "DEC--TAN" + assert fw_hdr[f"CTYPE{ra_axis}"] == "RA---TAN" + assert fw_hdr["WCSAXES"] == 2 - with pytest.warns(astwcs.FITSFixedWarning, match='The WCS transformation has more axes'): + with pytest.warns( + astwcs.FITSFixedWarning, match="The WCS transformation has more axes" + ): # this raises a warning unimportant for this testing the pix2world # FITSFixedWarning(u'The WCS transformation has more axes (3) than # the image it is associated with (2)') @@ -851,15 +931,11 @@ def test_to_fits_tab_cube(gwcs_3d_galactic_spectral): # FITS WCS -TAB: hdr, bt = w.to_fits_tab() - hdulist = fits.HDUList( - [fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2, )), hdr), bt] - ) + hdulist = fits.HDUList([fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2,)), hdr), bt]) fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) hdr, bt = w.to_fits_tab(bounding_box=w.bounding_box) - hdulist = fits.HDUList( - [fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2, )), hdr), bt] - ) + hdulist = fits.HDUList([fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2,)), hdr), bt]) fits_wcs_user_bb = astwcs.WCS(hdulist[0].header, hdulist) # test points: @@ -870,19 +946,22 @@ def test_to_fits_tab_cube(gwcs_3d_galactic_spectral): z = zmin + (zmax - zmin) * np.random.random(100) # test: - assert np.allclose(w(x, y, z), fits_wcs.wcs_pix2world(x, y, z, 0), - rtol=1e-6, atol=1e-7) + assert np.allclose( + w(x, y, z), fits_wcs.wcs_pix2world(x, y, z, 0), rtol=1e-6, atol=1e-7 + ) + + assert np.allclose( + w(x, y, z), fits_wcs_user_bb.wcs_pix2world(x, y, z, 0), rtol=1e-6, atol=1e-7 + ) - assert np.allclose(w(x, y, z), fits_wcs_user_bb.wcs_pix2world(x, y, z, 0), - rtol=1e-6, atol=1e-7) -@pytest.mark.filterwarnings('ignore:.*The WCS transformation has more axes.*') +@pytest.mark.filterwarnings("ignore:.*The WCS transformation has more axes.*") def test_to_fits_tab_7d(gwcs_7d_complex_mapping): # gWCS: w = gwcs_7d_complex_mapping # create FITS headers and -TAB headers - hdr, bt = w.to_fits(projection='TAN') + hdr, bt = w.to_fits(projection="TAN") # create FITS WCS object: hdus = [fits.PrimaryHDU(np.zeros(w.array_shape), hdr)] @@ -944,18 +1023,20 @@ def test_to_fits_no_sip_used(gwcs_spec_cel_time_4d): w = gwcs_spec_cel_time_4d # create FITS headers and -TAB headers - with pytest.warns(UserWarning, match='SIP distortion is not supported when the number'): + with pytest.warns( + UserWarning, match="SIP distortion is not supported when the number" + ): # UserWarning: SIP distortion is not supported when the number # of axes in WCS is larger than 2. Setting 'degree' # to 1 and 'max_inv_pix_error' to None. hdr, _ = w.to_fits(degree=3) # check that FITS WCS is not using SIP - assert not hdr['?_ORDER'] - assert not hdr['?P_ORDER'] - assert not hdr['A_?_?'] - assert not hdr['B_?_?'] - assert not any(s.endswith('-SIP') for s in hdr['CTYPE?'].values()) + assert not hdr["?_ORDER"] + assert not hdr["?P_ORDER"] + assert not hdr["A_?_?"] + assert not hdr["B_?_?"] + assert not any(s.endswith("-SIP") for s in hdr["CTYPE?"].values()) def test_to_fits_1D_round_trip(gwcs_1d_spectral): @@ -964,9 +1045,7 @@ def test_to_fits_1D_round_trip(gwcs_1d_spectral): # FITS WCS -SIP (for celestial) and -TAB (for spectral): hdr, bt = w.to_fits() - hdulist = fits.HDUList( - [fits.PrimaryHDU(np.ones(w.array_shape), hdr), bt[0]] - ) + hdulist = fits.HDUList([fits.PrimaryHDU(np.ones(w.array_shape), hdr), bt[0]]) fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) # test points: @@ -988,7 +1067,7 @@ def test_to_fits_sip_tab_cube(gwcs_cube_with_separable_spectral): w, axes_order = gwcs_cube_with_separable_spectral # FITS WCS -SIP (for celestial) and -TAB (for spectral): - hdr, bt = w.to_fits(projection=models.Sky2Pix_TAN(name='TAN')) + hdr, bt = w.to_fits(projection=models.Sky2Pix_TAN(name="TAN")) # create FITS WCS object: hdus = [fits.PrimaryHDU(np.zeros(w.array_shape), hdr)] @@ -1017,7 +1096,7 @@ def test_to_fits_tab_time_cube(gwcs_cube_with_separable_time): w = gwcs_cube_with_separable_time # FITS WCS -SIP (for celestial) and -TAB (for spectral): - hdr, bt = w.to_fits(projection=models.Sky2Pix_TAN(name='TAN')) + hdr, bt = w.to_fits(projection=models.Sky2Pix_TAN(name="TAN")) # create FITS WCS object: hdus = [fits.PrimaryHDU(np.zeros(w.array_shape), hdr)] @@ -1025,7 +1104,7 @@ def test_to_fits_tab_time_cube(gwcs_cube_with_separable_time): hdulist = fits.HDUList(hdus) fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) - assert np.allclose(hdulist[1].data['coordinates'].ravel(), np.arange(128)) + assert np.allclose(hdulist[1].data["coordinates"].ravel(), np.arange(128)) # test points: (xmin, xmax), (ymin, ymax), (zmin, zmax) = w.bounding_box @@ -1040,20 +1119,25 @@ def test_to_fits_tab_time_cube(gwcs_cube_with_separable_time): assert np.allclose(world_crds, fits_wcs.wcs_pix2world(x, y, z, 0)) # test round-tripping: - assert np.allclose((x, y, z), fits_wcs.wcs_world2pix(*world_crds, 0), rtol=1e-5, atol=1e-5) + assert np.allclose( + (x, y, z), fits_wcs.wcs_world2pix(*world_crds, 0), rtol=1e-5, atol=1e-5 + ) def test_to_fits_tab_miri_image(): # gWCS: - fn = os.path.join(data_path, 'miriwcs.asdf') - with asdf.open(fn, lazy_load=False, ignore_missing_extensions=True, **asdf_open_memory_mapping_kwarg(memmap=False)) as af: - w = af.tree['wcs'] + fn = os.path.join(data_path, "miriwcs.asdf") + with asdf.open( + fn, + lazy_load=False, + ignore_missing_extensions=True, + **asdf_open_memory_mapping_kwarg(memmap=False), + ) as af: + w = af.tree["wcs"] # FITS WCS -TAB: hdr, bt = w.to_fits_tab(sampling=0.5) - hdulist = fits.HDUList( - [fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2, )), hdr), bt] - ) + hdulist = fits.HDUList([fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2,)), hdr), bt]) fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) @@ -1064,21 +1148,25 @@ def test_to_fits_tab_miri_image(): y = ymin + (ymax - ymin) * np.random.random(100) # test: - assert np.allclose(w(x, y), fits_wcs.wcs_pix2world(x, y, 0), - rtol=1e-6, atol=1e-7) + assert np.allclose(w(x, y), fits_wcs.wcs_pix2world(x, y, 0), rtol=1e-6, atol=1e-7) def test_to_fits_tab_miri_lrs(): - fn = os.path.join(data_path, 'miri_lrs_wcs.asdf') - with asdf.open(fn, lazy_load=False, ignore_missing_extensions=True, **asdf_open_memory_mapping_kwarg(memmap=False)) as af: - w = af.tree['wcs'] + fn = os.path.join(data_path, "miri_lrs_wcs.asdf") + with asdf.open( + fn, + lazy_load=False, + ignore_missing_extensions=True, + **asdf_open_memory_mapping_kwarg(memmap=False), + ) as af: + w = af.tree["wcs"] # FITS WCS -TAB: hdr, bt = w.to_fits(sampling=0.25) - hdulist = fits.HDUList( - [fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2, )), hdr), bt[0]] - ) - with pytest.warns(astwcs.FITSFixedWarning, match='The WCS transformation has more axes'): + hdulist = fits.HDUList([fits.PrimaryHDU(np.ones(w.pixel_n_dim * (2,)), hdr), bt[0]]) + with pytest.warns( + astwcs.FITSFixedWarning, match="The WCS transformation has more axes" + ): # this raises a warning unimportant for this testing the pix2world # FITSFixedWarning(u'The WCS transformation has more axes (3) than # the image it is associated with (2)') @@ -1095,7 +1183,7 @@ def test_to_fits_tab_miri_lrs(): tab = np.array(fits_wcs.wcs_pix2world(x, y, 0, 0)) m = np.cumprod(np.isfinite(ref), dtype=np.bool_, axis=0) - assert hdr['WCSAXES'] == 3 + assert hdr["WCSAXES"] == 3 assert np.allclose(ref[m], tab[m], rtol=5e-6, atol=5e-6, equal_nan=True) @@ -1103,8 +1191,11 @@ def test_in_image(): # create a 1-dim WCS: w1 = wcs.WCS( [ - (cf.SpectralFrame(name='input', axes_names=('x',), unit=(u.pix,)), models.Scale(2)), - (cf.SpectralFrame(name='output', axes_names=('x'), unit=(u.pix,)), None) + ( + cf.SpectralFrame(name="input", axes_names=("x",), unit=(u.pix,)), + models.Scale(2), + ), + (cf.SpectralFrame(name="output", axes_names=("x"), unit=(u.pix,)), None), ] ) w1.bounding_box = (1, 5) @@ -1118,29 +1209,46 @@ def test_in_image(): ) # create a 2-dim WCS: - w2 = wcs.WCS([(cf.Frame2D(name='input', axes_names=('x', 'y'), unit=(u.pix, u.pix)), - models.Scale(2) & models.Scale(1.5)), - (cf.Frame2D(name='output', axes_names=('x', 'y'), unit=(u.pix, u.pix)), - None)]) + w2 = wcs.WCS( + [ + ( + cf.Frame2D(name="input", axes_names=("x", "y"), unit=(u.pix, u.pix)), + models.Scale(2) & models.Scale(1.5), + ), + ( + cf.Frame2D(name="output", axes_names=("x", "y"), unit=(u.pix, u.pix)), + None, + ), + ] + ) w2.bounding_box = [(1, 100), (2, 20)] assert np.isscalar(w2.in_image(2, 6)) assert not np.isscalar(w2.in_image([2], [6])) - assert (w2.in_image(4, 6)) + assert w2.in_image(4, 6) assert not (w2.in_image(5, 0)) assert np.array_equal( w2.in_image( [[9, 10, 11, 15], [8, 9, 67, 98], [2, 2, np.nan, 102]], - [[9, np.nan, 11, 15], [8, 9, 67, 98], [1, 1, np.nan, -10]] + [[9, np.nan, 11, 15], [8, 9, 67, 98], [1, 1, np.nan, -10]], ), - [[ True, False, True, True], [ True, True, False, False], [False, False, False, False]], + [ + [True, False, True, True], + [True, True, False, False], + [False, False, False, False], + ], ) def test_iter_inv(): - fn = os.path.join(data_path, 'nircamwcs.asdf') - with asdf.open(fn, lazy_load=False, ignore_missing_extensions=True, **asdf_open_memory_mapping_kwarg(memmap=True)) as af: - w = af.tree['wcs'] + fn = os.path.join(data_path, "nircamwcs.asdf") + with asdf.open( + fn, + lazy_load=False, + ignore_missing_extensions=True, + **asdf_open_memory_mapping_kwarg(memmap=True), + ) as af: + w = af.tree["wcs"] # remove analytic/user-supplied inverse: w.pipeline[0].transform.inverse = None w.bounding_box = None @@ -1148,9 +1256,7 @@ def test_iter_inv(): # test single point assert np.allclose((1, 2), w.invert(*w(1, 2))) assert np.allclose( - (np.nan, np.nan), - w.numerical_inverse(*w(np.nan, 2)), - equal_nan=True + (np.nan, np.nan), w.numerical_inverse(*w(np.nan, 2)), equal_nan=True ) # prepare to test a vector of points: @@ -1162,29 +1268,34 @@ def test_iter_inv(): *w(x, y), adaptive=True, detect_divergence=True, - tolerance=1e-4, maxiter=50, - quiet=False + tolerance=1e-4, + maxiter=50, + quiet=False, ) assert np.allclose((x, y), (xp, yp)) - with asdf.open(fn, lazy_load=False, ignore_missing_extensions=True, **asdf_open_memory_mapping_kwarg(memmap=True)) as af: - w = af.tree['wcs'] + with asdf.open( + fn, + lazy_load=False, + ignore_missing_extensions=True, + **asdf_open_memory_mapping_kwarg(memmap=True), + ) as af: + w = af.tree["wcs"] # test single point assert np.allclose((1, 2), w.numerical_inverse(*w(1, 2))) assert np.allclose( - (np.nan, np.nan), - w.numerical_inverse(*w(np.nan, 2)), - equal_nan=True + (np.nan, np.nan), w.numerical_inverse(*w(np.nan, 2)), equal_nan=True ) # don't detect devergence xp, yp = w.numerical_inverse( *w(x, y), adaptive=True, - tolerance=1e-5, maxiter=50, + tolerance=1e-5, + maxiter=50, detect_divergence=False, - quiet=False + quiet=False, ) assert np.allclose((x, y), (xp, yp)) @@ -1194,7 +1305,7 @@ def test_iter_inv(): adaptive=True, detect_divergence=True, maxiter=2, # force not reaching requested accuracy - quiet=False + quiet=False, ) xp, yp = e.value.best_solution.T @@ -1207,7 +1318,7 @@ def test_iter_inv(): adaptive=False, detect_divergence=True, quiet=False, - with_bounding_box=False + with_bounding_box=False, ) assert np.allclose((x, y), (xp, yp)) @@ -1217,10 +1328,11 @@ def test_iter_inv(): xp, yp = w.numerical_inverse( *w(x, y, with_bounding_box=False), adaptive=False, - tolerance=1e-5, maxiter=50, + tolerance=1e-5, + maxiter=50, detect_divergence=True, quiet=False, - with_bounding_box=False + with_bounding_box=False, ) assert np.allclose((x, y), (xp, yp)) @@ -1231,10 +1343,11 @@ def test_iter_inv(): xp, yp = w.numerical_inverse( *w(x, y, with_bounding_box=False), adaptive=False, - tolerance=1e-5, maxiter=50, + tolerance=1e-5, + maxiter=50, detect_divergence=True, quiet=False, - with_bounding_box=False + with_bounding_box=False, ) assert np.allclose((x, y), (xp, yp)) @@ -1252,15 +1365,19 @@ def test_tabular_2d_quantity(): points = [(np.arange(size) - 0) * points_unit for size in shape] kwargs = { - 'bounds_error': False, - 'fill_value': np.nan, - 'method': 'nearest', + "bounds_error": False, + "fill_value": np.nan, + "method": "nearest", } forward = models.Tabular2D(points, data, **kwargs) - input_frame = cf.CoordinateFrame(2, ("PIXEL", "PIXEL"), (0,1), unit=(u.pix, u.pix), name="detector") - output_frame = cf.CoordinateFrame(1, "CUSTOM", (0,), unit=(u.m/u.s,)) - w = wcs.WCS(forward_transform=forward, input_frame=input_frame, output_frame=output_frame) + input_frame = cf.CoordinateFrame( + 2, ("PIXEL", "PIXEL"), (0, 1), unit=(u.pix, u.pix), name="detector" + ) + output_frame = cf.CoordinateFrame(1, "CUSTOM", (0,), unit=(u.m / u.s,)) + w = wcs.WCS( + forward_transform=forward, input_frame=input_frame, output_frame=output_frame + ) bb = w.bounding_box assert all(u.allclose(u.Quantity(b), [0, 2] * u.pix) for b in bb) @@ -1271,11 +1388,11 @@ def test_initialize_wcs_with_list(): # containing both Step() and (frame, transform) tuples # make pipeline consisting of tuples and Steps - shift1 = models.Shift(10 * u .pix) & models.Shift(2 * u.pix) + shift1 = models.Shift(10 * u.pix) & models.Shift(2 * u.pix) shift2 = models.Shift(3 * u.pix) - pipeline = [('detector', shift1), wcs.Step('extra_step', shift2)] + pipeline = [("detector", shift1), wcs.Step("extra_step", shift2)] - extra_step = ('extra_step', None) + extra_step = ("extra_step", None) pipeline.append(extra_step) # make sure no warnings occur when creating wcs with this pipeline @@ -1288,9 +1405,9 @@ def test_sip_roundtrip(): hdr = fits.Header.fromtextfile(os.path.join(data_path, "acs.hdr"), endcard=False) nx = ny = 1024 - hdr['naxis'] = 2 - hdr['naxis1'] = nx - hdr['naxis2'] = ny + hdr["naxis"] = 2 + hdr["naxis1"] = nx + hdr["naxis2"] = ny with warnings.catch_warnings(): warnings.simplefilter("ignore", category=astwcs.FITSFixedWarning) gw = _gwcs_from_hst_fits_wcs(hdr) @@ -1298,31 +1415,28 @@ def test_sip_roundtrip(): max_pix_error=1e-6, max_inv_pix_error=None, npoints=64, - crpix=(hdr['crpix1'], hdr['crpix2']) + crpix=(hdr["crpix1"], hdr["crpix2"]), ) - for k in ['naxis', 'naxis1', 'naxis2', 'ctype1', 'ctype2', 'a_order', 'b_order']: + for k in ["naxis", "naxis1", "naxis2", "ctype1", "ctype2", "a_order", "b_order"]: assert hdr[k] == hdr_back[k] - for k in ['cd1_1', 'cd1_2', 'cd2_1', 'cd2_2']: + for k in ["cd1_1", "cd1_2", "cd2_1", "cd2_2"]: assert np.allclose(hdr[k], hdr_back[k], atol=1e-14, rtol=1e-9) - for t in ('a', 'b'): - order = hdr[f'{t}_order'] + for t in ("a", "b"): + order = hdr[f"{t}_order"] for i in range(order + 1): for j in range(order + 1): if 1 < i + j <= order: - k = f'{t}_{i}_{j}' + k = f"{t}_{i}_{j}" assert np.allclose( - hdr[k], - hdr_back[k], - atol=1e-15, - rtol=1.0e-8 * 10**(i + j) + hdr[k], hdr_back[k], atol=1e-15, rtol=1.0e-8 * 10 ** (i + j) ) def test_spatial_spectral_stokes(): - """ Converts a FITS WCS to GWCS and compares results.""" + """Converts a FITS WCS to GWCS and compares results.""" hdr = fits.Header.fromfile(os.path.join(data_path, "stokes.txt")) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=astwcs.FITSFixedWarning) @@ -1331,26 +1445,38 @@ def test_spatial_spectral_stokes(): crval = aw.wcs.crval cdelt = aw.wcs.cdelt - fk5 = cf.CelestialFrame(reference_frame=coord.FK5(), name='FK5') - detector = cf.Frame2D(name='detector', axes_order=(0, 1)) - spec = cf.SpectralFrame(name='FREQ', unit=[u.Hz, ], axes_order=(2, ), axes_names=('freq', )) + fk5 = cf.CelestialFrame(reference_frame=coord.FK5(), name="FK5") + detector = cf.Frame2D(name="detector", axes_order=(0, 1)) + spec = cf.SpectralFrame( + name="FREQ", + unit=[ + u.Hz, + ], + axes_order=(2,), + axes_names=("freq",), + ) stokes = cf.StokesFrame(axes_order=(3,)) world = cf.CompositeFrame(frames=[fk5, spec, stokes]) - det2sky = (models.Shift(-crpix[0]) & models.Shift(-crpix[1]) | - models.Scale(cdelt[0]) & models.Scale(cdelt[1]) | - models.Pix2Sky_SIN() | models.RotateNative2Celestial(crval[0], crval[1], 180)) + det2sky = ( + models.Shift(-crpix[0]) & models.Shift(-crpix[1]) + | models.Scale(cdelt[0]) & models.Scale(cdelt[1]) + | models.Pix2Sky_SIN() + | models.RotateNative2Celestial(crval[0], crval[1], 180) + ) det2freq = models.Shift(-crpix[2]) | models.Scale(cdelt[2]) | models.Shift(crval[2]) - det2stokes = models.Shift(-crpix[3]) | models.Scale(cdelt[3]) | models.Shift(crval[3]) + det2stokes = ( + models.Shift(-crpix[3]) | models.Scale(cdelt[3]) | models.Shift(crval[3]) + ) - gw = wcs.WCS([wcs.Step(detector, det2sky & det2freq & det2stokes), - wcs.Step(world, None)] - ) + gw = wcs.WCS( + [wcs.Step(detector, det2sky & det2freq & det2stokes), wcs.Step(world, None)] + ) x1 = np.array([0, 0, 0, 0, 0]) x2 = np.array([0, 1, 2, 3, 4]) - gw_sky, gw_spec, gw_stokes = gw.pixel_to_world(x1+1, x1+1, x1+1, x2+1) + gw_sky, gw_spec, gw_stokes = gw.pixel_to_world(x1 + 1, x1 + 1, x1 + 1, x2 + 1) aw_sky, aw_spec, aw_stokes = aw.pixel_to_world(x1, x1, x1, x2) assert_allclose(gw_sky.data.lon, aw_sky.data.lon) @@ -1361,19 +1487,25 @@ def test_spatial_spectral_stokes(): def test_wcs_str(): w = wcs.WCS(output_frame="icrs") - assert 'icrs' in str(w) + assert "icrs" in str(w) def test_bounding_box_is_returned_F(): bbox_tuple = ((1, 2), (3, 4)) - detector_2d_frame = cf.Frame2D(name='detector', axes_order=(0, 1)) + detector_2d_frame = cf.Frame2D(name="detector", axes_order=(0, 1)) model_2d_shift = models.Shift(1) & models.Shift(2) model_2d_shift_bbox = model_2d_shift.copy() model_2d_shift_bbox.bounding_box = bbox_tuple - frame = cf.CoordinateFrame(name="quantity", axes_order=(0, 1), naxes=2, axes_type=("SPATIAL", "SPATIAL"), unit=(u.km, u.km)) + frame = cf.CoordinateFrame( + name="quantity", + axes_order=(0, 1), + naxes=2, + axes_type=("SPATIAL", "SPATIAL"), + unit=(u.km, u.km), + ) # Demonstrate that model_2d_shift does not have a bounding box with pytest.raises(NotImplementedError): @@ -1427,7 +1559,9 @@ def test_no_bounding_box_if_read_from_file(tmp_path): # Write a bad wcs bounding box to an asdf file asdf_file = tmp_path / "bad_wcs.asdf" - af = asdf.AsdfFile({"wcs": gwcs_2d_bad_bounding_box_order()}) # re-create the bad wcs object + af = asdf.AsdfFile( + {"wcs": gwcs_2d_bad_bounding_box_order()} + ) # re-create the bad wcs object af.write_to(asdf_file) with asdf.open(asdf_file) as af: @@ -1449,31 +1583,38 @@ def test_split_frame_wcs(): # Input is (lat, wave, lon) # lat: multiply by 20 arcsec, lon: multiply by 15 deg # result should be 20 arcsec, 10nm, 45 deg - spatial = models.Multiply(20*u.arcsec/u.pix) & models.Multiply(15*u.deg/u.pix) - compound = models.Linear1D(intercept=0*u.nm, slope=10*u.nm/u.pix) & spatial + spatial = models.Multiply(20 * u.arcsec / u.pix) & models.Multiply( + 15 * u.deg / u.pix + ) + compound = models.Linear1D(intercept=0 * u.nm, slope=10 * u.nm / u.pix) & spatial # This forward transforms uses mappings to be (lat, wave, lon) - forward = models.Mapping((1, 0, 2)) | compound | models.Mapping((1, 0, 2)) + forward = models.Mapping((1, 0, 2)) | compound | models.Mapping((1, 0, 2)) # Setup the output frame - celestial_frame = cf.CelestialFrame(axes_order=(2, 0), unit=(u.deg, u.arcsec), - reference_frame=coord.ICRS(), axes_names=('lon', 'lat')) - #celestial_frame = cf.CelestialFrame(axes_order=(2, 0), unit=(u.arcsec, u.deg), + celestial_frame = cf.CelestialFrame( + axes_order=(2, 0), + unit=(u.deg, u.arcsec), + reference_frame=coord.ICRS(), + axes_names=("lon", "lat"), + ) + # celestial_frame = cf.CelestialFrame(axes_order=(2, 0), unit=(u.arcsec, u.deg), # reference_frame=coord.ICRS()) - spectral_frame = cf.SpectralFrame(axes_order=(1,), unit=u.nm, axes_names='wave') + spectral_frame = cf.SpectralFrame(axes_order=(1,), unit=u.nm, axes_names="wave") output_frame = cf.CompositeFrame([spectral_frame, celestial_frame]) - #output_frame = cf.CompositeFrame([celestial_frame, spectral_frame]) + # output_frame = cf.CompositeFrame([celestial_frame, spectral_frame]) - input_frame = cf.CoordinateFrame(3, ["PIXEL"]*3, - axes_order=list(range(3)), unit=[u.pix]*3) + input_frame = cf.CoordinateFrame( + 3, ["PIXEL"] * 3, axes_order=list(range(3)), unit=[u.pix] * 3 + ) iwcs = wcs.WCS(forward, input_frame, output_frame) - input_pixel = [1*u.pix, 1*u.pix, 3*u.pix] + input_pixel = [1 * u.pix, 1 * u.pix, 3 * u.pix] output_world = iwcs.pixel_to_world_values(*input_pixel) output_pixel = iwcs.world_to_pixel_values(*output_world) assert_allclose(output_pixel, u.Quantity(input_pixel).to_value(u.pix)) - expected_world = [20*u.arcsec, 10*u.nm, 45*u.deg] - #expected_world = [15*u.deg, 20*u.nm, 60*u.arcsec] + expected_world = [20 * u.arcsec, 10 * u.nm, 45 * u.deg] + # expected_world = [15*u.deg, 20*u.nm, 60*u.arcsec] for expected, output in zip(expected_world, output_world): assert_allclose(output, expected.value) @@ -1492,22 +1633,26 @@ def test_split_frame_wcs(): def test_reordered_celestial(): # This is a spatial model which is ordered lat, lon for the purposes of this test. # Expected lat=45 deg, lon=20 arcsec - spatial = models.Multiply(20*u.arcsec/u.pix) & models.Multiply(15*u.deg/u.pix) | models.Mapping((1,0)) + spatial = models.Multiply(20 * u.arcsec / u.pix) & models.Multiply( + 15 * u.deg / u.pix + ) | models.Mapping((1, 0)) - celestial_frame = cf.CelestialFrame(axes_order=(1, 0), unit=(u.arcsec, u.deg), - reference_frame=coord.ICRS()) + celestial_frame = cf.CelestialFrame( + axes_order=(1, 0), unit=(u.arcsec, u.deg), reference_frame=coord.ICRS() + ) - input_frame = cf.CoordinateFrame(2, ["PIXEL"]*2, - axes_order=list(range(2)), unit=[u.pix]*2) + input_frame = cf.CoordinateFrame( + 2, ["PIXEL"] * 2, axes_order=list(range(2)), unit=[u.pix] * 2 + ) iwcs = wcs.WCS(spatial, input_frame, celestial_frame) - input_pixel = [1*u.pix, 3*u.pix] + input_pixel = [1 * u.pix, 3 * u.pix] output_world = iwcs.pixel_to_world_values(*input_pixel) output_pixel = iwcs.world_to_pixel_values(*output_world) assert_allclose(output_pixel, u.Quantity(input_pixel).to_value(u.pix)) - expected_world = [45*u.deg, 20*u.arcsec] + expected_world = [45 * u.deg, 20 * u.arcsec] assert_allclose(output_world, [e.value for e in expected_world]) world_obj = iwcs.pixel_to_world(*input_pixel) @@ -1527,14 +1672,14 @@ def test_high_level_objects_in_pipeline_forward(gwcs_with_pipeline_celestial): """ iwcs = gwcs_with_pipeline_celestial - input_pixel = [1*u.pix, 1*u.pix] + input_pixel = [1 * u.pix, 1 * u.pix] output_world = iwcs(*input_pixel) assert output_world[0].unit == u.deg assert output_world[1].unit == u.deg - assert u.allclose(output_world[0], 20*u.arcsec + 1*u.deg) - assert u.allclose(output_world[1], 15*u.deg + 2*u.deg) + assert u.allclose(output_world[0], 20 * u.arcsec + 1 * u.deg) + assert u.allclose(output_world[1], 15 * u.deg + 2 * u.deg) # with_units=True puts the result in the frame units rather than in the # model units. @@ -1550,19 +1695,16 @@ def test_high_level_objects_in_pipeline_forward(gwcs_with_pipeline_celestial): ) assert intermediate_world[0].unit == u.arcsec assert intermediate_world[1].unit == u.deg - assert u.allclose(intermediate_world[0], 20*u.arcsec) - assert u.allclose(intermediate_world[1], 15*u.deg) + assert u.allclose(intermediate_world[0], 20 * u.arcsec) + assert u.allclose(intermediate_world[1], 15 * u.deg) intermediate_world_with_units = iwcs.transform( - "input", - "celestial", - *input_pixel, - with_units=True + "input", "celestial", *input_pixel, with_units=True ) assert isinstance(intermediate_world_with_units, coord.SkyCoord) sc = intermediate_world_with_units - assert u.allclose(sc.ra, 20*u.arcsec) - assert u.allclose(sc.dec, 15*u.deg) + assert u.allclose(sc.ra, 20 * u.arcsec) + assert u.allclose(sc.dec, 15 * u.deg) def test_high_level_objects_in_pipeline_backward(gwcs_with_pipeline_celestial): @@ -1573,13 +1715,13 @@ def test_high_level_objects_in_pipeline_backward(gwcs_with_pipeline_celestial): iwcs = gwcs_with_pipeline_celestial input_world = [ - 20*u.arcsec + 1*u.deg, - 15*u.deg + 2*u.deg, + 20 * u.arcsec + 1 * u.deg, + 15 * u.deg + 2 * u.deg, ] pixel = iwcs.invert(*input_world) assert all(isinstance(p, u.Quantity) for p in pixel) - assert u.allclose(pixel, [1, 1]*u.pix) + assert u.allclose(pixel, [1, 1] * u.pix) pixel = iwcs.invert( *input_world, @@ -1587,7 +1729,7 @@ def test_high_level_objects_in_pipeline_backward(gwcs_with_pipeline_celestial): ) assert all(isinstance(p, u.Quantity) for p in pixel) - assert u.allclose(pixel, [1, 1]*u.pix) + assert u.allclose(pixel, [1, 1] * u.pix) intermediate_world = iwcs.transform( "output", @@ -1595,7 +1737,7 @@ def test_high_level_objects_in_pipeline_backward(gwcs_with_pipeline_celestial): *input_world, ) assert all(isinstance(p, u.Quantity) for p in intermediate_world) - assert u.allclose(intermediate_world, [20*u.arcsec, 15*u.deg]) + assert u.allclose(intermediate_world, [20 * u.arcsec, 15 * u.deg]) intermediate_world = iwcs.transform( "output", diff --git a/gwcs/tests/utils.py b/gwcs/tests/utils.py index e62a9e40..f80ae7df 100644 --- a/gwcs/tests/utils.py +++ b/gwcs/tests/utils.py @@ -1,14 +1,18 @@ import numpy as np from astropy.modeling.models import ( - Shift, Polynomial2D, Pix2Sky_TAN, RotateNative2Celestial, Mapping + Shift, + Polynomial2D, + Pix2Sky_TAN, + RotateNative2Celestial, + Mapping, ) from astropy import coordinates as coord from astropy import units from astropy import wcs as fits_wcs -from .. wcs import WCS +from ..wcs import WCS from .. import coordinate_frames as cf @@ -19,7 +23,7 @@ def coeffs_to_poly(mat, degree): for i in range(mat.shape[0]): for j in range(mat.shape[1]): if 0 < i + j <= degree: - setattr(pol, f'c{i}_{j}', mat[i, j]) + setattr(pol, f"c{i}_{j}", mat[i, j]) return pol w = fits_wcs.WCS(header, hdu) @@ -41,16 +45,20 @@ def coeffs_to_poly(mat, degree): # construct GWCS: det2sky = ( - (Shift(-x0) & Shift(-y0)) | Mapping((0, 1, 0, 1)) | (polx & poly) | - Pix2Sky_TAN() | RotateNative2Celestial(*w.wcs.crval, 180) + (Shift(-x0) & Shift(-y0)) + | Mapping((0, 1, 0, 1)) + | (polx & poly) + | Pix2Sky_TAN() + | RotateNative2Celestial(*w.wcs.crval, 180) ) - detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"), - unit=(units.pix, units.pix)) + detector_frame = cf.Frame2D( + name="detector", axes_names=("x", "y"), unit=(units.pix, units.pix) + ) sky_frame = cf.CelestialFrame( reference_frame=getattr(coord, w.wcs.radesys).__call__(), name=w.wcs.radesys, - unit=(units.deg, units.deg) + unit=(units.deg, units.deg), ) pipeline = [(detector_frame, det2sky), (sky_frame, None)] gw = WCS(pipeline) diff --git a/gwcs/utils.py b/gwcs/utils.py index 9fc5ce5f..f72abae0 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -3,6 +3,7 @@ Utility function for WCS """ + import re import functools import numpy as np @@ -15,19 +16,19 @@ # these ctype values do not include yzLN and yzLT pairs -sky_pairs = {"equatorial": ["RA", "DEC"], - "ecliptic": ["ELON", "ELAT"], - "galactic": ["GLON", "GLAT"], - "helioecliptic": ["HLON", "HLAT"], - "supergalactic": ["SLON", "SLAT"], - # "spec": specsystems - } +sky_pairs = { + "equatorial": ["RA", "DEC"], + "ecliptic": ["ELON", "ELAT"], + "galactic": ["GLON", "GLAT"], + "helioecliptic": ["HLON", "HLAT"], + "supergalactic": ["SLON", "SLAT"], + # "spec": specsystems +} -radesys = ['ICRS', 'FK5', 'FK4', 'FK4-NO-E', 'GAPPT', 'GALACTIC'] +radesys = ["ICRS", "FK5", "FK4", "FK4-NO-E", "GAPPT", "GALACTIC"] class UnsupportedTransformError(Exception): - def __init__(self, message): super(UnsupportedTransformError, self).__init__(message) @@ -39,13 +40,11 @@ def __init__(self, code): class RegionError(Exception): - def __init__(self, message): super(RegionError, self).__init__(message) class CoordinateFrameError(Exception): - def __init__(self, message): super(CoordinateFrameError, self).__init__(message) @@ -156,9 +155,11 @@ def get_projcode(wcs_info): sky_axes, _, _ = get_axes(wcs_info) if not sky_axes: return None - projcode = wcs_info['CTYPE'][sky_axes[0]][5:8].upper() + projcode = wcs_info["CTYPE"][sky_axes[0]][5:8].upper() if projcode not in projections.projcodes: - raise UnsupportedProjectionError('Projection code %s, not recognized' % projcode) + raise UnsupportedProjectionError( + "Projection code %s, not recognized" % projcode + ) return projcode @@ -179,25 +180,25 @@ def read_wcs_from_header(header): wcs_info = {} try: - wcs_info['WCSAXES'] = header['WCSAXES'] + wcs_info["WCSAXES"] = header["WCSAXES"] except KeyError: - p = re.compile(r'ctype[\d]*', re.IGNORECASE) - ctypes = header['CTYPE*'] + p = re.compile(r"ctype[\d]*", re.IGNORECASE) + ctypes = header["CTYPE*"] keys = list(ctypes.keys()) for key in keys[::-1]: if p.split(key)[-1] != "": keys.remove(key) - wcs_info['WCSAXES'] = len(keys) - wcsaxes = wcs_info['WCSAXES'] + wcs_info["WCSAXES"] = len(keys) + wcsaxes = wcs_info["WCSAXES"] # if not present call get_csystem - wcs_info['RADESYS'] = header.get('RADESYS', 'ICRS') - wcs_info['VAFACTOR'] = header.get('VAFACTOR', 1) - wcs_info['NAXIS'] = header.get('NAXIS', 0) + wcs_info["RADESYS"] = header.get("RADESYS", "ICRS") + wcs_info["VAFACTOR"] = header.get("VAFACTOR", 1) + wcs_info["NAXIS"] = header.get("NAXIS", 0) # date keyword? # wcs_info['DATEOBS'] = header.get('DATE-OBS', 'DATEOBS') - wcs_info['EQUINOX'] = header.get("EQUINOX", None) - wcs_info['EPOCH'] = header.get("EPOCH", None) - wcs_info['DATEOBS'] = header.get("MJD-OBS", header.get("DATE-OBS", None)) + wcs_info["EQUINOX"] = header.get("EQUINOX", None) + wcs_info["EPOCH"] = header.get("EPOCH", None) + wcs_info["DATEOBS"] = header.get("MJD-OBS", header.get("DATE-OBS", None)) ctype = [] cunit = [] @@ -205,35 +206,35 @@ def read_wcs_from_header(header): crval = [] cdelt = [] for i in range(1, wcsaxes + 1): - ctype.append(header['CTYPE{0}'.format(i)]) - cunit.append(header.get('CUNIT{0}'.format(i), None)) - crpix.append(header.get('CRPIX{0}'.format(i), 0.0)) - crval.append(header.get('CRVAL{0}'.format(i), 0.0)) - cdelt.append(header.get('CDELT{0}'.format(i), 1.0)) - - if 'CD1_1' in header: - wcs_info['has_cd'] = True + ctype.append(header["CTYPE{0}".format(i)]) + cunit.append(header.get("CUNIT{0}".format(i), None)) + crpix.append(header.get("CRPIX{0}".format(i), 0.0)) + crval.append(header.get("CRVAL{0}".format(i), 0.0)) + cdelt.append(header.get("CDELT{0}".format(i), 1.0)) + + if "CD1_1" in header: + wcs_info["has_cd"] = True else: - wcs_info['has_cd'] = False + wcs_info["has_cd"] = False pc = np.zeros((wcsaxes, wcsaxes)) for i in range(1, wcsaxes + 1): for j in range(1, wcsaxes + 1): try: - if wcs_info['has_cd']: - pc[i - 1, j - 1] = header['CD{0}_{1}'.format(i, j)] + if wcs_info["has_cd"]: + pc[i - 1, j - 1] = header["CD{0}_{1}".format(i, j)] else: - pc[i - 1, j - 1] = header['PC{0}_{1}'.format(i, j)] + pc[i - 1, j - 1] = header["PC{0}_{1}".format(i, j)] except KeyError: if i == j: - pc[i - 1, j - 1] = 1. + pc[i - 1, j - 1] = 1.0 else: - pc[i - 1, j - 1] = 0. - wcs_info['CTYPE'] = ctype - wcs_info['CUNIT'] = cunit - wcs_info['CRPIX'] = crpix - wcs_info['CRVAL'] = crval - wcs_info['CDELT'] = cdelt - wcs_info['PC'] = pc + pc[i - 1, j - 1] = 0.0 + wcs_info["CTYPE"] = ctype + wcs_info["CUNIT"] = cunit + wcs_info["CRPIX"] = crpix + wcs_info["CRVAL"] = crval + wcs_info["CDELT"] = cdelt + wcs_info["PC"] = pc return wcs_info @@ -261,7 +262,7 @@ def get_axes(header): # Split each CTYPE value at "-" and take the first part. # This should represent the coordinate system. - ctype = [ax.split('-')[0].upper() for ax in wcs_info['CTYPE']] + ctype = [ax.split("-")[0].upper() for ax in wcs_info["CTYPE"]] sky_inmap = [] spec_inmap = [] unknown = [] @@ -282,32 +283,45 @@ def get_axes(header): def _is_skysys_consistent(ctype, sky_inmap): - """ Determine if the sky axes in CTYPE match to form a standard celestial system.""" + """Determine if the sky axes in CTYPE match to form a standard celestial system.""" for item in sky_pairs.values(): if ctype[sky_inmap[0]] == item[0]: if ctype[sky_inmap[1]] != item[1]: raise ValueError( - "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype)) + "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype) + ) break elif ctype[sky_inmap[1]] == item[0]: if ctype[sky_inmap[0]] != item[1]: raise ValueError( - "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype)) + "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype) + ) sky_inmap = sky_inmap[::-1] break -specsystems = ["WAVE", "FREQ", "ENER", "WAVEN", "AWAV", - "VRAD", "VOPT", "ZOPT", "BETA", "VELO"] - -sky_systems_map = {'ICRS': coords.ICRS, - 'FK5': coords.FK5, - 'FK4': coords.FK4, - 'FK4NOE': coords.FK4NoETerms, - 'GAL': coords.Galactic, - 'HOR': coords.AltAz - } +specsystems = [ + "WAVE", + "FREQ", + "ENER", + "WAVEN", + "AWAV", + "VRAD", + "VOPT", + "ZOPT", + "BETA", + "VELO", +] + +sky_systems_map = { + "ICRS": coords.ICRS, + "FK5": coords.FK5, + "FK4": coords.FK4, + "FK4NOE": coords.FK4NoETerms, + "GAL": coords.Galactic, + "HOR": coords.AltAz, +} def make_fitswcs_transform(header): @@ -333,7 +347,7 @@ def make_fitswcs_transform(header): wcs_nonlinear = fitswcs_nonlinear(wcs_info) if wcs_nonlinear is not None: transforms.append(wcs_nonlinear) - return functools.reduce(core._model_oper('|'), transforms) + return functools.reduce(core._model_oper("|"), transforms) def fitswcs_linear(header): @@ -353,7 +367,7 @@ def fitswcs_linear(header): else: raise TypeError("Expected a FITS Header or a dict.") - pc = wcs_info['PC'] + pc = wcs_info["PC"] # get the part of the PC matrix corresponding to the imaging axes sky_axes, spec_axes, unknown = get_axes(wcs_info) if pc.shape != (2, 2): @@ -373,28 +387,32 @@ def fitswcs_linear(header): crpix = [] cdelt = [] for i in sky_axes: - crpix.append(wcs_info['CRPIX'][i]) - cdelt.append(wcs_info['CDELT'][i]) + crpix.append(wcs_info["CRPIX"][i]) + cdelt.append(wcs_info["CDELT"][i]) else: - cdelt = wcs_info['CDELT'] - crpix = wcs_info['CRPIX'] + cdelt = wcs_info["CDELT"] + crpix = wcs_info["CRPIX"] # if wcsaxes == 2: - rotation = astmodels.AffineTransformation2D(matrix=pc, name='pc_matrix') + rotation = astmodels.AffineTransformation2D(matrix=pc, name="pc_matrix") # elif wcsaxes == 3 : # rotation = AffineTransformation3D(matrix=matrix) # else: # raise DimensionsError("WCSLinearTransform supports only 2 or 3 dimensions, " # "{0} given".format(wcsaxes)) - translation_models = [astmodels.Shift(-(shift - 1), name='crpix' + str(i + 1)) - for i, shift in enumerate(crpix)] + translation_models = [ + astmodels.Shift(-(shift - 1), name="crpix" + str(i + 1)) + for i, shift in enumerate(crpix) + ] translation = functools.reduce(lambda x, y: x & y, translation_models) - if not wcs_info['has_cd']: + if not wcs_info["has_cd"]: # Do not compute scaling since CDELT* = 1 if CD is present. - scaling_models = [astmodels.Scale(scale, name='cdelt' + str(i + 1)) - for i, scale in enumerate(cdelt)] + scaling_models = [ + astmodels.Scale(scale, name="cdelt" + str(i + 1)) + for i, scale in enumerate(cdelt) + ] scaling = functools.reduce(lambda x, y: x & y, scaling_models) wcs_linear = translation | rotation | scaling @@ -428,14 +446,14 @@ def fitswcs_nonlinear(header): # Create the sky rotation transform sky_axes, _, _ = get_axes(wcs_info) if sky_axes: - phip, lonp = [wcs_info['CRVAL'][i] for i in sky_axes] + phip, lonp = [wcs_info["CRVAL"][i] for i in sky_axes] # TODO: write "def compute_lonpole(projcode, l)" # Set a default tvalue for now thetap = 180 n2c = astmodels.RotateNative2Celestial(phip, lonp, thetap, name="crval") transforms.append(n2c) if transforms: - return functools.reduce(core._model_oper('|'), transforms) + return functools.reduce(core._model_oper("|"), transforms) return None @@ -454,7 +472,7 @@ def create_projection_transform(projcode): Projection transform. """ - projklassname = 'Pix2Sky_' + projcode + projklassname = "Pix2Sky_" + projcode try: projklass = getattr(projections, projklassname) except AttributeError: @@ -472,8 +490,10 @@ def is_high_level(*args, low_level_wcs): if len(args) != len(low_level_wcs.world_axis_object_classes): return False - type_match = [(type(arg), waoc[0]) - for arg, waoc in zip(args, low_level_wcs.world_axis_object_classes.values())] + type_match = [ + (type(arg), waoc[0]) + for arg, waoc in zip(args, low_level_wcs.world_axis_object_classes.values()) + ] types_are_high_level = [argt is t for argt, t in type_match] @@ -484,6 +504,7 @@ def is_high_level(*args, low_level_wcs): raise TypeError( "Invalid types were passed, got " f"({', '.join(tm[0].__name__ for tm in type_match)}) expected " - f"({', '.join(tm[1].__name__ for tm in type_match)}).") + f"({', '.join(tm[1].__name__ for tm in type_match)})." + ) return False diff --git a/gwcs/wcs.py b/gwcs/wcs.py index de684805..533e1b43 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -12,12 +12,21 @@ from astropy.modeling.bounding_box import CompoundBoundingBox from astropy.modeling.bounding_box import ModelBoundingBox as Bbox from astropy.modeling.core import Model -from astropy.modeling.models import (Const1D, Identity, Mapping, Polynomial2D, - RotateCelestial2Native, Shift, - Sky2Pix_TAN) +from astropy.modeling.models import ( + Const1D, + Identity, + Mapping, + Polynomial2D, + RotateCelestial2Native, + Shift, + Sky2Pix_TAN, +) from astropy.modeling.parameters import _tofloat from astropy.wcs.utils import celestial_frame_to_wcs, proj_plane_pixel_scales -from astropy.wcs.wcsapi.high_level_api import high_level_objects_to_values, values_to_high_level_objects +from astropy.wcs.wcsapi.high_level_api import ( + high_level_objects_to_values, + values_to_high_level_objects, +) from scipy import linalg, optimize @@ -27,9 +36,9 @@ from .utils import CoordinateFrameError from .wcstools import grid_from_bounding_box -__all__ = ['WCS', 'Step', 'NoConvergence'] +__all__ = ["WCS", "Step", "NoConvergence"] -_ITER_INV_KWARGS = ['tolerance', 'maxiter', 'adaptive', 'detect_divergence', 'quiet'] +_ITER_INV_KWARGS = ["tolerance", "maxiter", "adaptive", "detect_divergence", "quiet"] class NoConvergence(Exception): @@ -66,8 +75,16 @@ class NoConvergence(Exception): then ``slow_conv`` will be set to `None`. """ - def __init__(self, *args, best_solution=None, accuracy=None, niter=None, - divergent=None, slow_conv=None): + + def __init__( + self, + *args, + best_solution=None, + accuracy=None, + niter=None, + divergent=None, + slow_conv=None, + ): super().__init__(*args) self.best_solution = best_solution @@ -83,7 +100,7 @@ class GwcsBoundingBoxWarning(UserWarning): """ -class _WorldAxisInfo(): +class _WorldAxisInfo: def __init__(self, axis, frame, world_axis_order, cunit, ctype, input_axes): """ A class for holding information about a world axis from an output frame. @@ -137,8 +154,9 @@ class WCS(GWCSAPIMixin): """ - def __init__(self, forward_transform=None, input_frame='detector', output_frame=None, - name=""): + def __init__( + self, forward_transform=None, input_frame="detector", output_frame=None, name="" + ): self._approx_inverse = None self._available_frames = [] self._pipeline = [] @@ -158,16 +176,20 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): if forward_transform is not None: if isinstance(forward_transform, Model): if output_frame is None: - raise CoordinateFrameError("An output_frame must be specified " - "if forward_transform is a model.") + raise CoordinateFrameError( + "An output_frame must be specified " + "if forward_transform is a model." + ) _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) super(WCS, self).__setattr__(_input_frame, inp_frame_obj) super(WCS, self).__setattr__(_output_frame, outp_frame_obj) - self._pipeline = [(input_frame, forward_transform.copy()), - (output_frame, None)] + self._pipeline = [ + (input_frame, forward_transform.copy()), + (output_frame, None), + ] elif isinstance(forward_transform, list): for item in forward_transform: if isinstance(item, Step): @@ -177,20 +199,21 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): super(WCS, self).__setattr__(name, frame_obj) self._pipeline = forward_transform else: - raise TypeError("Expected forward_transform to be a model or a " - "(frame, transform) list, got {0}".format( - type(forward_transform))) + raise TypeError( + "Expected forward_transform to be a model or a " + "(frame, transform) list, got {0}".format(type(forward_transform)) + ) else: # Initialize a WCS without a forward_transform - allows building a WCS programmatically. if output_frame is None: - raise CoordinateFrameError("An output_frame must be specified " - "if forward_transform is None.") + raise CoordinateFrameError( + "An output_frame must be specified " "if forward_transform is None." + ) _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) super(WCS, self).__setattr__(_input_frame, inp_frame_obj) super(WCS, self).__setattr__(_output_frame, outp_frame_obj) - self._pipeline = [(_input_frame, None), - (_output_frame, None)] + self._pipeline = [(_input_frame, None), (_output_frame, None)] def get_transform(self, from_frame, to_frame): """ @@ -214,12 +237,12 @@ def get_transform(self, from_frame, to_frame): from_ind = self._get_frame_index(from_frame) to_ind = self._get_frame_index(to_frame) if to_ind < from_ind: - transforms = [step.transform for step in self._pipeline[to_ind: from_ind]] + transforms = [step.transform for step in self._pipeline[to_ind:from_ind]] transforms = [tr.inverse for tr in transforms[::-1]] elif to_ind == from_ind: return None else: - transforms = [step.transform for step in self._pipeline[from_ind: to_ind]] + transforms = [step.transform for step in self._pipeline[from_ind:to_ind]] return functools.reduce(lambda x, y: x | y, transforms) def set_transform(self, from_frame, to_frame, transform): @@ -240,21 +263,29 @@ def set_transform(self, from_frame, to_frame, transform): if not self._pipeline: if from_name != self._input_frame: raise CoordinateFrameError( - "Expected 'from_frame' to be {0}".format(self._input_frame)) + "Expected 'from_frame' to be {0}".format(self._input_frame) + ) if to_frame != self._output_frame: raise CoordinateFrameError( - "Expected 'to_frame' to be {0}".format(self._output_frame)) + "Expected 'to_frame' to be {0}".format(self._output_frame) + ) try: from_ind = self._get_frame_index(from_name) except ValueError: - raise CoordinateFrameError("Frame {0} is not in the available frames".format(from_name)) + raise CoordinateFrameError( + "Frame {0} is not in the available frames".format(from_name) + ) try: to_ind = self._get_frame_index(to_name) except ValueError: - raise CoordinateFrameError("Frame {0} is not in the available frames".format(to_name)) + raise CoordinateFrameError( + "Frame {0} is not in the available frames".format(to_name) + ) if from_ind + 1 != to_ind: - raise ValueError("Frames {0} and {1} are not in sequence".format(from_name, to_name)) + raise ValueError( + "Frames {0} and {1} are not in sequence".format(from_name, to_name) + ) self._pipeline[from_ind].transform = transform @property @@ -265,7 +296,9 @@ def forward_transform(self): if not self._pipeline: return None - transform = functools.reduce(lambda x, y: x | y, [step.transform for step in self._pipeline[:-1]]) + transform = functools.reduce( + lambda x, y: x | y, [step.transform for step in self._pipeline[:-1]] + ) if self.bounding_box is not None: # Currently compound models do not attempt to combine individual model @@ -289,7 +322,9 @@ def backward_transform(self): try: backward = self.forward_transform.inverse except NotImplementedError as err: - raise NotImplementedError("Could not construct backward transform. \n{0}".format(err)) + raise NotImplementedError( + "Could not construct backward transform. \n{0}".format(err) + ) try: backward.inverse except NotImplementedError: # means "hasattr" won't work @@ -303,7 +338,9 @@ def _get_frame_by_name(self, frame_name): if not isinstance(frame_name, str): return frame_name - frames = [step.frame for step in self._pipeline if step.frame.name == frame_name] + frames = [ + step.frame for step in self._pipeline if step.frame.name == frame_name + ] if len(frames) > 1: raise ValueError(f"There is more than one frame named {frame_name}") if len(frames) == 0: @@ -316,11 +353,16 @@ def _get_frame_index(self, frame): """ if isinstance(frame, cf.CoordinateFrame): frame = frame.name - frame_names = [step.frame if isinstance(step.frame, str) else step.frame.name for step in self._pipeline] + frame_names = [ + step.frame if isinstance(step.frame, str) else step.frame.name + for step in self._pipeline + ] try: return frame_names.index(frame) except ValueError as e: - raise CoordinateFrameError(f"Frame {frame} is not in the available frames") from e + raise CoordinateFrameError( + f"Frame {frame} is not in the available frames" + ) from e def _get_frame_name(self, frame): """ @@ -348,14 +390,18 @@ def _get_frame_name(self, frame): def _add_units_input(self, arrays, frame): if frame is not None: - return tuple(u.Quantity(array, unit) for array, unit in zip(arrays, frame.unit)) + return tuple( + u.Quantity(array, unit) for array, unit in zip(arrays, frame.unit) + ) return arrays def _remove_units_input(self, arrays, frame): if frame is not None: - return tuple(array.to_value(unit) if isinstance(array, u.Quantity) else array - for array, unit in zip(arrays, frame.unit)) + return tuple( + array.to_value(unit) if isinstance(array, u.Quantity) else array + for array, unit in zip(arrays, frame.unit) + ) return arrays @@ -392,8 +438,15 @@ def __call__(self, *args, **kwargs): return high_level return results - def _call_forward(self, *args, from_frame=None, to_frame=None, - with_bounding_box=True, fill_value=np.nan, **kwargs): + def _call_forward( + self, + *args, + from_frame=None, + to_frame=None, + with_bounding_box=True, + fill_value=np.nan, + **kwargs, + ): """ Executes the forward transform, but values only. """ @@ -416,10 +469,9 @@ def _call_forward(self, *args, from_frame=None, to_frame=None, if not transform.uses_quantity and input_is_quantity: args = self._remove_units_input(args, from_frame) - return transform(*args, - with_bounding_box=with_bounding_box, - fill_value=fill_value, - **kwargs) + return transform( + *args, with_bounding_box=with_bounding_box, fill_value=fill_value, **kwargs + ) def in_image(self, *args, **kwargs): """ @@ -500,7 +552,7 @@ def invert(self, *args, **kwargs): """ # must pop before calling the model - with_units = kwargs.pop('with_units', False) + with_units = kwargs.pop("with_units", False) if utils.is_high_level(*args, low_level_wcs=self): args = high_level_objects_to_values(*args, low_level_wcs=self) @@ -510,14 +562,18 @@ def invert(self, *args, **kwargs): if with_units: # values are always expected to be arrays or scalars not quantities results = self._remove_units_input(results, self.input_frame) - high_level = values_to_high_level_objects(*results, low_level_wcs=self.input_frame) + high_level = values_to_high_level_objects( + *results, low_level_wcs=self.input_frame + ) if len(high_level) == 1: high_level = high_level[0] return high_level return results - def _call_backward(self, *args, with_bounding_box=True, fill_value=np.nan, **kwargs): + def _call_backward( + self, *args, with_bounding_box=True, fill_value=np.nan, **kwargs + ): try: transform = self.backward_transform except NotImplementedError: @@ -536,11 +592,21 @@ def _call_backward(self, *args, with_bounding_box=True, fill_value=np.nan, **kwa # remove iterative inverse-specific keyword arguments: akwargs = {k: v for k, v in kwargs.items() if k not in _ITER_INV_KWARGS} - result = transform(*args, with_bounding_box=with_bounding_box, fill_value=fill_value, **akwargs) + result = transform( + *args, + with_bounding_box=with_bounding_box, + fill_value=fill_value, + **akwargs, + ) else: # Always strip units for numerical inverse args = self._remove_units_input(args, self.output_frame) - result = self.numerical_inverse(*args, with_bounding_box=with_bounding_box, fill_value=fill_value, **kwargs) + result = self.numerical_inverse( + *args, + with_bounding_box=with_bounding_box, + fill_value=fill_value, + **kwargs, + ) # deal with values outside the bounding box if with_bounding_box and self.bounding_box is not None: @@ -557,7 +623,9 @@ def outside_footprint(self, world_arrays): not_numerical = False if utils.is_high_level(world_arrays[0], low_level_wcs=self): not_numerical = True - world_arrays = high_level_objects_to_values(*world_arrays, low_level_wcs=self) + world_arrays = high_level_objects_to_values( + *world_arrays, low_level_wcs=self + ) for axtyp in axes_types: ind = np.asarray((np.asarray(self.output_frame.axes_type) == axtyp)) @@ -570,14 +638,17 @@ def outside_footprint(self, world_arrays): min_ax = axis_range.min() max_ax = axis_range.max() - if (axtyp == 'SPATIAL' and str(phys).endswith((".ra", ".lon")) - and (max_ax - min_ax) > 180): - # most likely this coordinate is wrapped at 360 - d = 0.5 * (min_ax + max_ax) - m = (axis_range <= d) - min_ax = axis_range[m].max() - max_ax = axis_range[~m].min() - outside = (coord > min_ax) & (coord < max_ax) + if ( + axtyp == "SPATIAL" + and str(phys).endswith((".ra", ".lon")) + and (max_ax - min_ax) > 180 + ): + # most likely this coordinate is wrapped at 360 + d = 0.5 * (min_ax + max_ax) + m = axis_range <= d + min_ax = axis_range[m].max() + max_ax = axis_range[~m].min() + outside = (coord > min_ax) & (coord < max_ax) else: outside = (coord < min_ax) | (coord > max_ax) if np.any(outside): @@ -587,10 +658,11 @@ def outside_footprint(self, world_arrays): coord[outside] = np.nan world_arrays[idim] = coord if not_numerical: - world_arrays = values_to_high_level_objects(*world_arrays, low_level_wcs=self) + world_arrays = values_to_high_level_objects( + *world_arrays, low_level_wcs=self + ) return world_arrays - def out_of_bounds(self, pixel_arrays, fill_value=np.nan): if np.isscalar(pixel_arrays) or self.input_frame.naxes == 1: pixel_arrays = [pixel_arrays] @@ -610,9 +682,18 @@ def out_of_bounds(self, pixel_arrays, fill_value=np.nan): pixel_arrays = pixel_arrays[0] return pixel_arrays - def numerical_inverse(self, *args, tolerance=1e-5, maxiter=30, adaptive=True, - detect_divergence=True, quiet=True, with_bounding_box=True, - fill_value=np.nan, **kwargs): + def numerical_inverse( + self, + *args, + tolerance=1e-5, + maxiter=30, + adaptive=True, + detect_divergence=True, + quiet=True, + with_bounding_box=True, + fill_value=np.nan, + **kwargs, + ): """ Invert coordinates from output frame to input frame using numerical inverse. @@ -844,16 +925,20 @@ def numerical_inverse(self, *args, tolerance=1e-5, maxiter=30, adaptive=True, """ if kwargs.pop("with_units", False): - raise ValueError("Support for with_units in numerical_inverse has been removed, use inverse") + raise ValueError( + "Support for with_units in numerical_inverse has been removed, use inverse" + ) args_shape = np.shape(args) nargs = args_shape[0] arg_dim = len(args_shape) - 1 if nargs != self.world_n_dim: - raise ValueError("Number of input coordinates is different from " - "the number of defined world coordinates in the " - f"WCS ({self.world_n_dim:d})") + raise ValueError( + "Number of input coordinates is different from " + "the number of defined world coordinates in the " + f"WCS ({self.world_n_dim:d})" + ) if self.world_n_dim != self.pixel_n_dim: raise NotImplementedError( @@ -879,16 +964,21 @@ def numerical_inverse(self, *args, tolerance=1e-5, maxiter=30, adaptive=True, if not np.all(np.isfinite(x0)): return [np.array(np.nan) for _ in range(nargs)] - result = tuple(self._vectorized_fixed_point( - x0, argsi, - tolerance=tolerance, - maxiter=maxiter, - adaptive=adaptive, - detect_divergence=detect_divergence, - quiet=quiet, - with_bounding_box=with_bounding_box, - fill_value=fill_value - ).T.ravel().tolist()) + result = tuple( + self._vectorized_fixed_point( + x0, + argsi, + tolerance=tolerance, + maxiter=maxiter, + adaptive=adaptive, + detect_divergence=detect_divergence, + quiet=quiet, + with_bounding_box=with_bounding_box, + fill_value=fill_value, + ) + .T.ravel() + .tolist() + ) else: arg_shape = args_shape[1:] @@ -902,23 +992,33 @@ def numerical_inverse(self, *args, tolerance=1e-5, maxiter=30, adaptive=True, x0 = np.array(self._approx_inverse(*args)).T result = self._vectorized_fixed_point( - x0, args.T, + x0, + args.T, tolerance=tolerance, maxiter=maxiter, adaptive=adaptive, detect_divergence=detect_divergence, quiet=quiet, with_bounding_box=with_bounding_box, - fill_value=fill_value + fill_value=fill_value, ).T result = tuple(np.reshape(result, args_shape)) return result - def _vectorized_fixed_point(self, pix0, world, tolerance, maxiter, - adaptive, detect_divergence, quiet, - with_bounding_box, fill_value): + def _vectorized_fixed_point( + self, + pix0, + world, + tolerance, + maxiter, + adaptive, + detect_divergence, + quiet, + with_bounding_box, + fill_value, + ): # ############################################################ # # INITIALIZE ITERATIVE PROCESS: ## # ############################################################ @@ -941,8 +1041,13 @@ def _vectorized_fixed_point(self, pix0, world, tolerance, maxiter, l2, phi2 = np.deg2rad(self.__call__(*(crpix + [-0.5, 0.5]))) l3, phi3 = np.deg2rad(self.__call__(*(crpix + 0.5))) l4, phi4 = np.deg2rad(self.__call__(*(crpix + [0.5, -0.5]))) - area = np.abs(0.5 * ((l4 - l2) * (np.sin(phi1) - np.sin(phi3)) + - (l1 - l3) * (np.sin(phi2) - np.sin(phi4)))) + area = np.abs( + 0.5 + * ( + (l4 - l2) * (np.sin(phi1) - np.sin(phi3)) + + (l1 - l3) * (np.sin(phi2) - np.sin(phi4)) + ) + ) inv_pscale = 1 / np.rad2deg(np.sqrt(area)) # form equation: @@ -952,7 +1057,14 @@ def f(x): return np.add(inv_pscale * dw, x) def froot(x): - return np.mod(np.subtract(self.__call__(*x, with_bounding_box=False), worldi) - 180.0, 360.0) - 180.0 + return ( + np.mod( + np.subtract(self.__call__(*x, with_bounding_box=False), worldi) + - 180.0, + 360.0, + ) + - 180.0 + ) # compute correction: def correction(pix): @@ -981,16 +1093,16 @@ def correction(pix): inddiv = None # Turn off numpy runtime warnings for 'invalid' and 'over': - old_invalid = np.geterr()['invalid'] - old_over = np.geterr()['over'] - np.seterr(invalid='ignore', over='ignore') + old_invalid = np.geterr()["invalid"] + old_over = np.geterr()["over"] + np.seterr(invalid="ignore", over="ignore") # ############################################################ # # NON-ADAPTIVE ITERATIONS: ## # ############################################################ if not adaptive: # Fixed-point iterations: - while (np.nanmax(dn) >= tol2 and k < maxiter): + while np.nanmax(dn) >= tol2 and k < maxiter: # Find correction to the previous solution: dpix = correction(pix) @@ -1002,16 +1114,16 @@ def correction(pix): # scenario when successive approximations converge): if detect_divergence: - divergent = (dn >= dnprev) + divergent = dn >= dnprev if np.any(divergent): # Find solutions that have not yet converged: - slowconv = (dn >= tol2) - inddiv, = np.where(divergent & slowconv) + slowconv = dn >= tol2 + (inddiv,) = np.where(divergent & slowconv) if inddiv.shape[0] > 0: # Update indices of elements that # still need correction: - conv = (dn < dnprev) + conv = dn < dnprev iconv = np.where(conv) # Apply correction: @@ -1022,7 +1134,7 @@ def correction(pix): # For the next iteration choose # non-divergent points that have not yet # converged to the requested accuracy: - ind, = np.where(slowconv & conv) + (ind,) = np.where(slowconv & conv) world = world[ind] dnprev[ind] = dn[ind] k += 1 @@ -1043,11 +1155,11 @@ def correction(pix): # ############################################################ if adaptive: if ind is None: - ind, = np.where(np.isfinite(pix).all(axis=1)) + (ind,) = np.where(np.isfinite(pix).all(axis=1)) world = world[ind] # "Adaptive" fixed-point iterations: - while (ind.shape[0] > 0 and k < maxiter): + while ind.shape[0] > 0 and k < maxiter: # Find correction to the previous solution: dpixnew = correction(pix[ind]) @@ -1074,7 +1186,7 @@ def correction(pix): # Find indices of solutions that have not yet # converged to the requested accuracy # AND that do not diverge: - subind, = np.where((dnnew >= tol2) & conv) + (subind,) = np.where((dnnew >= tol2) & conv) else: # Apply correction: @@ -1083,7 +1195,7 @@ def correction(pix): # Find indices of solutions that have not yet # converged to the requested accuracy: - subind, = np.where(dnnew >= tol2) + (subind,) = np.where(dnnew >= tol2) # Choose solutions that need more iterations: ind = ind[subind] @@ -1096,13 +1208,14 @@ def correction(pix): # # AND FAILED-TO-CONVERGE POINTS ## # ############################################################ # Identify diverging and/or invalid points: - invalid = ((~np.all(np.isfinite(pix), axis=1)) & - (np.all(np.isfinite(world0), axis=1))) + invalid = (~np.all(np.isfinite(pix), axis=1)) & ( + np.all(np.isfinite(world0), axis=1) + ) # When detect_divergence is False, dnprev is outdated # (it is the norm of the very first correction). # Still better than nothing... - inddiv, = np.where(((dn >= tol2) & (dn >= dnprev)) | invalid) + (inddiv,) = np.where(((dn >= tol2) & (dn >= dnprev)) | invalid) if inddiv.shape[0] == 0: inddiv = None @@ -1115,13 +1228,13 @@ def correction(pix): result = optimize.root( froot, pix0[idx], - method='hybr', + method="hybr", tol=tolerance / (np.linalg.norm(pix0[idx]) + 1), - options={'maxfev': 2 * maxiter} + options={"maxfev": 2 * maxiter}, ) - if result['success']: - pix[idx, :] = result['x'] + if result["success"]: + pix[idx, :] = result["x"] invalid[idx] = False else: bad.append(idx) @@ -1134,7 +1247,7 @@ def correction(pix): # Identify points that did not converge within 'maxiter' # iterations: if k >= maxiter: - ind, = np.where((dn >= tol2) & (dn < dnprev) & (~invalid)) + (ind,) = np.where((dn >= tol2) & (dn < dnprev) & (~invalid)) if ind.shape[0] == 0: ind = None else: @@ -1152,18 +1265,25 @@ def correction(pix): raise NoConvergence( "'WCS.numerical_inverse' failed to " "converge to the requested accuracy after {:d} " - "iterations.".format(k), best_solution=pix, - accuracy=np.abs(dpix), niter=k, - slow_conv=ind, divergent=None) + "iterations.".format(k), + best_solution=pix, + accuracy=np.abs(dpix), + niter=k, + slow_conv=ind, + divergent=None, + ) else: raise NoConvergence( "'WCS.numerical_inverse' failed to " "converge to the requested accuracy.\n" "After {:d} iterations, the solution is diverging " - "at least for one input point." - .format(k), best_solution=pix, - accuracy=np.abs(dpix), niter=k, - slow_conv=ind, divergent=inddiv) + "at least for one input point.".format(k), + best_solution=pix, + accuracy=np.abs(dpix), + niter=k, + slow_conv=ind, + divergent=inddiv, + ) if with_bounding_box and self.bounding_box is not None: # find points outside the bounding box and replace their values @@ -1210,7 +1330,9 @@ def transform(self, from_frame, to_frame, *args, **kwargs): if backward and utils.is_high_level(*args, low_level_wcs=from_frame): args = high_level_objects_to_values(*args, low_level_wcs=from_frame) - results = self._call_forward(*args, from_frame=from_frame, to_frame=to_frame, **kwargs) + results = self._call_forward( + *args, from_frame=from_frame, to_frame=to_frame, **kwargs + ) if with_units: # values are always expected to be arrays or scalars not quantities @@ -1232,7 +1354,10 @@ def available_frames(self): {frame_name: frame_object or None} """ if self._pipeline: - return [step.frame if isinstance(step.frame, str) else step.frame.name for step in self._pipeline ] + return [ + step.frame if isinstance(step.frame, str) else step.frame.name + for step in self._pipeline + ] else: return None @@ -1286,35 +1411,47 @@ def insert_frame(self, input_frame, transform, output_frame): except CoordinateFrameError: input_index = None if input_frame_obj is None: - raise ValueError(f"New coordinate frame {input_name} must " - "be defined") + raise ValueError( + f"New coordinate frame {input_name} must " "be defined" + ) try: output_index = self._get_frame_index(output_frame) except CoordinateFrameError: output_index = None if output_frame_obj is None: - raise ValueError(f"New coordinate frame {output_name} must " - "be defined") + raise ValueError( + f"New coordinate frame {output_name} must " "be defined" + ) new_frames = [input_index, output_index].count(None) if new_frames == 0: - raise ValueError("Could not insert frame as both frames " - f"{input_name} and {output_name} already exist") + raise ValueError( + "Could not insert frame as both frames " + f"{input_name} and {output_name} already exist" + ) elif new_frames == 2: - raise ValueError("Could not insert frame as neither frame " - f"{input_name} nor {output_name} exists") + raise ValueError( + "Could not insert frame as neither frame " + f"{input_name} nor {output_name} exists" + ) if input_index is None: - self._pipeline = (self._pipeline[:output_index] + - [Step(input_frame_obj, transform)] + - self._pipeline[output_index:]) + self._pipeline = ( + self._pipeline[:output_index] + + [Step(input_frame_obj, transform)] + + self._pipeline[output_index:] + ) super(WCS, self).__setattr__(input_name, input_frame_obj) else: split_step = self._pipeline[input_index] - self._pipeline = (self._pipeline[:input_index] + - [Step(split_step.frame, transform), - Step(output_frame_obj, split_step.transform)] + - self._pipeline[input_index + 1:]) + self._pipeline = ( + self._pipeline[:input_index] + + [ + Step(split_step.frame, transform), + Step(output_frame_obj, split_step.transform), + ] + + self._pipeline[input_index + 1 :] + ) super(WCS, self).__setattr__(output_name, output_frame_obj) @property @@ -1322,7 +1459,7 @@ def unit(self): """The unit of the coordinates in the output coordinate system.""" if self._pipeline: try: - #return getattr(self, self._pipeline[-1][0].name).unit + # return getattr(self, self._pipeline[-1][0].name).unit return self._pipeline[-1].frame.unit except AttributeError: return None @@ -1396,7 +1533,7 @@ def bounding_box(self): "The bounding_box was set in C order on the transform prior to being used in the gwcs!\n" "Check that you intended that ordering for the bounding_box, and consider setting it in F order.\n" "The bounding_box will remain meaning the same but will be converted to F order for consistency in the GWCS.", - GwcsBoundingBoxWarning + GwcsBoundingBoxWarning, ) self.bounding_box = bb.bounding_box(order="F") bb = self.bounding_box @@ -1424,9 +1561,9 @@ def bounding_box(self, value): try: # Make sure the dimensions of the new bbox are correct. if isinstance(value, CompoundBoundingBox): - bbox = CompoundBoundingBox.validate(transform_0, value, order='F') + bbox = CompoundBoundingBox.validate(transform_0, value, order="F") else: - bbox = Bbox.validate(transform_0, value, order='F') + bbox = Bbox.validate(transform_0, value, order="F") except Exception: raise @@ -1438,8 +1575,9 @@ def attach_compound_bounding_box(self, cbbox, selector_args): frames = self.available_frames transform_0 = self.get_transform(frames[0], frames[1]) - self.bounding_box = CompoundBoundingBox.validate(transform_0, cbbox, selector_args=selector_args, - order='F') + self.bounding_box = CompoundBoundingBox.validate( + transform_0, cbbox, selector_args=selector_args, order="F" + ) def _get_axes_indices(self): try: @@ -1454,7 +1592,7 @@ def __str__(self): col1 = [step.frame for step in self._pipeline] col2 = [] - for item in self._pipeline[: -1]: + for item in self._pipeline[:-1]: model = item.transform if model is None: col2.append(None) @@ -1463,12 +1601,13 @@ def __str__(self): else: col2.append(model.__class__.__name__) col2.append(None) - t = Table([col1, col2], names=['From', 'Transform']) + t = Table([col1, col2], names=["From", "Transform"]) return str(t) def __repr__(self): fmt = "".format( - self.output_frame, self.input_frame, self.forward_transform) + self.output_frame, self.input_frame, self.forward_transform + ) return fmt def footprint(self, bounding_box=None, center=False, axis_type="all"): @@ -1493,14 +1632,21 @@ def footprint(self, bounding_box=None, center=False, axis_type="all"): is clockwise, starting from the bottom left corner. """ + def _order_clockwise(v): - return np.asarray([[v[0][0], v[1][0]], [v[0][0], v[1][1]], - [v[0][1], v[1][1]], [v[0][1], v[1][0]]]).T + return np.asarray( + [ + [v[0][0], v[1][0]], + [v[0][0], v[1][1]], + [v[0][1], v[1][1]], + [v[0][1], v[1][0]], + ] + ).T if bounding_box is None: if self.bounding_box is None: raise TypeError("Need a valid bounding_box to compute the footprint.") - bb = self.bounding_box.bounding_box(order='F') + bb = self.bounding_box.bounding_box(order="F") else: bb = bounding_box @@ -1519,16 +1665,20 @@ def _order_clockwise(v): if center: vertices = utils._toindex(vertices) - result = np.asarray(self.__call__(*vertices, **{'with_bounding_box': False})) + result = np.asarray(self.__call__(*vertices, **{"with_bounding_box": False})) axis_type = axis_type.lower() - if axis_type == 'spatial' and all_spatial: + if axis_type == "spatial" and all_spatial: return result.T if axis_type != "all": - axtyp_ind = np.array([t.lower() for t in self.output_frame.axes_type]) == axis_type + axtyp_ind = ( + np.array([t.lower() for t in self.output_frame.axes_type]) == axis_type + ) if not axtyp_ind.any(): - raise ValueError('This WCS does not have axis of type "{}".'.format(axis_type)) + raise ValueError( + 'This WCS does not have axis of type "{}".'.format(axis_type) + ) if len(axtyp_ind) > 1: result = np.asarray([(r.min(), r.max()) for r in result[axtyp_ind]]) @@ -1570,10 +1720,18 @@ def fix_inputs(self, fixed): new_pipeline.extend(self.pipeline[1:]) return self.__class__(new_pipeline) - def to_fits_sip(self, bounding_box=None, max_pix_error=0.25, degree=None, - max_inv_pix_error=0.25, inv_degree=None, - npoints=32, crpix=None, projection='TAN', - verbose=False): + def to_fits_sip( + self, + bounding_box=None, + max_pix_error=0.25, + degree=None, + max_inv_pix_error=0.25, + inv_degree=None, + npoints=32, + crpix=None, + projection="TAN", + verbose=False, + ): """ Construct a SIP-based approximation to the WCS for the axes corresponding to the `~gwcs.coordinate_frames.CelestialFrame` @@ -1685,17 +1843,27 @@ def to_fits_sip(self, bounding_box=None, max_pix_error=0.25, degree=None, npoints=npoints, crpix=crpix, projection=projection, - matrix_type='CD', - verbose=verbose + matrix_type="CD", + verbose=verbose, ) return hdr - def _to_fits_sip(self, celestial_group, keep_axis_position, - bounding_box, max_pix_error, degree, - max_inv_pix_error, inv_degree, - npoints, crpix, projection, matrix_type, - verbose): + def _to_fits_sip( + self, + celestial_group, + keep_axis_position, + bounding_box, + max_pix_error, + degree, + max_inv_pix_error, + inv_degree, + npoints, + crpix, + projection, + matrix_type, + verbose, + ): r""" Construct a SIP-based approximation to the WCS for the axes corresponding to the `~gwcs.coordinate_frames.CelestialFrame` @@ -1772,33 +1940,42 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, if isinstance(matrix_type, str): matrix_type = matrix_type.upper() - if matrix_type not in ['CD', 'PC-CDELT1', 'PC-SUM1', 'PC-DET1', 'PC-SCALE']: + if matrix_type not in ["CD", "PC-CDELT1", "PC-SUM1", "PC-DET1", "PC-SCALE"]: raise ValueError(f"Unsupported 'matrix_type' value: {repr(matrix_type)}.") if npoints < 8: - raise ValueError("Number of sampling points is too small. 'npoints' must be >= 8.") + raise ValueError( + "Number of sampling points is too small. 'npoints' must be >= 8." + ) if isinstance(projection, str): projection = projection.upper() try: - sky2pix_proj = getattr(projections, f'Sky2Pix_{projection}')(name=projection) + sky2pix_proj = getattr(projections, f"Sky2Pix_{projection}")( + name=projection + ) except AttributeError: raise ValueError("Unsupported FITS WCS sky projection: {projection}") elif isinstance(projection, projections.Sky2PixProjection): sky2pix_proj = projection projection = projection.name - if not projection or not isinstance(projection, str) or len(projection) != 3: + if ( + not projection + or not isinstance(projection, str) + or len(projection) != 3 + ): raise ValueError("Unsupported FITS WCS sky projection: {sky2pix_proj}") try: - getattr(projections, f'Sky2Pix_{projection}')() + getattr(projections, f"Sky2Pix_{projection}")() except AttributeError: raise ValueError("Unsupported FITS WCS sky projection: {projection}") else: raise TypeError( "'projection' must be either a FITS WCS string projection code " - "or an instance of astropy.modeling.projections.Pix2SkyProjection.") + "or an instance of astropy.modeling.projections.Pix2SkyProjection." + ) frame = celestial_group[0].frame @@ -1812,8 +1989,10 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, input_axes = sorted(set(input_axes)) if len(input_axes) != 2: - raise ValueError("Only CelestialFrame that correspond to two " - "input axes are supported.") + raise ValueError( + "Only CelestialFrame that correspond to two " + "input axes are supported." + ) # Axis number for FITS axes. # iax? - image axes; nlon, nlat - celestial axes: @@ -1844,8 +2023,9 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, # statement commented out immediately above. transform = _fix_transform_inputs(self.forward_transform, fixi_dict) - transform = transform | Mapping((lon_axis, lat_axis), - n_inputs=self.forward_transform.n_outputs) + transform = transform | Mapping( + (lon_axis, lat_axis), n_inputs=self.forward_transform.n_outputs + ) (xmin, xmax) = bounding_box[input_axes[0]] (ymin, ymax) = bounding_box[input_axes[1]] @@ -1867,15 +2047,16 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, # Now rotate to native system and deproject. Recall that transform # expects pixels in the original coordinate system, but the SIP # transform is relative to crpix coordinates, thus the initial shift. - ntransform = ((Shift(crpix1) & Shift(crpix2)) | transform - | RotateCelestial2Native(lon, lat, 180) - | sky2pix_proj) + ntransform = ( + (Shift(crpix1) & Shift(crpix2)) + | transform + | RotateCelestial2Native(lon, lat, 180) + | sky2pix_proj + ) # standard sampling: u, v = _make_sampling_grid( - npoints, - tuple(bounding_box[k] for k in input_axes), - crpix=[crpix1, crpix2] + npoints, tuple(bounding_box[k] for k in input_axes), crpix=[crpix1, crpix2] ) undist_x, undist_y = ntransform(u, v) @@ -1883,7 +2064,7 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, ud, vd = _make_sampling_grid( 2 * npoints, tuple(bounding_box[k] for k in input_axes), - crpix=[crpix1, crpix2] + crpix=[crpix1, crpix2], ) undist_xd, undist_yd = ntransform(ud, vd) @@ -1899,21 +2080,31 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, if verbose: print("\nFitting forward SIP ...") fit_poly_x, fit_poly_y, max_resid = _fit_2D_poly( - degree, max_pix_error, plate_scale, - u, v, undist_x, undist_y, - ud, vd, undist_xd, undist_yd, - verbose=verbose + degree, + max_pix_error, + plate_scale, + u, + v, + undist_x, + undist_y, + ud, + vd, + undist_xd, + undist_yd, + verbose=verbose, ) # The following is necessary to put the fit into the SIP formalism. - cdmat, sip_poly_x, sip_poly_y = _reform_poly_coefficients(fit_poly_x, fit_poly_y) + cdmat, sip_poly_x, sip_poly_y = _reform_poly_coefficients( + fit_poly_x, fit_poly_y + ) # cdmat = np.array([[fit_poly_x.c1_0.value, fit_poly_x.c0_1.value], # [fit_poly_y.c1_0.value, fit_poly_y.c0_1.value]]) det = cdmat[0][0] * cdmat[1][1] - cdmat[0][1] * cdmat[1][0] - U = ( cdmat[1][1] * undist_x - cdmat[0][1] * undist_y) / det + U = (cdmat[1][1] * undist_x - cdmat[0][1] * undist_y) / det V = (-cdmat[1][0] * undist_x + cdmat[0][0] * undist_y) / det detd = cdmat[0][0] * cdmat[1][1] - cdmat[0][1] * cdmat[1][0] - Ud = ( cdmat[1][1] * undist_xd - cdmat[0][1] * undist_yd) / detd + Ud = (cdmat[1][1] * undist_xd - cdmat[0][1] * undist_yd) / detd Vd = (-cdmat[1][0] * undist_xd + cdmat[0][0] * undist_yd) / detd if max_inv_pix_error: @@ -1921,11 +2112,18 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, print("\nFitting inverse SIP ...") fit_inv_poly_u, fit_inv_poly_v, max_inv_resid = _fit_2D_poly( inv_degree, - max_inv_pix_error, 1, - U, V, u-U, v-V, - Ud, Vd, ud-Ud, vd-Vd, - verbose=verbose - ) + max_inv_pix_error, + 1, + U, + V, + u - U, + v - V, + Ud, + Vd, + ud - Ud, + vd - Vd, + verbose=verbose, + ) # create header with WCS info: w = celestial_frame_to_wcs(frame.reference_frame, projection=projection) @@ -1936,68 +2134,68 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, hdr = w.to_header(True) # data array info: - hdr.insert(0, ('NAXIS', 2, 'number of array dimensions')) - hdr.insert(1, (f'NAXIS{iax1:d}', int(xmax) + 1)) - hdr.insert(2, (f'NAXIS{iax2:d}', int(ymax) + 1)) - assert len(hdr['NAXIS*']) == 3 + hdr.insert(0, ("NAXIS", 2, "number of array dimensions")) + hdr.insert(1, (f"NAXIS{iax1:d}", int(xmax) + 1)) + hdr.insert(2, (f"NAXIS{iax2:d}", int(ymax) + 1)) + assert len(hdr["NAXIS*"]) == 3 # list of celestial axes related keywords: - cel_kwd = ['CRVAL', 'CTYPE', 'CUNIT'] + cel_kwd = ["CRVAL", "CTYPE", "CUNIT"] # Add SIP info: if fit_poly_x.degree > 1: - mat_kind = 'CD' + mat_kind = "CD" # CDELT is not used with CD matrix (PC->CD later): - del hdr['CDELT?'] + del hdr["CDELT?"] - hdr['CTYPE1'] = hdr['CTYPE1'].strip() + '-SIP' - hdr['CTYPE2'] = hdr['CTYPE2'].strip() + '-SIP' - hdr['A_ORDER'] = fit_poly_x.degree - hdr['B_ORDER'] = fit_poly_x.degree - _store_2D_coefficients(hdr, sip_poly_x, 'A') - _store_2D_coefficients(hdr, sip_poly_y, 'B') - hdr['sipmxerr'] = (max_resid, 'Max diff from GWCS (equiv pix).') + hdr["CTYPE1"] = hdr["CTYPE1"].strip() + "-SIP" + hdr["CTYPE2"] = hdr["CTYPE2"].strip() + "-SIP" + hdr["A_ORDER"] = fit_poly_x.degree + hdr["B_ORDER"] = fit_poly_x.degree + _store_2D_coefficients(hdr, sip_poly_x, "A") + _store_2D_coefficients(hdr, sip_poly_y, "B") + hdr["sipmxerr"] = (max_resid, "Max diff from GWCS (equiv pix).") if max_inv_pix_error: - hdr['AP_ORDER'] = fit_inv_poly_u.degree - hdr['BP_ORDER'] = fit_inv_poly_u.degree - _store_2D_coefficients(hdr, fit_inv_poly_u, 'AP', keeplinear=True) - _store_2D_coefficients(hdr, fit_inv_poly_v, 'BP', keeplinear=True) - hdr['sipiverr'] = (max_inv_resid, 'Max diff for inverse (pixels)') + hdr["AP_ORDER"] = fit_inv_poly_u.degree + hdr["BP_ORDER"] = fit_inv_poly_u.degree + _store_2D_coefficients(hdr, fit_inv_poly_u, "AP", keeplinear=True) + _store_2D_coefficients(hdr, fit_inv_poly_v, "BP", keeplinear=True) + hdr["sipiverr"] = (max_inv_resid, "Max diff for inverse (pixels)") else: - if matrix_type.startswith('PC'): - mat_kind = 'PC' - cel_kwd.append('CDELT') + if matrix_type.startswith("PC"): + mat_kind = "PC" + cel_kwd.append("CDELT") - if matrix_type == 'PC-CDELT1': + if matrix_type == "PC-CDELT1": cdelt = [1.0, 1.0] - elif matrix_type == 'PC-SUM1': + elif matrix_type == "PC-SUM1": norm = np.sqrt(np.sum(w.wcs.pc**2)) cdelt = [norm, norm] - elif matrix_type == 'PC-DET1': + elif matrix_type == "PC-DET1": det_pc = np.linalg.det(w.wcs.pc) norm = np.sqrt(np.abs(det_pc)) cdelt = [norm, np.sign(det_pc) * norm] - elif matrix_type == 'PC-SCALE': + elif matrix_type == "PC-SCALE": cdelt = proj_plane_pixel_scales(w) for i in range(1, 3): s = cdelt[i - 1] - hdr[f'CDELT{i}'] = s + hdr[f"CDELT{i}"] = s for j in range(1, 3): - pc_kwd = f'PC{i}_{j}' + pc_kwd = f"PC{i}_{j}" if pc_kwd in hdr: hdr[pc_kwd] = w.wcs.pc[i - 1, j - 1] / s else: - mat_kind = 'CD' - del hdr['CDELT?'] + mat_kind = "CD" + del hdr["CDELT?"] - hdr['sipmxerr'] = (max_resid, 'Max diff from GWCS (equiv pix).') + hdr["sipmxerr"] = (max_resid, "Max diff from GWCS (equiv pix).") # Construct CD matrix while remapping input axes. # We do not update comments to typical comments for CD matrix elements @@ -2013,48 +2211,56 @@ def _to_fits_sip(self, celestial_group, keep_axis_position, # remap input axes: axis_rename = {} if iax1 != 1: - axis_rename['CRPIX1'] = f'CRPIX{iax1}' + axis_rename["CRPIX1"] = f"CRPIX{iax1}" if iax2 != 2: - axis_rename['CRPIX2'] = f'CRPIX{iax2}' + axis_rename["CRPIX2"] = f"CRPIX{iax2}" # CP/PC matrix: - axis_rename[f'PC{old_nlon}_1'] = f'{mat_kind}{nlon}_{iax1}' - axis_rename[f'PC{old_nlon}_2'] = f'{mat_kind}{nlon}_{iax2}' - axis_rename[f'PC{old_nlat}_1'] = f'{mat_kind}{nlat}_{iax1}' - axis_rename[f'PC{old_nlat}_2'] = f'{mat_kind}{nlat}_{iax2}' + axis_rename[f"PC{old_nlon}_1"] = f"{mat_kind}{nlon}_{iax1}" + axis_rename[f"PC{old_nlon}_2"] = f"{mat_kind}{nlon}_{iax2}" + axis_rename[f"PC{old_nlat}_1"] = f"{mat_kind}{nlat}_{iax1}" + axis_rename[f"PC{old_nlat}_2"] = f"{mat_kind}{nlat}_{iax2}" # remap celestial axes keywords: for kwd in cel_kwd: for iold, inew in [(1, nlon), (2, nlat)]: if iold != inew: - axis_rename[f'{kwd:s}{iold:d}'] = f'{kwd:s}{inew:d}' + axis_rename[f"{kwd:s}{iold:d}"] = f"{kwd:s}{inew:d}" # construct new header cards with remapped axes: new_cards = [] for c in hdr.cards: if c[0] in axis_rename: - c = fits.Card(keyword=axis_rename[c.keyword], value=c.value, comment=c.comment) + c = fits.Card( + keyword=axis_rename[c.keyword], value=c.value, comment=c.comment + ) new_cards.append(c) hdr = fits.Header(new_cards) - hdr['WCSAXES'] = 2 - hdr.insert('WCSAXES', ('WCSNAME', f'{self.output_frame.name}'), after=True) + hdr["WCSAXES"] = 2 + hdr.insert("WCSAXES", ("WCSNAME", f"{self.output_frame.name}"), after=True) # for PC matrix formalism, set diagonal elements to 0 if necessary # (by default, in PC formalism, diagonal matrix elements by default # are 0): - if mat_kind == 'PC': + if mat_kind == "PC": if nlon not in [iax1, iax2]: hdr.insert( - f'{mat_kind}{nlon}_{iax2}', - (f'{mat_kind}{nlon}_{nlon}', 0.0, - 'Coordinate transformation matrix element') + f"{mat_kind}{nlon}_{iax2}", + ( + f"{mat_kind}{nlon}_{nlon}", + 0.0, + "Coordinate transformation matrix element", + ), ) if nlat not in [iax1, iax2]: hdr.insert( - f'{mat_kind}{nlat}_{iax2}', - (f'{mat_kind}{nlat}_{nlat}', 0.0, - 'Coordinate transformation matrix element') + f"{mat_kind}{nlat}_{iax2}", + ( + f"{mat_kind}{nlat}_{nlat}", + 0.0, + "Coordinate transformation matrix element", + ), ) return hdr @@ -2096,13 +2302,16 @@ def _separable_groups(self, detect_celestial): ``detect_celestial`` is set to `True`. """ + def find_frame(axis_number): for frame in frames: if axis_number in frame.axes_order: return frame else: - raise RuntimeError("Encountered an output axes that does not " - "belong to any output coordinate frames.") + raise RuntimeError( + "Encountered an output axes that does not " + "belong to any output coordinate frames." + ) # use correlation matrix to find separable axes: corr_mat = self.axis_correlation_matrix @@ -2141,9 +2350,12 @@ def find_frame(axis_number): s = sorted(s) frame = find_frame(s[0]) - celestial = (detect_celestial and len(s) == 2 and - len(frame.axes_order) == 2 and - isinstance(frame, cf.CelestialFrame)) + celestial = ( + detect_celestial + and len(s) == 2 + and len(frame.axes_order) == 2 + and isinstance(frame, cf.CelestialFrame) + ) for axno in s: if axno not in frame.axes_order: @@ -2157,9 +2369,9 @@ def find_frame(axis_number): axis=axno, frame=frame, world_axis_order=self.output_frame.axes_order.index(axno), - cunit=frame.unit[fidx].to_string('fits', fraction=True).upper(), + cunit=frame.unit[fidx].to_string("fits", fraction=True).upper(), ctype=cf.get_ctype_from_ucd(self.world_axis_physical_types[axno]), - input_axes=mapping[axno] + input_axes=mapping[axno], ) axis_info_group.append(axis_info) input_axes.extend(mapping[axno]) @@ -2171,23 +2383,36 @@ def find_frame(axis_number): axes_groups.append(axis_info_group) # sanity check: - input_axes = set(sum((ax.input_axes for ax in world_axes), - world_axes[0].input_axes.__class__())) + input_axes = set( + sum( + (ax.input_axes for ax in world_axes), + world_axes[0].input_axes.__class__(), + ) + ) n_inputs = len(input_axes) - if (n_inputs != self.pixel_n_dim or - max(input_axes) + 1 != n_inputs or - min(input_axes) < 0): - raise ValueError("Input axes indices are inconsistent with the " - "forward transformation.") + if ( + n_inputs != self.pixel_n_dim + or max(input_axes) + 1 != n_inputs + or min(input_axes) < 0 + ): + raise ValueError( + "Input axes indices are inconsistent with the " + "forward transformation." + ) if detect_celestial: return axes_groups, world_axes, celestial_group else: return axes_groups, world_axes - def to_fits_tab(self, bounding_box=None, bin_ext_name='WCS-TABLE', - coord_col_name='coordinates', sampling=1): + def to_fits_tab( + self, + bounding_box=None, + bin_ext_name="WCS-TABLE", + coord_col_name="coordinates", + sampling=1, + ): """ Construct a FITS WCS ``-TAB``-based approximation to the WCS in the form of a FITS header and a binary table extension. For the @@ -2251,9 +2476,7 @@ def to_fits_tab(self, bounding_box=None, bin_ext_name='WCS-TABLE', """ if bounding_box is None: if self.bounding_box is None: - raise ValueError( - "Need a valid bounding_box to compute the footprint." - ) + raise ValueError("Need a valid bounding_box to compute the footprint.") bounding_box = self.bounding_box else: @@ -2272,10 +2495,12 @@ def to_fits_tab(self, bounding_box=None, bin_ext_name='WCS-TABLE', ) try: - sampling = np.broadcast_to(sampling, (self.pixel_n_dim, )) + sampling = np.broadcast_to(sampling, (self.pixel_n_dim,)) except ValueError: - raise ValueError("Number of sampling values either must be 1 " - "or it must match the number of pixel axes.") + raise ValueError( + "Number of sampling values either must be 1 " + "or it must match the number of pixel axes." + ) _, world_axes = self._separable_groups(detect_celestial=False) @@ -2286,15 +2511,26 @@ def to_fits_tab(self, bounding_box=None, bin_ext_name='WCS-TABLE', bounding_box=bounding_box, bin_ext=bin_ext_name, coord_col_name=coord_col_name, - sampling=sampling + sampling=sampling, ) return hdr, bin_table_hdu - def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, - max_inv_pix_error=0.25, inv_degree=None, npoints=32, - crpix=None, projection='TAN', bin_ext_name='WCS-TABLE', - coord_col_name='coordinates', sampling=1, verbose=False): + def to_fits( + self, + bounding_box=None, + max_pix_error=0.25, + degree=None, + max_inv_pix_error=0.25, + inv_degree=None, + npoints=32, + crpix=None, + projection="TAN", + bin_ext_name="WCS-TABLE", + coord_col_name="coordinates", + sampling=1, + verbose=False, + ): """ Construct a FITS WCS ``-TAB``-based approximation to the WCS in the form of a FITS header and a binary table extension. For the @@ -2432,9 +2668,7 @@ def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, """ if bounding_box is None: if self.bounding_box is None: - raise ValueError( - "Need a valid bounding_box to compute the footprint." - ) + raise ValueError("Need a valid bounding_box to compute the footprint.") bounding_box = self.bounding_box else: @@ -2453,10 +2687,12 @@ def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, ) try: - sampling = np.broadcast_to(sampling, (self.pixel_n_dim, )) + sampling = np.broadcast_to(sampling, (self.pixel_n_dim,)) except ValueError: - raise ValueError("Number of sampling values either must be 1 " - "or it must match the number of pixel axes.") + raise ValueError( + "Number of sampling values either must be 1 " + "or it must match the number of pixel axes." + ) world_axes_groups, _, celestial_group = self._separable_groups( detect_celestial=True @@ -2490,16 +2726,16 @@ def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, npoints=npoints, crpix=crpix, projection=projection, - matrix_type='PC-CDELT1', - verbose=verbose + matrix_type="PC-CDELT1", + verbose=verbose, ) - use_cd = 'A_ORDER' in hdr + use_cd = "A_ORDER" in hdr else: use_cd = False hdr = fits.Header() - hdr['NAXIS'] = 0 - hdr['WCSAXES'] = 0 + hdr["NAXIS"] = 0 + hdr["WCSAXES"] = 0 # now handle non-celestial axes using -TAB convention for each # separable axes group: @@ -2515,17 +2751,24 @@ def to_fits(self, bounding_box=None, max_pix_error=0.25, degree=None, bounding_box=bounding_box, bin_ext=(bin_ext_name, extver0 + 1), coord_col_name=coord_col_name, - sampling=sampling + sampling=sampling, ) hdulist.append(bin_table_hdu) - hdr.add_comment('FITS WCS created by approximating a gWCS') + hdr.add_comment("FITS WCS created by approximating a gWCS") return hdr, hdulist - - def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, - bin_ext, coord_col_name, sampling): + def _to_fits_tab( + self, + hdr, + world_axes_group, + use_cd, + bounding_box, + bin_ext, + coord_col_name, + sampling, + ): """ Construct a FITS WCS ``-TAB``-based approximation to the WCS in the form of a FITS header and a binary table extension. For the @@ -2616,11 +2859,11 @@ def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, bin_ext = (bin_ext, 1) if isinstance(bounding_box, Bbox): - bounding_box = bounding_box.bounding_box(order='F') + bounding_box = bounding_box.bounding_box(order="F") if isinstance(bounding_box, list): for index, bbox in enumerate(bounding_box): if isinstance(bbox, Bbox): - bounding_box[index] = bbox.bounding_box(order='F') + bounding_box[index] = bbox.bounding_box(order="F") # identify input axes: input_axes = [] @@ -2636,33 +2879,35 @@ def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, # Create initial header and deal with non-degenerate axes if hdr is None: hdr = fits.Header() - hdr['NAXIS'] = n_inputs, 'number of array dimensions' - hdr['WCSAXES'] = n_outputs - hdr.insert('WCSAXES', ('WCSNAME', f'{self.output_frame.name}'), after=True) + hdr["NAXIS"] = n_inputs, "number of array dimensions" + hdr["WCSAXES"] = n_outputs + hdr.insert("WCSAXES", ("WCSNAME", f"{self.output_frame.name}"), after=True) else: - hdr['NAXIS'] += n_inputs - hdr['WCSAXES'] += n_outputs + hdr["NAXIS"] += n_inputs + hdr["WCSAXES"] += n_outputs # see what axes have been already populated in the header: used_hdr_axes = [] - for v in hdr['naxis*'].keys(): + for v in hdr["naxis*"].keys(): try: - used_hdr_axes.append(int(v.split('NAXIS')[1]) - 1) + used_hdr_axes.append(int(v.split("NAXIS")[1]) - 1) except ValueError: continue degenerate_axis_start = max( - self.pixel_n_dim + 1, - max(used_hdr_axes) + 1 if used_hdr_axes else 1 + self.pixel_n_dim + 1, max(used_hdr_axes) + 1 if used_hdr_axes else 1 ) # Deal with non-degenerate axes and add NAXISi to the header: - offset = hdr.index('NAXIS') + offset = hdr.index("NAXIS") for iax in input_axes: iiax = int(np.searchsorted(used_hdr_axes, iax)) - hdr.insert(iiax + offset + 1, (f'NAXIS{iax + 1:d}', int(max(bounding_box[iiax])) + 1)) + hdr.insert( + iiax + offset + 1, + (f"NAXIS{iax + 1:d}", int(max(bounding_box[iiax])) + 1), + ) # 1D grid coordinates: gcrds = [] @@ -2682,20 +2927,18 @@ def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, } transform = _fix_transform_inputs(self.forward_transform, fixi_dict) - transform = transform | Mapping(world_axes_idx, - n_inputs=self.forward_transform.n_outputs) + transform = transform | Mapping( + world_axes_idx, n_inputs=self.forward_transform.n_outputs + ) - xyz = np.meshgrid(*gcrds[::-1], indexing='ij')[::-1] + xyz = np.meshgrid(*gcrds[::-1], indexing="ij")[::-1] shape = xyz[0].shape xyz = [v.ravel() for v in xyz] - coord = np.stack( - transform(*xyz), - axis=-1 - ) + coord = np.stack(transform(*xyz), axis=-1) - coord = coord.reshape(shape + (len(world_axes_group), )) + coord = coord.reshape(shape + (len(world_axes_group),)) # create header with WCS info: if hdr is None: @@ -2709,36 +2952,36 @@ def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, if len(ct) > 4: raise ValueError("Axis type name too long.") - hdr[f'CTYPE{k1:d}'] = ct + (4 - len(ct)) * '-' + '-TAB' - hdr[f'CUNIT{k1:d}'] = self.world_axis_units[k] - hdr[f'PS{k1:d}_0'] = bin_ext[0] - hdr[f'PV{k1:d}_1'] = bin_ext[1] - hdr[f'PS{k1:d}_1'] = coord_col_name - hdr[f'PV{k1:d}_3'] = widx + 1 - hdr[f'CRVAL{k1:d}'] = 1 + hdr[f"CTYPE{k1:d}"] = ct + (4 - len(ct)) * "-" + "-TAB" + hdr[f"CUNIT{k1:d}"] = self.world_axis_units[k] + hdr[f"PS{k1:d}_0"] = bin_ext[0] + hdr[f"PV{k1:d}_1"] = bin_ext[1] + hdr[f"PS{k1:d}_1"] = coord_col_name + hdr[f"PV{k1:d}_3"] = widx + 1 + hdr[f"CRVAL{k1:d}"] = 1 if widx < n_inputs: m1 = input_axes[widx] + 1 - hdr[f'CRPIX{m1:d}'] = gcrds[widx][0] + 1 + hdr[f"CRPIX{m1:d}"] = gcrds[widx][0] + 1 if use_cd: - hdr[f'CD{k1:d}_{m1:d}'] = cdelt[widx] + hdr[f"CD{k1:d}_{m1:d}"] = cdelt[widx] else: if k1 != m1: - hdr[f'PC{k1:d}_{k1:d}'] = 0.0 - hdr[f'PC{k1:d}_{m1:d}'] = 1.0 - hdr[f'CDELT{k1:d}'] = cdelt[widx] + hdr[f"PC{k1:d}_{k1:d}"] = 0.0 + hdr[f"PC{k1:d}_{m1:d}"] = 1.0 + hdr[f"CDELT{k1:d}"] = cdelt[widx] else: m1 = degenerate_axis_start degenerate_axis_start += 1 - hdr[f'CRPIX{m1:d}'] = 1 + hdr[f"CRPIX{m1:d}"] = 1 if use_cd: - hdr[f'CD{k1:d}_{m1:d}'] = 1.0 + hdr[f"CD{k1:d}_{m1:d}"] = 1.0 else: if k1 != m1: - hdr[f'PC{k1:d}_{k1:d}'] = 0.0 - hdr[f'PC{k1:d}_{m1:d}'] = 1.0 - hdr[f'CDELT{k1:d}'] = 1 + hdr[f"PC{k1:d}_{k1:d}"] = 0.0 + hdr[f"PC{k1:d}_{m1:d}"] = 1.0 + hdr[f"CDELT{k1:d}"] = 1 # Uncomment 3 lines below to enable use of degenerate axes: # hdr['NAXIS'] = hdr['NAXIS'] + 1 @@ -2750,10 +2993,10 @@ def _to_fits_tab(self, hdr, world_axes_group, use_cd, bounding_box, # structured array (data) for binary table HDU: arr = np.array( - [(coord, )], + [(coord,)], dtype=[ (coord_col_name, np.float64, coord.shape), - ] + ], ) # create binary table HDU: @@ -2770,8 +3013,9 @@ def _calc_approx_inv(self, max_inv_pix_error=5, inv_degree=None, npoints=16): try: # try to use analytic inverse if available: - self._approx_inverse = functools.partial(self.backward_transform, - with_bounding_box=False) + self._approx_inverse = functools.partial( + self.backward_transform, with_bounding_box=False + ) return except (NotImplementedError, KeyError): pass @@ -2793,9 +3037,12 @@ def _calc_approx_inv(self, max_inv_pix_error=5, inv_degree=None, npoints=16): # transformation to the middle of the bounding box ("image") in order # to minimize projection effects across the entire image, # thus the initial shift. - ntransform = ((Shift(crpix[0]) & Shift(crpix[1])) | self.forward_transform - | RotateCelestial2Native(crval1, crval2, 180) - | Sky2Pix_TAN()) + ntransform = ( + (Shift(crpix[0]) & Shift(crpix[1])) + | self.forward_transform + | RotateCelestial2Native(crval1, crval2, 180) + | Sky2Pix_TAN() + ) # standard sampling: u, v = _make_sampling_grid(npoints, self.bounding_box, crpix=crpix) @@ -2807,16 +3054,26 @@ def _calc_approx_inv(self, max_inv_pix_error=5, inv_degree=None, npoints=16): fit_inv_poly_u, fit_inv_poly_v, max_inv_resid = _fit_2D_poly( None, - max_inv_pix_error, 1, - undist_x, undist_y, u, v, - undist_xd, undist_yd, ud, vd, - verbose=True + max_inv_pix_error, + 1, + undist_x, + undist_y, + u, + v, + undist_xd, + undist_yd, + ud, + vd, + verbose=True, ) - self._approx_inverse = (RotateCelestial2Native(crval1, crval2, 180) | - Sky2Pix_TAN() | Mapping((0, 1, 0, 1)) | - (fit_inv_poly_u & fit_inv_poly_v) | - (Shift(crpix[0]) & Shift(crpix[1]))) + self._approx_inverse = ( + RotateCelestial2Native(crval1, crval2, 180) + | Sky2Pix_TAN() + | Mapping((0, 1, 0, 1)) + | (fit_inv_poly_u & fit_inv_poly_v) + | (Shift(crpix[0]) & Shift(crpix[1])) + ) def _poly_fit_lu(xin, yin, xout, yout, degree, coord_pow=None): @@ -2835,8 +3092,7 @@ def _poly_fit_lu(xin, yin, xout, yout, degree, coord_pow=None): # on the same coordinate grid in _fit_2D_poly by reusing computed # powers. powers = [ - (i, j) - for i in range(degree + 1) for j in range(degree + 1 - i) if i + j > 0 + (i, j) for i in range(degree + 1) for j in range(degree + 1 - i) if i + j > 0 ] if coord_pow is None: coord_pow = {} @@ -2892,7 +3148,7 @@ def pow2(p, q): a[j, i] = a[i, j] with warnings.catch_warnings(record=True): - warnings.simplefilter('error', category=linalg.LinAlgWarning) + warnings.simplefilter("error", category=linalg.LinAlgWarning) try: lu_piv = linalg.lu_factor(a) poly_coeff_x = linalg.lu_solve(lu_piv, bx).astype(float) @@ -2912,16 +3168,26 @@ def pow2(p, q): fitx = np.dot(pseudo_vander, poly_coeff_x) fity = np.dot(pseudo_vander, poly_coeff_y) - dist = np.sqrt((xout - fitx)**2 + (yout - fity)**2) + dist = np.sqrt((xout - fitx) ** 2 + (yout - fity) ** 2) max_resid = dist.max() return poly_coeff_x, poly_coeff_y, max_resid, powers, cond -def _fit_2D_poly(degree, max_error, plate_scale, - xin, yin, xout, yout, - xind, yind, xoutd, youtd, - verbose=False): +def _fit_2D_poly( + degree, + max_error, + plate_scale, + xin, + yin, + xout, + yout, + xind, + yind, + xoutd, + youtd, + verbose=False, +): """ Fit a pair of ordinary 2D polynomials to the supplied transform. @@ -2929,7 +3195,7 @@ def _fit_2D_poly(degree, max_error, plate_scale, # The case of one pass with the specified polynomial degree if degree is None: deglist = list(range(1, 10)) - elif hasattr(degree, '__iter__'): + elif hasattr(degree, "__iter__"): deglist = sorted(map(int, degree)) if deglist[0] < 1 or deglist[-1] > 9: raise ValueError("Allowed values for SIP degree are [1...9]") @@ -2943,7 +3209,7 @@ def _fit_2D_poly(degree, max_error, plate_scale, fit_error = np.inf if verbose and not single_degree: - print(f'Maximum specified SIP approximation error: {max_error}') + print(f"Maximum specified SIP approximation error: {max_error}") max_error *= plate_scale fit_warning_msg = "Failed to achieve requested SIP approximation accuracy." @@ -2999,8 +3265,8 @@ def _fit_2D_poly(degree, max_error, plate_scale, fit_poly_x = Polynomial2D(degree=deg, c0_0=0.0) fit_poly_y = Polynomial2D(degree=deg, c0_0=0.0) for cx, cy, (p, q) in zip(cfx, cfy, powers): - setattr(fit_poly_x, f'c{p:1d}_{q:1d}', cx) - setattr(fit_poly_y, f'c{p:1d}_{q:1d}', cy) + setattr(fit_poly_x, f"c{p:1d}_{q:1d}", cx) + setattr(fit_poly_y, f"c{p:1d}_{q:1d}", cy) if fit_warning_msg: warnings.warn(fit_warning_msg, linalg.LinAlgWarning) @@ -3008,10 +3274,7 @@ def _fit_2D_poly(degree, max_error, plate_scale, if fit_error <= max_error or single_degree: # Check to see if double sampling meets error requirement. max_resid = _compute_distance_residual( - xoutd, - youtd, - fit_poly_x(xind, yind), - fit_poly_y(xind, yind) + xoutd, youtd, fit_poly_x(xind, yind), fit_poly_y(xind, yind) ) if verbose: print( @@ -3032,9 +3295,7 @@ def _fit_2D_poly(degree, max_error, plate_scale, if verbose: if single_degree: - print( - f"Maximum residual: {fit_error / plate_scale:.5g}" - ) + print(f"Maximum residual: {fit_error / plate_scale:.5g}") else: print( f"* Final SIP degree: {deg}. " @@ -3055,7 +3316,7 @@ def _compute_distance_residual(undist_x, undist_y, fit_poly_x, fit_poly_y): """ Compute the distance residuals and return the rms and maximum values. """ - dist = np.sqrt((undist_x - fit_poly_x)**2 + (undist_y - fit_poly_y)**2) + dist = np.sqrt((undist_x - fit_poly_x) ** 2 + (undist_y - fit_poly_y) ** 2) max_resid = dist.max() return max_resid @@ -3088,11 +3349,11 @@ def _reform_poly_coefficients(fit_poly_x, fit_poly_y): for i in range(0, degree + 1): for j in range(0, degree + 1): if (i + j > 1) and (i + j < degree + 1): - old_x = getattr(fit_poly_x, f'c{i}_{j}').value - old_y = getattr(fit_poly_y, f'c{i}_{j}').value + old_x = getattr(fit_poly_x, f"c{i}_{j}").value + old_y = getattr(fit_poly_y, f"c{i}_{j}").value newcoeff = np.dot(invcdmat, np.array([[old_x], [old_y]])) - setattr(sip_poly_x, f'c{i}_{j}', newcoeff[0, 0]) - setattr(sip_poly_y, f'c{i}_{j}', newcoeff[1, 0]) + setattr(sip_poly_x, f"c{i}_{j}", newcoeff[0, 0]) + setattr(sip_poly_y, f"c{i}_{j}", newcoeff[1, 0]) return cdmat, sip_poly_x, sip_poly_y @@ -3106,7 +3367,7 @@ def _store_2D_coefficients(hdr, poly_model, coeff_prefix, keeplinear=False): for i in range(0, degree + 1): for j in range(0, degree + 1): if (i + j) > mindeg and (i + j < degree + 1): - hdr[f'{coeff_prefix}_{i}_{j}'] = getattr(poly_model, f'c{i}_{j}').value + hdr[f"{coeff_prefix}_{i}_{j}"] = getattr(poly_model, f"c{i}_{j}").value def _fix_transform_inputs(transform, inputs): @@ -3125,10 +3386,7 @@ def _fix_transform_inputs(transform, inputs): c = 0 if c is None else (c + 1) mapping.append(c) - in_selector = Mapping( - mapping, - n_inputs = transform.n_inputs - len(inputs) - ) + in_selector = Mapping(mapping, n_inputs=transform.n_inputs - len(inputs)) input_fixer = Const1D(inputs[0]) if 0 in inputs else Identity(1) for k in range(1, transform.n_inputs): @@ -3151,6 +3409,7 @@ class Step: A transform from this step's frame to next step's frame. The transform of the last step should be `None`. """ + def __init__(self, frame, transform=None): self.frame = frame self.transform = transform @@ -3162,7 +3421,9 @@ def frame(self): @frame.setter def frame(self, val): if not isinstance(val, (cf.CoordinateFrame, str)): - raise TypeError('"frame" should be an instance of CoordinateFrame or a string.') + raise TypeError( + '"frame" should be an instance of CoordinateFrame or a string.' + ) self._frame = val @@ -3173,7 +3434,9 @@ def transform(self): @transform.setter def transform(self, val): if val is not None and not isinstance(val, (Model)): - raise TypeError('"transform" should be an instance of astropy.modeling.Model.') + raise TypeError( + '"transform" should be an instance of astropy.modeling.Model.' + ) self._transform = val @property @@ -3183,8 +3446,11 @@ def frame_name(self): return self.frame.name def __getitem__(self, ind): - warnings.warn("Indexing a WCS.pipeline step is deprecated. " - "Use the `frame` and `transform` attributes instead.", DeprecationWarning) + warnings.warn( + "Indexing a WCS.pipeline step is deprecated. " + "Use the `frame` and `transform` attributes instead.", + DeprecationWarning, + ) if ind not in (0, 1): raise IndexError("Allowed inices are 0 (frame) and 1 (transform).") if ind == 0: diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 0038d1f3..f7674ec0 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -14,11 +14,18 @@ from .utils import _compute_lon_pole -__all__ = ['wcs_from_fiducial', 'grid_from_bounding_box', 'wcs_from_points'] - - -def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, - transform=None, name='', bounding_box=None, input_frame=None): +__all__ = ["wcs_from_fiducial", "grid_from_bounding_box", "wcs_from_points"] + + +def wcs_from_fiducial( + fiducial, + coordinate_frame=None, + projection=None, + transform=None, + name="", + bounding_box=None, + input_frame=None, +): """ Create a WCS object from a fiducial point in a coordinate frame. @@ -57,33 +64,41 @@ def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, if transform is not None: if not isinstance(transform, Model): - raise UnsupportedTransformError("Expected transform to be an instance" - "of astropy.modeling.Model") + raise UnsupportedTransformError( + "Expected transform to be an instance" "of astropy.modeling.Model" + ) # transform_outputs = transform.n_outputs if isinstance(fiducial, coord.SkyCoord): - coordinate_frame = CelestialFrame(reference_frame=fiducial.frame, - unit=(fiducial.spherical.lon.unit, - fiducial.spherical.lat.unit)) + coordinate_frame = CelestialFrame( + reference_frame=fiducial.frame, + unit=(fiducial.spherical.lon.unit, fiducial.spherical.lat.unit), + ) fiducial_transform = _sky_transform(fiducial, projection) elif isinstance(coordinate_frame, CompositeFrame): trans_from_fiducial = [] for item in coordinate_frame.frames: ind = coordinate_frame.frames.index(item) try: - model = frame2transform[item.__class__](fiducial[ind], projection=projection) + model = frame2transform[item.__class__]( + fiducial[ind], projection=projection + ) except KeyError: raise TypeError("Coordinate frame {0} is not supported".format(item)) trans_from_fiducial.append(model) - fiducial_transform = functools.reduce(lambda x, y: x & y, - [tr for tr in trans_from_fiducial]) + fiducial_transform = functools.reduce( + lambda x, y: x & y, [tr for tr in trans_from_fiducial] + ) else: # The case of one coordinate frame with more than 1 axes. try: - fiducial_transform = frame2transform[coordinate_frame.__class__](fiducial, - projection=projection) + fiducial_transform = frame2transform[coordinate_frame.__class__]( + fiducial, projection=projection + ) except KeyError: - raise TypeError("Coordinate frame {0} is not supported".format(coordinate_frame)) + raise TypeError( + "Coordinate frame {0} is not supported".format(coordinate_frame) + ) if transform is not None: forward_transform = transform | fiducial_transform @@ -91,18 +106,26 @@ def wcs_from_fiducial(fiducial, coordinate_frame=None, projection=None, forward_transform = fiducial_transform if bounding_box is not None: if len(bounding_box) != forward_transform.n_outputs: - raise ValueError("Expected the number of items in 'bounding_box' to be equal to the " - "number of outputs of the forawrd transform.") + raise ValueError( + "Expected the number of items in 'bounding_box' to be equal to the " + "number of outputs of the forawrd transform." + ) forward_transform.bounding_box = bounding_box[::-1] if input_frame is None: - input_frame = 'detector' - return WCS(output_frame=coordinate_frame, input_frame=input_frame, - forward_transform=forward_transform, name=name) + input_frame = "detector" + return WCS( + output_frame=coordinate_frame, + input_frame=input_frame, + forward_transform=forward_transform, + name=name, + ) def _verify_projection(projection): if projection is None: - raise ValueError("Celestial coordinate frame requires a projection to be specified.") + raise ValueError( + "Celestial coordinate frame requires a projection to be specified." + ) if not isinstance(projection, projections.Projection): raise UnsupportedProjectionError(projection) @@ -129,15 +152,17 @@ def _spectral_transform(fiducial, **kwargs): def _frame2D_transform(fiducial, **kwargs): - fiducial_transform = functools.reduce(lambda x, y: x & y, - [models.Shift(val) for val in fiducial]) + fiducial_transform = functools.reduce( + lambda x, y: x & y, [models.Shift(val) for val in fiducial] + ) return fiducial_transform -frame2transform = {CelestialFrame: _sky_transform, - SpectralFrame: _spectral_transform, - Frame2D: _frame2D_transform - } +frame2transform = { + CelestialFrame: _sky_transform, + SpectralFrame: _spectral_transform, + Frame2D: _frame2D_transform, +} def grid_from_bounding_box(bounding_box, step=1, center=True, selector=None): @@ -189,6 +214,7 @@ def grid_from_bounding_box(bounding_box, step=1, center=True, selector=None): x, y [, z]: ndarray Grid of points. """ + def _bbox_to_pixel(bbox): return (np.floor(bbox[0] + 0.5), np.ceil(bbox[1] - 0.5)) @@ -197,7 +223,9 @@ def _bbox_to_pixel(bbox): if isinstance(bounding_box, CompoundBoundingBox): if selector is None: - raise ValueError("selector must be set when bounding_box is a CompoundBoundingBox") + raise ValueError( + "selector must be set when bounding_box is a CompoundBoundingBox" + ) bounding_box = bounding_box[selector] @@ -214,7 +242,7 @@ def _bbox_to_pixel(bbox): # 1D case if np.isscalar(bounding_box[0]): ndim = 1 - bounding_box = (bounding_box, ) + bounding_box = (bounding_box,) else: ndim = len(bounding_box) if center: @@ -227,8 +255,9 @@ def _bbox_to_pixel(bbox): step = np.repeat(step, ndim) if len(step) != len(bb): - raise ValueError('`step` must be a scalar, or tuple with length ' - 'matching `bounding_box`') + raise ValueError( + "`step` must be a scalar, or tuple with length " "matching `bounding_box`" + ) slices = [] for d, s in zip(bb, step): @@ -239,9 +268,14 @@ def _bbox_to_pixel(bbox): return grid -def wcs_from_points(xy, world_coords, proj_point='center', - projection=projections.Sky2Pix_TAN(), poly_degree=4, - polynomial_type='polynomial'): +def wcs_from_points( + xy, + world_coords, + proj_point="center", + projection=projections.Sky2Pix_TAN(), + poly_degree=4, + polynomial_type="polynomial", +): """ Given two matching sets of coordinates on detector and sky, compute the WCS. @@ -286,15 +320,16 @@ def wcs_from_points(xy, world_coords, proj_point='center', """ from .wcs import WCS - supported_poly_types = {"polynomial": models.Polynomial2D, - "chebyshev": models.Chebyshev2D, - "legendre": models.Legendre2D - } + supported_poly_types = { + "polynomial": models.Polynomial2D, + "chebyshev": models.Chebyshev2D, + "legendre": models.Legendre2D, + } x, y = xy if not isinstance(world_coords, coord.SkyCoord): - raise TypeError('`world_coords` must be an `~astropy.coordinates.SkyCoord`') + raise TypeError("`world_coords` must be an `~astropy.coordinates.SkyCoord`") try: lon, lat = world_coords.data.lon.deg, world_coords.data.lat.deg except AttributeError: @@ -306,29 +341,37 @@ def wcs_from_points(xy, world_coords, proj_point='center', proj_point.transform_to(world_coords) crval = (proj_point.data.lon, proj_point.data.lat) frame = proj_point.frame - elif proj_point == 'center': # use center of input points - sc1 = coord.SkyCoord(lon.min()*u.deg, lat.max()*u.deg) - sc2 = coord.SkyCoord(lon.max()*u.deg, lat.min()*u.deg) + elif proj_point == "center": # use center of input points + sc1 = coord.SkyCoord(lon.min() * u.deg, lat.max() * u.deg) + sc2 = coord.SkyCoord(lon.max() * u.deg, lat.min() * u.deg) pa = sc1.position_angle(sc2) sep = sc1.separation(sc2) - midpoint_sc = sc1.directional_offset_by(pa, sep/2) + midpoint_sc = sc1.directional_offset_by(pa, sep / 2) crval = (midpoint_sc.data.lon, midpoint_sc.data.lat) frame = sc1.frame else: - raise ValueError("`proj_point` must be set to 'center', or an" + - "`~astropy.coordinates.SkyCoord` object with " + - "a pair of points.") + raise ValueError( + "`proj_point` must be set to 'center', or an" + + "`~astropy.coordinates.SkyCoord` object with " + + "a pair of points." + ) if not isinstance(projection, projections.Projection): - raise UnsupportedProjectionError("Unsupported projection code {0}".format(projection)) + raise UnsupportedProjectionError( + "Unsupported projection code {0}".format(projection) + ) if polynomial_type not in supported_poly_types.keys(): - raise ValueError("Unsupported polynomial_type: {}. " - "Only one of {} is supported.".format(polynomial_type, - supported_poly_types.keys())) + raise ValueError( + "Unsupported polynomial_type: {}. " "Only one of {} is supported.".format( + polynomial_type, supported_poly_types.keys() + ) + ) - skyrot = models.RotateCelestial2Native(crval[0].to_value(u.deg), crval[1].to_value(u.deg), 180) - trans = (skyrot | projection) + skyrot = models.RotateCelestial2Native( + crval[0].to_value(u.deg), crval[1].to_value(u.deg), 180 + ) + trans = skyrot | projection projection_x, projection_y = trans(lon, lat) poly = supported_poly_types[polynomial_type](poly_degree) @@ -341,13 +384,14 @@ def wcs_from_points(xy, world_coords, proj_point='center', poly_x_inverse = fitter(poly, projection_x, projection_y, x) poly_y_inverse = fitter(poly, projection_x, projection_y, y) - distortion.inverse = models.Mapping((0, 1, 0, 1)) | poly_x_inverse & poly_y_inverse + distortion.inverse = ( + models.Mapping((0, 1, 0, 1)) | poly_x_inverse & poly_y_inverse + ) transform = distortion | projection.inverse | skyrot.inverse skyframe = CelestialFrame(reference_frame=frame) detector = Frame2D(name="detector") - pipeline = [(detector, transform), - (skyframe, None)] + pipeline = [(detector, transform), (skyframe, None)] return WCS(pipeline) From 95b03db27ed5a51db7db397a00b610cafe7f46ac Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 13:52:45 -0500 Subject: [PATCH 05/31] Apply full flake8 linting --- docs/conf.py | 2 +- gwcs/coordinate_frames.py | 34 +++++++++++++--------- gwcs/region.py | 3 +- gwcs/selector.py | 12 ++++---- gwcs/spectroscopy.py | 11 +++---- gwcs/tests/test_api.py | 5 +++- gwcs/wcs.py | 60 +++++++++++++++++++++++++-------------- gwcs/wcstools.py | 10 ++++--- pyproject.toml | 47 ++++++++++++++++++++++++++++++ 9 files changed, 132 insertions(+), 52 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 41a91823..e5910027 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # This is added to the end of RST files - a good place to put substitutions to # be used globally. -rst_epilog += """ +rst_epilog += """ """ # noqa: F405 # Top-level directory containing ASDF schemas (relative to current directory) diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 46979f3d..e3c8fc9f 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -54,21 +54,24 @@ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ One of the key concepts regarding coordinate frames is the ``axes_order`` argument. -This argument is used to map from the components of the frame to the inputs/outputs of the transform. -To illustrate this consider this situation where you have a forward transform -which outputs three coordinates ``[lat, lambda, lon]``. These would be -represented as a `.SpectralFrame` and a `.CelestialFrame`, however, the axes of -a `.CelestialFrame` are always ``[lon, lat]``, so by specifying two frames as +This argument is used to map from the components of the frame to the inputs/outputs +of the transform. To illustrate this consider this situation where you have a +forward transform which outputs three coordinates ``[lat, lambda, lon]``. These +would be represented as a `.SpectralFrame` and a `.CelestialFrame`, however, the +axes of a `.CelestialFrame` are always ``[lon, lat]``, so by specifying two +frames as .. code-block:: python [SpectralFrame(axes_order=(1,)), CelestialFrame(axes_order=(2, 0))] we would map the outputs of this transform into the correct positions in the frames. - As shown below, this is also used when constructing the inputs to the inverse transform. + As shown below, this is also used when constructing the inputs to the inverse + transform. -When taking the output from the forward transform the following transformation is performed by the coordinate frames: +When taking the output from the forward transform the following transformation +is performed by the coordinate frames: .. code-block:: @@ -90,7 +93,8 @@ SpectralCoord(lambda) SkyCoord((lon, lat)) -When considering the backward transform the following transformations take place in the coordinate frames before the transform is called: +When considering the backward transform the following transformations take place +in the coordinate frames before the transform is called: .. code-block:: @@ -400,7 +404,8 @@ class CoordinateFrame(BaseCoordinateFrame): axes_order : tuple of int A dimension in the input data that corresponds to this axis. reference_frame : astropy.coordinates.builtin_frames - Reference frame (usually used with output_frame to convert to world coordinate objects). + Reference frame (usually used with output_frame to convert to world + coordinate objects). unit : list of astropy.units.Unit Unit for each axis. axes_names : list @@ -564,7 +569,8 @@ def to_high_level_coordinates(self, *values): high_level_coordinates One (or more) high level object describing the coordinate. """ - # We allow Quantity-like objects here which values_to_high_level_objects does not. + # We allow Quantity-like objects here which values_to_high_level_objects + # does not. values = [ v.to_value(unit) if hasattr(v, "to_value") else v for v, unit in zip(values, self.unit) @@ -609,8 +615,9 @@ class CelestialFrame(CoordinateFrame): Representation of a Celesital coordinate system. This class has a native order of longitude then latitude, meaning - ``axes_names``, ``unit`` and ``axis_physical_types`` should be lon, lat ordered. If your transform is - in a different order this should be specified with ``axes_order``. + ``axes_names``, ``unit`` and ``axis_physical_types`` should be lon, lat + ordered. If your transform is in a different order this should be specified + with ``axes_order``. Parameters ---------- @@ -713,7 +720,8 @@ class SpectralFrame(CoordinateFrame): axes_order : tuple or int A dimension in the input data that corresponds to this axis. reference_frame : astropy.coordinates.builtin_frames - Reference frame (usually used with output_frame to convert to world coordinate objects). + Reference frame (usually used with output_frame to convert to world + coordinate objects). unit : str or units.Unit instance Spectral unit. axes_names : str diff --git a/gwcs/region.py b/gwcs/region.py index ea71a9b4..865e9b60 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -59,7 +59,8 @@ def scan(self, mask): Parameters ---------- mask : ndarray - An array with the shape of the mask to be uised in `~gwcs.selector.RegionsSelector`. + An array with the shape of the mask to be uised in + `~gwcs.selector.RegionsSelector`. Returns ------- diff --git a/gwcs/selector.py b/gwcs/selector.py index 54bcad03..f209a02d 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -20,9 +20,10 @@ The labels are used by `RegionsSelector` to match inputs to transforms. Finally, `RegionsSelector` evaluates the transforms on the corresponding inputs. Label mappers and transforms take the same inputs as -`RegionsSelector`. The inputs should be filtered appropriately using the ``inputs_mapping`` -argument which is ian instance of `~astropy.modeling.mappings.Mapping`. -The transforms in "transform_selector" should have the same number of inputs and outputs. +`RegionsSelector`. The inputs should be filtered appropriately using the +``inputs_mapping`` argument which is ian instance of +`~astropy.modeling.mappings.Mapping`. The transforms in "transform_selector" +should have the same number of inputs and outputs. This is illustrated below using two regions, labeled 1 and 2 :: @@ -239,7 +240,7 @@ def from_vertices(cls, shape, regions): ... } >>> mapper = LabelMapperArray.from_vertices((2400, 2400), regions) - """ + """ # noqa: E501 labels = np.array(list(regions.keys())) mask = np.zeros(shape, dtype=labels.dtype) @@ -565,7 +566,8 @@ def uses_quantity(self): return False else: raise ValueError( - "You can not mix models which use quantity and do not use quantity inside a RegionSelector" + "You can not mix models which use quantity and do not use " + "quantity inside a RegionSelector" ) def set_input(self, rid): diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index b963f9de..a13a286e 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -49,7 +49,7 @@ class WavelengthFromGratingEquation(Model): >>> print(lam) -1.7453292519934437e-10 m - """ + """ # noqa: E501 _separable = False @@ -105,7 +105,7 @@ class AnglesFromGratingEquation3D(Model): >>> print(alpha_out, beta_out, gamma_out) 0.04000174532925199 -1.7453292519934436e-06 0.9991996098716049 - """ + """ # noqa: E501 _separable = False linear = False @@ -382,11 +382,12 @@ def evaluate( 1.0 + (ref_temp - 15) * 3.4785e-3 ) - # Compute the relative index of the glass at Tref and Pref using Sellmeier equation I. + # Compute the relative index of the glass at Tref and Pref using + # Sellmeier equation I. lamrel = wavelength * nair_obs / nair_ref nrel = SellmeierGlass.evaluate(lamrel[0], B_coef, C_coef) - # Convert the relative index of refraction at the reference temperature and pressure - # to absolute. + # Convert the relative index of refraction at the reference temperature + # and pressure to absolute. nabs_ref = nrel * nair_ref # Compute the absolute index of the glass diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index b1a818ae..c6848b08 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -621,7 +621,10 @@ def test_mismatched_high_level_types(gwcs_3d_identity_units): with pytest.raises( TypeError, - match="Invalid types were passed.*(tuple, SpectralCoord).*(SkyCoord, SpectralCoord).*", + match=( + "Invalid types were passed.*(tuple, SpectralCoord)" + ".*(SkyCoord, SpectralCoord).*", + ), ): wcs.invert((1 * u.deg, 2 * u.deg), coord.SpectralCoord(10 * u.nm)) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 533e1b43..ddade258 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -143,8 +143,9 @@ class WCS(GWCSAPIMixin): forward_transform : `~astropy.modeling.Model` or a list The transform between ``input_frame`` and ``output_frame``. A list of (frame, transform) tuples where ``frame`` is the starting frame and - ``transform`` is the transform from this frame to the next one or ``output_frame``. - The last tuple is (transform, None), where None indicates the end of the pipeline. + ``transform`` is the transform from this frame to the next one or + ``output_frame``. The last tuple is (transform, None), where None indicates + the end of the pipeline. input_frame : str, `~gwcs.coordinate_frames.CoordinateFrame` A coordinates object or a string name. output_frame : str, `~gwcs.coordinate_frames.CoordinateFrame` @@ -204,7 +205,8 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): "(frame, transform) list, got {0}".format(type(forward_transform)) ) else: - # Initialize a WCS without a forward_transform - allows building a WCS programmatically. + # Initialize a WCS without a forward_transform - allows building a + # WCS programmatically. if output_frame is None: raise CoordinateFrameError( "An output_frame must be specified " "if forward_transform is None." @@ -302,8 +304,8 @@ def forward_transform(self): if self.bounding_box is not None: # Currently compound models do not attempt to combine individual model - # bounding boxes. Get the forward transform and assign the bounding_box to it - # before evaluating it. The order Model.bounding_box is reversed. + # bounding boxes. Get the forward transform and assign the bounding_box + # to it before evaluating it. The order Model.bounding_box is reversed. transform.bounding_box = self.bounding_box return transform @@ -311,7 +313,8 @@ def forward_transform(self): @property def backward_transform(self): """ - Return the total backward transform if available - from output to input coordinate system. + Return the total backward transform if available - from output to input + coordinate system. Raises ------ @@ -520,7 +523,8 @@ def invert(self, *args, **kwargs): Parameters ---------- - args : float, array like, `~astropy.coordinates.SkyCoord` or `~astropy.units.Unit` + args : float, array like, `~astropy.coordinates.SkyCoord` or + `~astropy.units.Unit` Coordinates to be inverted. The number of arguments must be equal to the number of world coordinates given by ``world_n_dim``. @@ -533,8 +537,8 @@ def invert(self, *args, **kwargs): Output value for inputs outside the bounding_box (default is ``np.nan``). with_units : bool, optional - If ``True`` then high level astropy object (i.e. ``Quantity``) will be returned. - Optional, default=False. + If ``True`` then high level astropy object (i.e. ``Quantity``) will + be returned. Optional, default=False. Other Parameters ---------------- @@ -923,10 +927,11 @@ def numerical_inverse( [3.65405005e+04 1.31364188e+04] [2.76552923e-05 1.14789013e-05]] - """ + """ # noqa: E501 if kwargs.pop("with_units", False): raise ValueError( - "Support for with_units in numerical_inverse has been removed, use inverse" + "Support for with_units in numerical_inverse has been removed, " + "use inverse" ) args_shape = np.shape(args) @@ -1313,8 +1318,8 @@ def transform(self, from_frame, to_frame, *args, **kwargs): If ``True`` - returns a `~astropy.coordinates.SkyCoord` or `~astropy.coordinates.SpectralCoord` object. with_bounding_box : bool, optional - If True(default) values in the result which correspond to any of the inputs being - outside the bounding_box are set to ``fill_value``. + If True(default) values in the result which correspond to any of + the inputs being outside the bounding_box are set to ``fill_value``. fill_value : float, optional Output value for inputs outside the bounding_box (default is np.nan). """ @@ -1530,9 +1535,12 @@ def bounding_box(self): and (isinstance(bb, CompoundBoundingBox) or len(bb) > 1) ): warnings.warn( - "The bounding_box was set in C order on the transform prior to being used in the gwcs!\n" - "Check that you intended that ordering for the bounding_box, and consider setting it in F order.\n" - "The bounding_box will remain meaning the same but will be converted to F order for consistency in the GWCS.", + "The bounding_box was set in C order on the transform prior to " + "being used in the gwcs!\n" + "Check that you intended that ordering for the bounding_box, " + "and consider setting it in F order.\n" + "The bounding_box will remain meaning the same but will be " + "converted to F order for consistency in the GWCS.", GwcsBoundingBoxWarning, ) self.bounding_box = bb.bounding_box(order="F") @@ -1546,7 +1554,8 @@ def bounding_box(self, value): Set the range of acceptable values for each input axis. The order of the axes is `~gwcs.coordinate_frames.CoordinateFrame.axes_order`. - For two inputs and axes_order(0, 1) the bounding box is ((xlow, xhigh), (ylow, yhigh)). + For two inputs and axes_order(0, 1) the bounding box is + ((xlow, xhigh), (ylow, yhigh)). Parameters ---------- @@ -1660,8 +1669,8 @@ def _order_clockwise(v): else: vertices = np.array(list(itertools.product(*bb))).T - # workaround an issue with bbox with quantity, interval needs to be a cquantity, not a list of quantities - # strip units + # workaround an issue with bbox with quantity, interval needs to be a cquantity, + # not a list of quantities strip units if center: vertices = utils._toindex(vertices) @@ -3458,8 +3467,15 @@ def __getitem__(self, ind): return self.transform def __str__(self): - return f"{self.frame_name}\t {getattr(self.transform, 'name', 'None') or self.transform.__class__.__name__}" + return ( + f"{self.frame_name}\t " + f"{getattr(self.transform, 'name', 'None') or + type(self.transform).__name__}" + ) def __repr__(self): - return f"Step(frame={self.frame_name}, \ - transform={getattr(self.transform, 'name', 'None') or self.transform.__class__.__name__})" + return ( + f"Step(frame={self.frame_name}, " + f"transform={getattr(self.transform, 'name', 'None') or + type(self.transform).__name__})" + ) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index f7674ec0..50f6c400 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -54,9 +54,10 @@ def wcs_from_fiducial( bounding_box : tuple The bounding box over which the WCS is valid. It is a tuple of tuples of size 2 where each tuple - represents a range of (low, high) values. The ``bounding_box`` is in the order of - the axes, `~gwcs.coordinate_frames.CoordinateFrame.axes_order`. - For two inputs and axes_order(0, 1) the bounding box is ((xlow, xhigh), (ylow, yhigh)). + represents a range of (low, high) values. The ``bounding_box`` is in the + order of the axes, `~gwcs.coordinate_frames.CoordinateFrame.axes_order`. + For two inputs and axes_order(0, 1) the bounding box is + ((xlow, xhigh), (ylow, yhigh)). input_frame : ~gwcs.coordinate_frames.CoordinateFrame` The input coordinate frame. """ @@ -177,7 +178,8 @@ def grid_from_bounding_box(bounding_box, step=1, center=True, selector=None): Parameters ---------- - bounding_box : tuple | ~astropy.modeling.bounding_box.ModelBoundingBox | ~astropy.modeling.bounding_box.CompoundBoundingBox + bounding_box : tuple | ~astropy.modeling.bounding_box.ModelBoundingBox | + ~astropy.modeling.bounding_box.CompoundBoundingBox The bounding_box of a WCS object, `~gwcs.wcs.WCS.bounding_box`. step : scalar or tuple Step size for grid in each dimension. Scalar applies to all dimensions. diff --git a/pyproject.toml b/pyproject.toml index 129d10fa..42ba5b9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,3 +112,50 @@ exclude_lines = [ ] [tool.setuptools_scm] + +[tool.ruff.lint] +select = [ + "F", # Pyflakes (part of default flake8) + "W", "E", # pycodestyle (part of default flake8) + #"I", # isort (import sorting) + # "N", # pep8-naming + #"D", # pydocstyle (docstring style guide) + #"UP", # pyupgrade (upgrade code to modern python) + # "YTT", # flake8-2020 (system version info) + # "ANN", # flake8-annotations (best practices for type annotations) + #"S", # flake8-bandit (security checks) + # "BLE", # flake8-blind-except (prevent blind except statements) + #"B", # flake8-bugbear (prevent common gotcha bugs) + # "A", # flake8-builtins (prevent shadowing of builtins) + # "C4", # flake8-comprehensions (best practices for comprehensions) + # "T10", # flake8-debugger (prevent debugger statements in code) + #"EM", # flake8-errormessages (best practices for error messages) + # "FA", # flake8-future-annotations (correct usage future annotations) + # "ISC", # flake8-implicit-str-concat (prevent implicit string concat) + # "ICN", # flake8-import-conventions (enforce import conventions) + #"G", # flake8-logging-format (best practices for logging) + # "INP", # flake8-no-pep420 (prevent use of PEP420, i.e. implicit name spaces) + #"PIE", # flake8-pie (misc suggested improvement linting) + # "T20", # flake8-print (prevent print statements in code) + #"PT", # flake8-pytest-style (best practices for pytest) + #"Q", # flake8-quotes (best practices for quotes) + # "RSE", # flake8-raise (best practices for raising exceptions) + #"RET", # flake8-return (best practices for return statements) + #"SLF", # flake8-self (prevent private member access) + # "SLOT", # flake8-slots (require __slots__ for immutable classes) + #"SIM", # flake8-simplify (suggest simplifications to code where possible) + # "TID", # flake8-tidy-imports (prevent banned api and best import practices) + # "TCH", # flake8-type-checking (move type checking imports into type checking blocks) + # "INT", # flake8-gettext (when to use printf style strings) + # "ARG", # flake8-unused-arguments (prevent unused arguments) + #"PTH", # flake8-use-pathlib (prefer pathlib over os.path) + # "ERA", # eradicate (remove commented out code) + # "PGH", # pygrep (simple grep checks) + #"PL", # pylint (general linting, flake8 alternative) + #"TRY", # tryceratops (linting for try/except blocks) + # "FLY", # flynt (f-string conversion where possible) + #"NPY", # NumPy-specific checks (recommendations from NumPy) + #"PERF", # Perflint (performance linting) + # "LOG", + #"RUF", # ruff specific checks +] From 9550436d825548027dbff7a0fb0ced6e2a6d2d12 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:05:38 -0500 Subject: [PATCH 06/31] Add ruff fixes --- convert_schemas.py | 12 ++++---- gwcs/converters/geometry.py | 14 ++++++--- gwcs/converters/selector.py | 10 +++---- gwcs/converters/spectroscopy.py | 18 ++++++------ gwcs/converters/wcs.py | 40 +++++++++++++------------- gwcs/coordinate_frames.py | 16 ++++------- gwcs/extension.py | 2 +- gwcs/geometry.py | 4 +-- gwcs/region.py | 8 +++--- gwcs/selector.py | 4 +-- gwcs/spectroscopy.py | 4 +-- gwcs/tests/test_api.py | 5 ++-- gwcs/tests/test_spectroscopy_models.py | 4 +-- gwcs/wcs.py | 13 ++++++--- gwcs/wcstools.py | 2 +- pyproject.toml | 2 +- 16 files changed, 83 insertions(+), 75 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index f8ca257d..8ab25d49 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -315,19 +315,19 @@ def recurse(o, name, schema, path, level, required=False): o.write(indent) o.write(":category:`Definitions:`\n\n") for key, val in schema["definitions"].items(): - recurse(o, key, val, path + ["definitions", key], level + 1) + recurse(o, key, val, [*path, "definitions", key], level + 1) if "anyOf" in schema and len(schema["anyOf"]) > 1: o.write(indent) o.write(":category:`Any of:`\n\n") for i, subschema in enumerate(schema["anyOf"]): - recurse(o, "—", subschema, path + ["anyOf", str(i)], level + 1) + recurse(o, "—", subschema, [*path, "anyOf", str(i)], level + 1) elif "allOf" in schema and len(schema["allOf"]) > 1: o.write(indent) o.write(":category:`All of:`\n\n") for i, subschema in enumerate(schema["allOf"]): - recurse(o, i, subschema, path + ["allOf", str(i)], level + 1) + recurse(o, i, subschema, [*path, "allOf", str(i)], level + 1) if schema.get("type") == "object": o.write(indent) @@ -337,7 +337,7 @@ def recurse(o, name, schema, path, level, required=False): o, key, val, - path + ["properties", key], + [*path, "properties", key], level + 1, key in schema.get("required", []), ) @@ -347,11 +347,11 @@ def recurse(o, name, schema, path, level, required=False): o.write(":category:`Items:`\n\n") items = schema.get("items") if isinstance(items, dict): - recurse(o, "items", items, path + ["items"], level + 1) + recurse(o, "items", items, [*path, "items"], level + 1) elif isinstance(items, list): for i, val in enumerate(items): name = "index[{0}]".format(i) - recurse(o, name, val, path + [str(i)], level + 1) + recurse(o, name, val, [*path, str(i)], level + 1) if "examples" in schema: o.write(indent) diff --git a/gwcs/converters/geometry.py b/gwcs/converters/geometry.py index 88f47557..88f41b8e 100644 --- a/gwcs/converters/geometry.py +++ b/gwcs/converters/geometry.py @@ -10,8 +10,11 @@ class DirectionCosinesConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/direction_cosines-*"] - types = ["gwcs.geometry.ToDirectionCosines", "gwcs.geometry.FromDirectionCosines"] + tags = ("tag:stsci.edu:gwcs/direction_cosines-*",) + types = ( + "gwcs.geometry.ToDirectionCosines", + "gwcs.geometry.FromDirectionCosines", + ) def from_yaml_tree_transform(self, node, tag, ctx): from ..geometry import ToDirectionCosines, FromDirectionCosines @@ -38,8 +41,11 @@ def to_yaml_tree_transform(self, model, tag, ctx): class SphericalCartesianConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/spherical_cartesian-*"] - types = ["gwcs.geometry.SphericalToCartesian", "gwcs.geometry.CartesianToSpherical"] + tags = ("tag:stsci.edu:gwcs/spherical_cartesian-*",) + types = ( + "gwcs.geometry.SphericalToCartesian", + "gwcs.geometry.CartesianToSpherical", + ) def from_yaml_tree_transform(self, node, tag, ctx): from ..geometry import SphericalToCartesian, CartesianToSpherical diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index b176090d..3dbb0fe8 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -15,13 +15,13 @@ class LabelMapperConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/label_mapper-*"] - types = [ + tags = ("tag:stsci.edu:gwcs/label_mapper-*",) + types = ( "gwcs.selector.LabelMapperArray", "gwcs.selector.LabelMapperDict", "gwcs.selector.LabelMapperRange", "gwcs.selector.LabelMapper", - ] + ) def from_yaml_tree_transform(self, node, tag, ctx): from ..selector import ( @@ -106,8 +106,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): class RegionsSelectorConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/regions_selector-*"] - types = ["gwcs.selector.RegionsSelector"] + tags = ("tag:stsci.edu:gwcs/regions_selector-*",) + types = ("gwcs.selector.RegionsSelector",) def from_yaml_tree_transform(self, node, tag, ctx): from ..selector import RegionsSelector diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index 98f9cde8..cfd22d8c 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -19,8 +19,8 @@ class SellmeierGlassConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/sellmeier_glass-*"] - types = ["gwcs.spectroscopy.SellmeierGlass"] + tags = ("tag:stsci.edu:gwcs/sellmeier_glass-*",) + types = ("gwcs.spectroscopy.SellmeierGlass",) def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import SellmeierGlass @@ -36,8 +36,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): class SellmeierZemaxConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/sellmeier_zemax-*"] - types = ["gwcs.spectroscopy.SellmeierZemax"] + tags = ("tag:stsci.edu:gwcs/sellmeier_zemax-*",) + types = ("gwcs.spectroscopy.SellmeierZemax",) def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import SellmeierZemax @@ -68,8 +68,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): class Snell3DConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/snell3d-*"] - types = ["gwcs.spectroscopy.Snell3D"] + tags = ("tag:stsci.edu:gwcs/snell3d-*",) + types = ("gwcs.spectroscopy.Snell3D",) def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import Snell3D @@ -81,11 +81,11 @@ def to_yaml_tree_transform(self, model, tag, ctx): class GratingEquationConverter(TransformConverterBase): - tags = ["tag:stsci.edu:gwcs/grating_equation-*"] - types = [ + tags = ("tag:stsci.edu:gwcs/grating_equation-*",) + types = ( "gwcs.spectroscopy.AnglesFromGratingEquation3D", "gwcs.spectroscopy.WavelengthFromGratingEquation", - ] + ) def from_yaml_tree_transform(self, node, tag, ctx): from ..spectroscopy import ( diff --git a/gwcs/converters/wcs.py b/gwcs/converters/wcs.py index de9e1e6f..2da50db9 100644 --- a/gwcs/converters/wcs.py +++ b/gwcs/converters/wcs.py @@ -7,20 +7,20 @@ __all__ = [ - "WCSConverter", "CelestialFrameConverter", "CompositeFrameConverter", "FrameConverter", "SpectralFrameConverter", "StepConverter", - "TemporalFrameConverter", "StokesFrameConverter", + "TemporalFrameConverter", + "WCSConverter", ] class WCSConverter(Converter): - tags = ["tag:stsci.edu:gwcs/wcs-*"] - types = ["gwcs.wcs.WCS"] + tags = ("tag:stsci.edu:gwcs/wcs-*",) + types = ("gwcs.wcs.WCS",) def from_yaml_tree(self, node, tag, ctx): from ..wcs import WCS, GwcsBoundingBoxWarning @@ -46,8 +46,8 @@ def to_yaml_tree(self, gwcsobj, tag, ctx): class StepConverter(Converter): - tags = ["tag:stsci.edu:gwcs/step-*"] - types = ["gwcs.wcs.Step"] + tags = ("tag:stsci.edu:gwcs/step-*",) + types = ("gwcs.wcs.Step",) def from_yaml_tree(self, node, tag, ctx): from ..wcs import Step @@ -59,8 +59,8 @@ def to_yaml_tree(self, step, tag, ctx): class FrameConverter(Converter): - tags = ["tag:stsci.edu:gwcs/frame-*"] - types = ["gwcs.coordinate_frames.CoordinateFrame"] + tags = ("tag:stsci.edu:gwcs/frame-*",) + types = ("gwcs.coordinate_frames.CoordinateFrame",) def _from_yaml_tree(self, node, tag, ctx): kwargs = {"name": node["name"]} @@ -125,8 +125,8 @@ def to_yaml_tree(self, frame, tag, ctx): class Frame2DConverter(FrameConverter): - tags = ["tag:stsci.edu:gwcs/frame2d-*"] - types = ["gwcs.coordinate_frames.Frame2D"] + tags = ("tag:stsci.edu:gwcs/frame2d-*",) + types = ("gwcs.coordinate_frames.Frame2D",) def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import Frame2D @@ -136,8 +136,8 @@ def from_yaml_tree(self, node, tag, ctx): class CelestialFrameConverter(FrameConverter): - tags = ["tag:stsci.edu:gwcs/celestial_frame-*"] - types = ["gwcs.coordinate_frames.CelestialFrame"] + tags = ("tag:stsci.edu:gwcs/celestial_frame-*",) + types = ("gwcs.coordinate_frames.CelestialFrame",) def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import CelestialFrame @@ -147,8 +147,8 @@ def from_yaml_tree(self, node, tag, ctx): class SpectralFrameConverter(FrameConverter): - tags = ["tag:stsci.edu:gwcs/spectral_frame-*"] - types = ["gwcs.coordinate_frames.SpectralFrame"] + tags = ("tag:stsci.edu:gwcs/spectral_frame-*",) + types = ("gwcs.coordinate_frames.SpectralFrame",) def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import SpectralFrame @@ -159,8 +159,8 @@ def from_yaml_tree(self, node, tag, ctx): class CompositeFrameConverter(FrameConverter): - tags = ["tag:stsci.edu:gwcs/composite_frame-*"] - types = ["gwcs.coordinate_frames.CompositeFrame"] + tags = ("tag:stsci.edu:gwcs/composite_frame-*",) + types = ("gwcs.coordinate_frames.CompositeFrame",) def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import CompositeFrame @@ -178,8 +178,8 @@ def to_yaml_tree(self, frame, tag, ctx): class TemporalFrameConverter(FrameConverter): - tags = ["tag:stsci.edu:gwcs/temporal_frame-*"] - types = ["gwcs.coordinate_frames.TemporalFrame"] + tags = ("tag:stsci.edu:gwcs/temporal_frame-*",) + types = ("gwcs.coordinate_frames.TemporalFrame",) def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import TemporalFrame @@ -189,8 +189,8 @@ def from_yaml_tree(self, node, tag, ctx): class StokesFrameConverter(FrameConverter): - tags = ["tag:stsci.edu:gwcs/stokes_frame-*"] - types = ["gwcs.coordinate_frames.StokesFrame"] + tags = ("tag:stsci.edu:gwcs/stokes_frame-*",) + types = ("gwcs.coordinate_frames.StokesFrame",) def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import StokesFrame diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index e3c8fc9f..780fef22 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -139,13 +139,13 @@ __all__ = [ "BaseCoordinateFrame", - "Frame2D", "CelestialFrame", - "SpectralFrame", "CompositeFrame", "CoordinateFrame", - "TemporalFrame", + "Frame2D", + "SpectralFrame", "StokesFrame", + "TemporalFrame", ] @@ -168,9 +168,7 @@ def _ucd1_to_ctype_name_mapping(ctype_to_ucd, allowed_ucd_duplicates): "Found unsupported duplicate physical type in 'astropy' mapping to CTYPE.\n" "Update 'gwcs' to the latest version or notify 'gwcs' developer.\n" "Duplicate physical types will be mapped to the following CTYPEs:\n" - + "\n".join( - [f"{repr(ucd):s} --> {repr(inv_map[ucd]):s}" for ucd in new_ucd] - ) + + "\n".join([f"{ucd!r:s} --> {inv_map[ucd]!r:s}" for ucd in new_ucd]) ) return inv_map @@ -841,10 +839,8 @@ def _default_axis_physical_types(self): def _convert_to_time(self, dt, *, unit, **kwargs): if ( - not isinstance(dt, time.TimeDelta) - and isinstance(dt, time.Time) - or isinstance(self.reference_frame.value, np.ndarray) - ): + not isinstance(dt, time.TimeDelta) and isinstance(dt, time.Time) + ) or isinstance(self.reference_frame.value, np.ndarray): return time.Time(dt, **kwargs) if not hasattr(dt, "unit"): diff --git a/gwcs/extension.py b/gwcs/extension.py index 1001a843..eda21ca2 100644 --- a/gwcs/extension.py +++ b/gwcs/extension.py @@ -78,7 +78,7 @@ class _EmptyExtension(Extension): extension_uri = "asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.0.0" - legacy_class_names = ["gwcs.extension.GWCSExtension"] + legacy_class_names = ("gwcs.extension.GWCSExtension",) TRANSFORM_EXTENSIONS.append(_EmptyExtension()) diff --git a/gwcs/geometry.py b/gwcs/geometry.py index 2fdada0e..8bf27fbd 100644 --- a/gwcs/geometry.py +++ b/gwcs/geometry.py @@ -10,10 +10,10 @@ __all__ = [ - "ToDirectionCosines", + "CartesianToSpherical", "FromDirectionCosines", "SphericalToCartesian", - "CartesianToSpherical", + "ToDirectionCosines", ] diff --git a/gwcs/region.py b/gwcs/region.py index 865e9b60..77210fba 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -12,7 +12,7 @@ from collections import OrderedDict import numpy as np -__all__ = ["Region", "Edge", "Polygon"] +__all__ = ["Edge", "Polygon", "Region"] _INTERSECT_ATOL = 1e2 * np.finfo(float).eps @@ -286,7 +286,7 @@ class Edge: """ - def __init__(self, name=None, start=None, stop=None, next=None): # noqa: A002 + def __init__(self, name=None, start=None, stop=None, next=None): self._start = None if start is not None: self._start = np.asarray(start) @@ -375,11 +375,11 @@ def __repr__(self): return fmt @property - def next(self): # noqa: A003 + def next(self): return self._next @next.setter - def next(self, edge): # noqa: A003 + def next(self, edge): if self._name is None: self._name = edge._name self._stop = edge._stop diff --git a/gwcs/selector.py b/gwcs/selector.py index f209a02d..37f2bd55 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -77,11 +77,11 @@ __all__ = [ + "LabelMapper", "LabelMapperArray", "LabelMapperDict", "LabelMapperRange", "RegionsSelector", - "LabelMapper", ] @@ -554,7 +554,7 @@ def __init__( self._input_units_allow_dimensionless = {key: False for key in self._inputs} super().__init__(n_models=1, name=name, **kwargs) # Validate uses_quantity at init time for nicer error message - self.uses_quantity # noqa + self.uses_quantity @property def uses_quantity(self): diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index a13a286e..df73f5bc 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -10,11 +10,11 @@ __all__ = [ - "WavelengthFromGratingEquation", "AnglesFromGratingEquation3D", - "Snell3D", "SellmeierGlass", "SellmeierZemax", + "Snell3D", + "WavelengthFromGratingEquation", ] diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index c6848b08..cd0c2b33 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -74,7 +74,8 @@ def wcs_ndim_types_units(request): fixture_wcs_ndim_types_units = pytest.mark.parametrize( "wcs_ndim_types_units", fixture_names, indirect=True ) -all_wcses_names = fixture_names + [ +all_wcses_names = [ + *fixture_names, "gwcs_3d_identity_units", "gwcs_stokes_lookup", "gwcs_3d_galactic_spectral", @@ -623,7 +624,7 @@ def test_mismatched_high_level_types(gwcs_3d_identity_units): TypeError, match=( "Invalid types were passed.*(tuple, SpectralCoord)" - ".*(SkyCoord, SpectralCoord).*", + ".*(SkyCoord, SpectralCoord).*" ), ): wcs.invert((1 * u.deg, 2 * u.deg), coord.SpectralCoord(10 * u.nm)) diff --git a/gwcs/tests/test_spectroscopy_models.py b/gwcs/tests/test_spectroscopy_models.py index b7e0013e..0fbb91e6 100644 --- a/gwcs/tests/test_spectroscopy_models.py +++ b/gwcs/tests/test_spectroscopy_models.py @@ -3,8 +3,8 @@ from astropy.modeling.models import Identity import numpy as np from numpy.testing import assert_allclose -from .. import spectroscopy as sp # noqa -from .. import geometry # noqa +from .. import spectroscopy as sp +from .. import geometry def test_angles_grating_equation(): diff --git a/gwcs/wcs.py b/gwcs/wcs.py index ddade258..be5f4161 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1043,9 +1043,9 @@ def _vectorized_fixed_point( crpix = np.mean(self.bounding_box, axis=-1) l1, phi1 = np.deg2rad(self.__call__(*(crpix - 0.5))) - l2, phi2 = np.deg2rad(self.__call__(*(crpix + [-0.5, 0.5]))) + l2, phi2 = np.deg2rad(self.__call__(*(crpix + [-0.5, 0.5]))) # noqa: RUF005 l3, phi3 = np.deg2rad(self.__call__(*(crpix + 0.5))) - l4, phi4 = np.deg2rad(self.__call__(*(crpix + [0.5, -0.5]))) + l4, phi4 = np.deg2rad(self.__call__(*(crpix + [0.5, -0.5]))) # noqa: RUF005 area = np.abs( 0.5 * ( @@ -1950,7 +1950,7 @@ def _to_fits_sip( matrix_type = matrix_type.upper() if matrix_type not in ["CD", "PC-CDELT1", "PC-SUM1", "PC-DET1", "PC-SCALE"]: - raise ValueError(f"Unsupported 'matrix_type' value: {repr(matrix_type)}.") + raise ValueError(f"Unsupported 'matrix_type' value: {matrix_type!r}.") if npoints < 8: raise ValueError( @@ -2947,7 +2947,12 @@ def _to_fits_tab( coord = np.stack(transform(*xyz), axis=-1) - coord = coord.reshape(shape + (len(world_axes_group),)) + coord = coord.reshape( + ( + *shape, + len(world_axes_group), + ) + ) # create header with WCS info: if hdr is None: diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 50f6c400..3460ad88 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -14,7 +14,7 @@ from .utils import _compute_lon_pole -__all__ = ["wcs_from_fiducial", "grid_from_bounding_box", "wcs_from_points"] +__all__ = ["grid_from_bounding_box", "wcs_from_fiducial", "wcs_from_points"] def wcs_from_fiducial( diff --git a/pyproject.toml b/pyproject.toml index 42ba5b9c..7b41633e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,5 +157,5 @@ select = [ #"NPY", # NumPy-specific checks (recommendations from NumPy) #"PERF", # Perflint (performance linting) # "LOG", - #"RUF", # ruff specific checks + "RUF", # ruff specific checks ] From 62d7b34c31c24fabb1746e877be4713d8cde0943 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:06:31 -0500 Subject: [PATCH 07/31] Sort imports --- convert_schemas.py | 2 +- gwcs/api.py | 4 +-- gwcs/converters/geometry.py | 9 +++---- gwcs/converters/selector.py | 11 ++++----- gwcs/converters/spectroscopy.py | 3 +-- gwcs/converters/tests/test_selector.py | 6 ++--- gwcs/converters/tests/test_transforms.py | 6 ++--- gwcs/converters/tests/test_wcs.py | 10 ++++---- gwcs/converters/wcs.py | 2 +- gwcs/coordinate_frames.py | 14 +++++------ gwcs/examples.py | 5 ++-- gwcs/extension.py | 20 +++++++-------- gwcs/geometry.py | 4 +-- gwcs/region.py | 1 + gwcs/selector.py | 4 +-- gwcs/spectroscopy.py | 3 +-- gwcs/tests/conftest.py | 3 +-- gwcs/tests/test_api.py | 12 ++++----- gwcs/tests/test_bounding_box.py | 4 +-- gwcs/tests/test_coordinate_systems.py | 15 ++++++------ gwcs/tests/test_extension.py | 4 +-- gwcs/tests/test_geometry.py | 6 ++--- gwcs/tests/test_region.py | 10 +++++--- gwcs/tests/test_spectroscopy_models.py | 7 +++--- gwcs/tests/test_utils.py | 12 ++++----- gwcs/tests/test_wcs.py | 31 +++++++++++------------- gwcs/tests/utils.py | 16 ++++++------ gwcs/utils.py | 12 ++++----- gwcs/wcs.py | 3 +-- gwcs/wcstools.py | 18 ++++++++------ pyproject.toml | 2 +- 31 files changed, 122 insertions(+), 137 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index 8ab25d49..58575e1b 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -1,12 +1,12 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst # -*- coding: utf-8 -*- -from collections import OrderedDict import io import json import os import sys import textwrap +from collections import OrderedDict import yaml diff --git a/gwcs/api.py b/gwcs/api.py index 7fbdb7ed..ad099e24 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -5,9 +5,9 @@ """ -from astropy.wcs.wcsapi import BaseLowLevelWCS, HighLevelWCSMixin -from astropy.modeling import separable import astropy.units as u +from astropy.modeling import separable +from astropy.wcs.wcsapi import BaseLowLevelWCS, HighLevelWCSMixin from gwcs import utils diff --git a/gwcs/converters/geometry.py b/gwcs/converters/geometry.py index 88f41b8e..3194af83 100644 --- a/gwcs/converters/geometry.py +++ b/gwcs/converters/geometry.py @@ -5,7 +5,6 @@ from asdf_astropy.converters.transform.core import TransformConverterBase - __all__ = ["DirectionCosinesConverter", "SphericalCartesianConverter"] @@ -17,7 +16,7 @@ class DirectionCosinesConverter(TransformConverterBase): ) def from_yaml_tree_transform(self, node, tag, ctx): - from ..geometry import ToDirectionCosines, FromDirectionCosines + from ..geometry import FromDirectionCosines, ToDirectionCosines transform_type = node["transform_type"] if transform_type == "to_direction_cosines": @@ -28,7 +27,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): raise TypeError(f"Unknown model_type {transform_type}") def to_yaml_tree_transform(self, model, tag, ctx): - from ..geometry import ToDirectionCosines, FromDirectionCosines + from ..geometry import FromDirectionCosines, ToDirectionCosines if isinstance(model, FromDirectionCosines): transform_type = "from_direction_cosines" @@ -48,7 +47,7 @@ class SphericalCartesianConverter(TransformConverterBase): ) def from_yaml_tree_transform(self, node, tag, ctx): - from ..geometry import SphericalToCartesian, CartesianToSpherical + from ..geometry import CartesianToSpherical, SphericalToCartesian transform_type = node["transform_type"] wrap_lon_at = node["wrap_lon_at"] @@ -60,7 +59,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): raise TypeError(f"Unknown model_type {transform_type}") def to_yaml_tree_transform(self, model, tag, ctx): - from ..geometry import SphericalToCartesian, CartesianToSpherical + from ..geometry import CartesianToSpherical, SphericalToCartesian if isinstance(model, SphericalToCartesian): transform_type = "spherical_to_cartesian" diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index 3dbb0fe8..e97b90e2 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -2,15 +2,14 @@ # -*- coding: utf-8 -*- from collections import OrderedDict + import numpy as np +from asdf.tags.core.ndarray import NDArrayType +from asdf_astropy.converters.transform.core import TransformConverterBase from astropy.modeling import models from astropy.modeling.core import Model from astropy.utils.misc import isiterable -from asdf.tags.core.ndarray import NDArrayType -from asdf_astropy.converters.transform.core import TransformConverterBase - - __all__ = ["LabelMapperConverter", "RegionsSelectorConverter"] @@ -25,10 +24,10 @@ class LabelMapperConverter(TransformConverterBase): def from_yaml_tree_transform(self, node, tag, ctx): from ..selector import ( + LabelMapper, LabelMapperArray, LabelMapperDict, LabelMapperRange, - LabelMapper, ) inputs_mapping = node.get("inputs_mapping", None) @@ -68,10 +67,10 @@ def from_yaml_tree_transform(self, node, tag, ctx): def to_yaml_tree_transform(self, model, tag, ctx): from ..selector import ( + LabelMapper, LabelMapperArray, LabelMapperDict, LabelMapperRange, - LabelMapper, ) node = OrderedDict() diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index cfd22d8c..e4838288 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -3,12 +3,11 @@ """ -from astropy import units as u from asdf_astropy.converters.transform.core import ( TransformConverterBase, parameter_to_value, ) - +from astropy import units as u __all__ = [ "GratingEquationConverter", diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index d6c0d607..76fed079 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -1,12 +1,12 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst # -*- coding: utf-8 -*- +import asdf import numpy as np +from astropy.modeling.models import Mapping, Polynomial2D, Scale, Shift from numpy.testing import assert_array_equal -from astropy.modeling.models import Mapping, Shift, Scale, Polynomial2D -import asdf -from ...tests.test_region import create_scalar_mapper from ... import selector +from ...tests.test_region import create_scalar_mapper def _assert_mapper_equal(a, b): diff --git a/gwcs/converters/tests/test_transforms.py b/gwcs/converters/tests/test_transforms.py index cefde098..3d671127 100644 --- a/gwcs/converters/tests/test_transforms.py +++ b/gwcs/converters/tests/test_transforms.py @@ -1,9 +1,8 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst # -*- coding: utf-8 -*- import pytest - -from astropy.modeling.models import Identity from astropy import units as u +from astropy.modeling.models import Identity try: from asdf_astropy.testing.helpers import assert_model_roundtrip @@ -12,9 +11,8 @@ assert_model_roundtrip, ) -from ... import spectroscopy as sp from ... import geometry - +from ... import spectroscopy as sp sell_glass = sp.SellmeierGlass( B_coef=[0.58339748, 0.46085267, 3.8915394], diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index 2308a08c..98af06f1 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -1,19 +1,19 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst # -*- coding: utf-8 -*- import os.path + import pytest astropy = pytest.importorskip("astropy", minversion="3.0") -from astropy.modeling import models # noqa: E402 -from astropy import coordinates as coord # noqa: E402 -from astropy import units as u # noqa: E402 -from astropy import time # noqa: E402 - import asdf # noqa: E402 from asdf_astropy.testing.helpers import ( # noqa: E402 assert_model_equal, ) +from astropy import coordinates as coord # noqa: E402 +from astropy import time # noqa: E402 +from astropy import units as u # noqa: E402 +from astropy.modeling import models # noqa: E402 from ... import coordinate_frames as cf # noqa: E402 from ... import wcs # noqa: E402 diff --git a/gwcs/converters/wcs.py b/gwcs/converters/wcs.py index 2da50db9..66814b47 100644 --- a/gwcs/converters/wcs.py +++ b/gwcs/converters/wcs.py @@ -3,8 +3,8 @@ import warnings from contextlib import suppress -from asdf.extension import Converter +from asdf.extension import Converter __all__ = [ "CelestialFrameConverter", diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 780fef22..17240933 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -118,24 +118,24 @@ """ import abc -from collections import defaultdict import logging import numbers -import numpy as np -from dataclasses import dataclass, InitVar +from collections import defaultdict +from dataclasses import InitVar, dataclass -from astropy.utils.misc import isiterable +import numpy as np +from astropy import coordinates as coord from astropy import time from astropy import units as u from astropy import utils as astutil -from astropy import coordinates as coord -from astropy.wcs.wcsapi.low_level_api import validate_physical_types, VALID_UCDS +from astropy.coordinates import StokesCoord +from astropy.utils.misc import isiterable from astropy.wcs.wcsapi.fitswcs import CTYPE_TO_UCD1 from astropy.wcs.wcsapi.high_level_api import ( high_level_objects_to_values, values_to_high_level_objects, ) -from astropy.coordinates import StokesCoord +from astropy.wcs.wcsapi.low_level_api import VALID_UCDS, validate_physical_types __all__ = [ "BaseCoordinateFrame", diff --git a/gwcs/examples.py b/gwcs/examples.py index 59cc59c8..ef9fbc61 100644 --- a/gwcs/examples.py +++ b/gwcs/examples.py @@ -1,9 +1,8 @@ -import numpy as np - import astropy.units as u -from astropy.time import Time +import numpy as np from astropy import coordinates as coord from astropy.modeling import models +from astropy.time import Time from . import coordinate_frames as cf from . import spectroscopy as sp diff --git a/gwcs/extension.py b/gwcs/extension.py index eda21ca2..813e30c6 100644 --- a/gwcs/extension.py +++ b/gwcs/extension.py @@ -2,26 +2,26 @@ import importlib.resources from asdf.extension import Extension, ManifestExtension + +from .converters.geometry import DirectionCosinesConverter, SphericalCartesianConverter +from .converters.selector import LabelMapperConverter, RegionsSelectorConverter +from .converters.spectroscopy import ( + GratingEquationConverter, + SellmeierGlassConverter, + SellmeierZemaxConverter, + Snell3DConverter, +) from .converters.wcs import ( CelestialFrameConverter, CompositeFrameConverter, - FrameConverter, Frame2DConverter, + FrameConverter, SpectralFrameConverter, StepConverter, StokesFrameConverter, TemporalFrameConverter, WCSConverter, ) -from .converters.selector import LabelMapperConverter, RegionsSelectorConverter -from .converters.spectroscopy import ( - GratingEquationConverter, - SellmeierGlassConverter, - SellmeierZemaxConverter, - Snell3DConverter, -) -from .converters.geometry import DirectionCosinesConverter, SphericalCartesianConverter - WCS_MODEL_CONVERTERS = [ CelestialFrameConverter(), diff --git a/gwcs/geometry.py b/gwcs/geometry.py index 8bf27fbd..1864325d 100644 --- a/gwcs/geometry.py +++ b/gwcs/geometry.py @@ -4,10 +4,10 @@ """ import numbers + import numpy as np -from astropy.modeling.core import Model from astropy import units as u - +from astropy.modeling.core import Model __all__ = [ "CartesianToSpherical", diff --git a/gwcs/region.py b/gwcs/region.py index 77210fba..1925f9c5 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -10,6 +10,7 @@ import abc from collections import OrderedDict + import numpy as np __all__ = ["Edge", "Polygon", "Region"] diff --git a/gwcs/selector.py b/gwcs/selector.py index 37f2bd55..bad78cb5 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -68,14 +68,14 @@ """ import warnings + import numpy as np -from astropy.modeling.core import Model from astropy.modeling import models as astmodels +from astropy.modeling.core import Model from . import region from .utils import RegionError, _toindex - __all__ = [ "LabelMapper", "LabelMapperArray", diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index df73f5bc..99a36eb1 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -3,11 +3,10 @@ Spectroscopy related models. """ +import astropy.units as u import numpy as np from astropy.modeling.core import Model from astropy.modeling.parameters import Parameter -import astropy.units as u - __all__ = [ "AnglesFromGratingEquation3D", diff --git a/gwcs/tests/conftest.py b/gwcs/tests/conftest.py index e21ebce6..03863d08 100644 --- a/gwcs/tests/conftest.py +++ b/gwcs/tests/conftest.py @@ -4,8 +4,7 @@ import pytest -from .. import examples -from .. import geometry +from .. import examples, geometry @pytest.fixture diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index cd0c2b33..8d1b4b9b 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -3,17 +3,17 @@ Tests the API defined in astropy APE 14 (https://doi.org/10.5281/zenodo.1188875). """ +import astropy.modeling.models as m +import astropy.units as u import numpy as np import pytest -from numpy.testing import assert_allclose, assert_array_equal - -import astropy.units as u -from astropy import time from astropy import coordinates as coord +from astropy import time from astropy.wcs.wcsapi import HighLevelWCSWrapper -import astropy.modeling.models as m -import gwcs.coordinate_frames as cf +from numpy.testing import assert_allclose, assert_array_equal + import gwcs +import gwcs.coordinate_frames as cf # Shorthand the name of the 2d gwcs fixture diff --git a/gwcs/tests/test_bounding_box.py b/gwcs/tests/test_bounding_box.py index 44ab0db0..c32d86c7 100644 --- a/gwcs/tests/test_bounding_box.py +++ b/gwcs/tests/test_bounding_box.py @@ -1,8 +1,6 @@ import numpy as np -from numpy.testing import assert_array_equal, assert_allclose - import pytest - +from numpy.testing import assert_allclose, assert_array_equal x = [-1, 2, 4, 13] y = [np.nan, np.nan, 4, np.nan] diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index f7e4f18c..6eb5c44d 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -1,22 +1,21 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import pytest import logging -import numpy as np -from numpy.testing import assert_allclose +import astropy import astropy.units as u -from astropy.time import Time, TimeDelta +import numpy as np +import pytest from astropy import coordinates as coord -from astropy.tests.helper import assert_quantity_allclose +from astropy.coordinates import SpectralCoord, StokesCoord from astropy.modeling import models as m +from astropy.tests.helper import assert_quantity_allclose +from astropy.time import Time, TimeDelta from astropy.wcs.wcsapi.fitswcs import CTYPE_TO_UCD1 -from astropy.coordinates import StokesCoord, SpectralCoord +from numpy.testing import assert_allclose from .. import WCS from .. import coordinate_frames as cf -import astropy - astropy_version = astropy.__version__ coord_frames = coord.builtin_frames.__all__[:] diff --git a/gwcs/tests/test_extension.py b/gwcs/tests/test_extension.py index b440ccc3..bee58430 100644 --- a/gwcs/tests/test_extension.py +++ b/gwcs/tests/test_extension.py @@ -3,10 +3,10 @@ import asdf import asdf_wcs_schemas -import gwcs.extension - import pytest +import gwcs.extension + @pytest.mark.skipif( asdf_wcs_schemas.__version__ < "0.2.0", diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index 16149ef9..814a7fa8 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -1,11 +1,10 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from itertools import product, permutations import io - -import pytest +from itertools import permutations, product import asdf import numpy as np +import pytest from astropy import units as u try: @@ -17,7 +16,6 @@ from .. import geometry - _INV_SQRT2 = 1.0 / np.sqrt(2.0) diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index e725d1f5..fcefc4e2 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -4,13 +4,15 @@ """ import warnings + import numpy as np -from numpy.testing import assert_equal, assert_allclose -from astropy.modeling import models import pytest -from gwcs import region, selector, WCS -from gwcs import utils as gwutils +from astropy.modeling import models +from numpy.testing import assert_allclose, assert_equal + +from gwcs import WCS, region, selector from gwcs import coordinate_frames as cf +from gwcs import utils as gwutils def test_LabelMapperArray_from_vertices_int(): diff --git a/gwcs/tests/test_spectroscopy_models.py b/gwcs/tests/test_spectroscopy_models.py index 0fbb91e6..daea4244 100644 --- a/gwcs/tests/test_spectroscopy_models.py +++ b/gwcs/tests/test_spectroscopy_models.py @@ -1,10 +1,11 @@ -import pytest import astropy.units as u -from astropy.modeling.models import Identity import numpy as np +import pytest +from astropy.modeling.models import Identity from numpy.testing import assert_allclose -from .. import spectroscopy as sp + from .. import geometry +from .. import spectroscopy as sp def test_angles_grating_equation(): diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index f8d7cf08..36c47767 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -1,22 +1,20 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst import os.path + import numpy as np -from astropy.io import fits -from astropy import wcs as fitswcs -from astropy import units as u +import pytest from astropy import coordinates as coord +from astropy import units as u +from astropy import wcs as fitswcs +from astropy.io import fits from astropy.modeling import models - from astropy.tests.helper import assert_quantity_allclose -import pytest from numpy.testing import assert_allclose from .. import utils as gwutils from ..utils import UnsupportedProjectionError - from . import data - data_path = os.path.split(os.path.abspath(data.__file__))[0] diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index c4f6fa03..9a78cc95 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -1,31 +1,28 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import warnings import os.path +import warnings -import pytest - +import asdf import numpy as np -from numpy.testing import assert_allclose, assert_equal - -from astropy.modeling import models, bind_compound_bounding_box -from astropy.modeling.bounding_box import ModelBoundingBox +import pytest from astropy import coordinates as coord -from astropy.io import fits from astropy import units as u from astropy import wcs as astwcs -from astropy.wcs import wcsapi +from astropy.io import fits +from astropy.modeling import bind_compound_bounding_box, models +from astropy.modeling.bounding_box import ModelBoundingBox from astropy.time import Time from astropy.utils.introspection import minversion -import asdf +from astropy.wcs import wcsapi +from numpy.testing import assert_allclose, assert_equal -from gwcs import wcs -from gwcs.wcstools import wcs_from_fiducial, grid_from_bounding_box, wcs_from_points from gwcs import coordinate_frames as cf -from gwcs.utils import CoordinateFrameError -from gwcs.tests.utils import _gwcs_from_hst_fits_wcs -from gwcs.tests import data +from gwcs import wcs from gwcs.examples import gwcs_2d_bad_bounding_box_order - +from gwcs.tests import data +from gwcs.tests.utils import _gwcs_from_hst_fits_wcs +from gwcs.utils import CoordinateFrameError +from gwcs.wcstools import grid_from_bounding_box, wcs_from_fiducial, wcs_from_points data_path = os.path.split(os.path.abspath(data.__file__))[0] @@ -881,7 +878,7 @@ def test_to_fits_sip_composite_frame_galactic(gwcs_3d_galactic_spectral): def test_to_fits_sip_composite_frame_keep_axis(gwcs_cube_with_separable_spectral): - from inspect import signature, Parameter + from inspect import Parameter, signature w, axes_order = gwcs_cube_with_separable_spectral _, _, celestial_group = w._separable_groups(detect_celestial=True) diff --git a/gwcs/tests/utils.py b/gwcs/tests/utils.py index f80ae7df..60878b28 100644 --- a/gwcs/tests/utils.py +++ b/gwcs/tests/utils.py @@ -1,19 +1,17 @@ import numpy as np - +from astropy import coordinates as coord +from astropy import units +from astropy import wcs as fits_wcs from astropy.modeling.models import ( - Shift, - Polynomial2D, + Mapping, Pix2Sky_TAN, + Polynomial2D, RotateNative2Celestial, - Mapping, + Shift, ) -from astropy import coordinates as coord -from astropy import units -from astropy import wcs as fits_wcs - -from ..wcs import WCS from .. import coordinate_frames as cf +from ..wcs import WCS def _gwcs_from_hst_fits_wcs(header, hdu=None): diff --git a/gwcs/utils.py b/gwcs/utils.py index f72abae0..62cf6d71 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -4,17 +4,17 @@ """ -import re import functools +import re + +import astropy.units as u import numpy as np -from astropy.modeling import models as astmodels -from astropy.modeling import core, projections -from astropy.io import fits from astropy import coordinates as coords -import astropy.units as u +from astropy.io import fits +from astropy.modeling import core, projections +from astropy.modeling import models as astmodels from astropy.wcs import Celprm - # these ctype values do not include yzLN and yzLT pairs sky_pairs = { "equatorial": ["RA", "DEC"], diff --git a/gwcs/wcs.py b/gwcs/wcs.py index be5f4161..4bf07e47 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -3,8 +3,8 @@ import itertools import warnings -import astropy.units as u import astropy.io.fits as fits +import astropy.units as u import numpy as np import numpy.linalg as npla from astropy import utils as astutil @@ -27,7 +27,6 @@ high_level_objects_to_values, values_to_high_level_objects, ) - from scipy import linalg, optimize from . import coordinate_frames as cf diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 3460ad88..32747604 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -1,18 +1,20 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst import functools import warnings + import numpy as np -from astropy.modeling.core import Model -from astropy.modeling import projections -from astropy.modeling import models, fitting -from astropy.modeling.bounding_box import CompoundBoundingBox, ModelBoundingBox from astropy import coordinates as coord from astropy import units as u +from astropy.modeling import fitting, models, projections +from astropy.modeling.bounding_box import CompoundBoundingBox, ModelBoundingBox +from astropy.modeling.core import Model -from .coordinate_frames import CelestialFrame, SpectralFrame, Frame2D, CompositeFrame -from .utils import UnsupportedTransformError, UnsupportedProjectionError -from .utils import _compute_lon_pole - +from .coordinate_frames import CelestialFrame, CompositeFrame, Frame2D, SpectralFrame +from .utils import ( + UnsupportedProjectionError, + UnsupportedTransformError, + _compute_lon_pole, +) __all__ = ["grid_from_bounding_box", "wcs_from_fiducial", "wcs_from_points"] diff --git a/pyproject.toml b/pyproject.toml index 7b41633e..661ff677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,7 +117,7 @@ exclude_lines = [ select = [ "F", # Pyflakes (part of default flake8) "W", "E", # pycodestyle (part of default flake8) - #"I", # isort (import sorting) + "I", # isort (import sorting) # "N", # pep8-naming #"D", # pydocstyle (docstring style guide) #"UP", # pyupgrade (upgrade code to modern python) From 477ce20d949abf08b4231a2eedbb35c356a5a354 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:10:42 -0500 Subject: [PATCH 08/31] Apply PyUgrade --- convert_schemas.py | 45 +++++++++--------- docs/conf.py | 3 +- gwcs/api.py | 4 +- gwcs/converters/__init__.py | 1 - gwcs/converters/selector.py | 5 +- gwcs/converters/spectroscopy.py | 4 +- gwcs/converters/tests/test_selector.py | 1 - gwcs/converters/tests/test_transforms.py | 1 - gwcs/converters/tests/test_wcs.py | 1 - gwcs/converters/wcs.py | 1 - gwcs/coordinate_frames.py | 29 ++++++------ gwcs/selector.py | 24 ++++------ gwcs/tests/test_api.py | 6 +-- gwcs/tests/test_wcs.py | 2 +- gwcs/utils.py | 34 +++++++------- gwcs/wcs.py | 58 +++++++++++------------- gwcs/wcstools.py | 15 ++---- pyproject.toml | 2 +- 18 files changed, 104 insertions(+), 132 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index 58575e1b..757e9971 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- import io import json @@ -31,7 +30,7 @@ def write_if_different(filename, data): original_data = None if original_data != data: - print("Converting schema {0}".format(os.path.basename(filename))) + print(f"Converting schema {os.path.basename(filename)}") with open(filename, "wb") as fd: fd.write(data) @@ -52,7 +51,7 @@ def write_header(o, content, level): """ levels = "=-~^." if level >= len(levels): - o.write("**{0}**\n\n".format(content)) + o.write(f"**{content}**\n\n") else: o.write(content) o.write("\n") @@ -95,35 +94,35 @@ def format_range( The formatted range expression """ if minimum is not None and maximum is not None: - part = "{0} ".format(minimum) + part = f"{minimum} " if exclusiveMinimum: part += "<" else: part += "≤" - part += " {0} ".format(var_middle) + part += f" {var_middle} " if exclusiveMaximum: part += "<" else: part += "≤" - part += " {0}".format(maximum) + part += f" {maximum}" elif minimum is not None: if var_end is not None: - part = "{0} ".format(var_end) + part = f"{var_end} " else: part = "" if exclusiveMinimum: - part += "> {0}".format(minimum) + part += f"> {minimum}" else: - part += "≥ {0}".format(minimum) + part += f"≥ {minimum}" elif maximum is not None: if var_end is not None: - part = "{0} ".format(var_end) + part = f"{var_end} " else: part = "" if exclusiveMaximum: - part += "< {0}".format(maximum) + part += f"< {maximum}" else: - part += "≤ {0}".format(maximum) + part += f"≤ {maximum}" else: return None return part @@ -149,13 +148,13 @@ def format_type(schema, root): elif "$ref" in schema: ref = schema["$ref"] if ref.startswith("#/"): - return ":ref:`{0} <{1}/{2}>`".format(ref[2:], root, ref[2:]) + return f":ref:`{ref[2:]} <{root}/{ref[2:]}>`" else: basename = os.path.basename(ref) if "tag:stsci.edu:asdf" in ref or "tag:astropy.org:astropy" in ref: - return "`{0} <{1}>`".format(basename, ref) + return f"`{basename} <{ref}>`" else: - return ":doc:`{0} <{1}>`".format(basename, ref) + return f":doc:`{basename} <{ref}>`" else: type = schema.get("type") @@ -184,9 +183,9 @@ def format_type(schema, root): if "pattern" in schema: pattern = schema["pattern"].encode("unicode_escape") pattern = pattern.decode("ascii") - parts.append(":soft:`regex` :regexp:`{0}`".format(pattern)) + parts.append(f":soft:`regex` :regexp:`{pattern}`") if "format" in schema: - parts.append(":soft:`format` {0}".format(schema["format"])) + parts.append(":soft:`format` {}".format(schema["format"])) parts.append(")") elif type in ("integer", "number"): @@ -281,13 +280,13 @@ def recurse(o, name, schema, path, level, required=False): indent = " " * max(level, 0) o.write("\n\n") o.write(indent) - o.write(".. _{0}:\n\n".format(os.path.join(*path))) + o.write(f".. _{os.path.join(*path)}:\n\n") if level == 0: write_header(o, name, level) else: if name != "items": o.write(indent) - o.write(":entry:`{0}`\n\n".format(name)) + o.write(f":entry:`{name}`\n\n") o.write(indent) if path[0].startswith("tag:stsci.edu:asdf"): @@ -308,7 +307,7 @@ def recurse(o, name, schema, path, level, required=False): if "default" in schema: o.write(indent) - o.write(":soft:`Default:` {0}".format(json.dumps(schema["default"]))) + o.write(":soft:`Default:` {}".format(json.dumps(schema["default"]))) o.write("\n\n") if "definitions" in schema: @@ -350,7 +349,7 @@ def recurse(o, name, schema, path, level, required=False): recurse(o, "items", items, [*path, "items"], level + 1) elif isinstance(items, list): for i, val in enumerate(items): - name = "index[{0}]".format(i) + name = f"index[{i}]" recurse(o, name, val, [*path, str(i)], level + 1) if "examples" in schema: @@ -395,7 +394,7 @@ def construct_mapping(self, node, deep=False): raise yaml.constructor.ConstructorError( None, None, - "expected a mapping node, but found %s" % node.id, + f"expected a mapping node, but found {node.id}", node.start_mark, ) mapping = OrderedDict() @@ -407,7 +406,7 @@ def construct_mapping(self, node, deep=False): raise yaml.constructor.ConstructorError( "while constructing a mapping", node.start_mark, - "found unacceptable key (%s)" % exc, + f"found unacceptable key ({exc})", key_node.start_mark, ) value = self.construct_object(value_node, deep=deep) diff --git a/docs/conf.py b/docs/conf.py index e5910027..72cd277c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed under a 3-clause BSD style license - see LICENSE.rst # # Astropy documentation build configuration file. @@ -131,7 +130,7 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = "{0} v{1}".format(project, release) +html_title = f"{project} v{release}" # Output file base name for HTML help builder. htmlhelp_basename = project + "doc" diff --git a/gwcs/api.py b/gwcs/api.py index ad099e24..3210fbda 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -223,8 +223,8 @@ def pixel_shape(self, value): if len(value) != wcs_naxes: raise ValueError( "The number of data axes, " - "{}, does not equal the " - "shape {}.".format(wcs_naxes, len(value)) + f"{wcs_naxes}, does not equal the " + f"shape {len(value)}." ) self._pixel_shape = tuple(value) diff --git a/gwcs/converters/__init__.py b/gwcs/converters/__init__.py index ccbc6cb9..9dce85d0 100644 --- a/gwcs/converters/__init__.py +++ b/gwcs/converters/__init__.py @@ -1,2 +1 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index e97b90e2..91f07e63 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- from collections import OrderedDict @@ -83,7 +82,7 @@ def to_yaml_tree_transform(self, model, tag, ctx): elif isinstance(model, LabelMapper): node["mapper"] = model.mapper node["inputs"] = list(model.inputs) - elif isinstance(model, (LabelMapperDict, LabelMapperRange)): + elif isinstance(model, (LabelMapperDict, LabelMapperRange)): # noqa: UP038 if hasattr(model, "atol"): node["atol"] = model.atol mapper = OrderedDict() @@ -99,7 +98,7 @@ def to_yaml_tree_transform(self, model, tag, ctx): node["mapper"] = mapper node["inputs"] = list(model.inputs) else: - raise TypeError("Unrecognized type of LabelMapper - {0}".format(model)) + raise TypeError(f"Unrecognized type of LabelMapper - {model}") return node diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index e4838288..1b67c5e9 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -105,7 +105,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): ) else: raise ValueError( - "Can't create a GratingEquation model with " "output {0}".format(output) + "Can't create a GratingEquation model with " f"output {output}" ) return model @@ -128,6 +128,6 @@ def to_yaml_tree_transform(self, model, tag, ctx): node["output"] = "wavelength" else: raise TypeError( - "Can't serialize an instance of {0}".format(model.__class__.__name__) + f"Can't serialize an instance of {model.__class__.__name__}" ) return node diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index 76fed079..f0883c0e 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- import asdf import numpy as np from astropy.modeling.models import Mapping, Polynomial2D, Scale, Shift diff --git a/gwcs/converters/tests/test_transforms.py b/gwcs/converters/tests/test_transforms.py index 3d671127..3fb49832 100644 --- a/gwcs/converters/tests/test_transforms.py +++ b/gwcs/converters/tests/test_transforms.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- import pytest from astropy import units as u from astropy.modeling.models import Identity diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index 98af06f1..2fda38bd 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- import os.path import pytest diff --git a/gwcs/converters/wcs.py b/gwcs/converters/wcs.py index 66814b47..7ce69cd4 100644 --- a/gwcs/converters/wcs.py +++ b/gwcs/converters/wcs.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -# -*- coding: utf-8 -*- import warnings from contextlib import suppress diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 17240933..c2a0b7a5 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -269,7 +269,7 @@ def _default_axis_physical_types(self): The default physical types to use for this frame if none are specified by the user. """ - return tuple("custom:{}".format(t) for t in self.axes_type) + return tuple(f"custom:{t}" for t in self.axes_type) class BaseCoordinateFrame(abc.ABC): @@ -453,18 +453,15 @@ def _default_axis_physical_types(self, axes_type): The default physical types to use for this frame if none are specified by the user. """ - return tuple("custom:{}".format(t) for t in axes_type) + return tuple(f"custom:{t}" for t in axes_type) def __repr__(self): - fmt = '<{0}(name="{1}", unit={2}, axes_names={3}, axes_order={4}'.format( - self.__class__.__name__, - self.name, - self.unit, - self.axes_names, - self.axes_order, + fmt = ( + f'<{self.__class__.__name__}(name="{self.name}", unit={self.unit}, ' + f"axes_names={self.axes_names}, axes_order={self.axes_order}" ) if self.reference_frame is not None: - fmt += ", reference_frame={0}".format(self.reference_frame) + fmt += f", reference_frame={self.reference_frame}" fmt += ")>" return fmt @@ -681,7 +678,7 @@ def _default_axis_physical_types(self, reference_frame, axes_names): return "pos.galactic.lon", "pos.galactic.lat" elif isinstance( reference_frame, - (coord.GeocentricTrueEcliptic, coord.GCRS, coord.PrecessedGeocentric), + coord.GeocentricTrueEcliptic | coord.GCRS | coord.PrecessedGeocentric, ): return "pos.bodyrc.lon", "pos.bodyrc.lat" elif isinstance(reference_frame, coord.builtin_frames.BaseRADecFrame): @@ -689,7 +686,7 @@ def _default_axis_physical_types(self, reference_frame, axes_names): elif isinstance(reference_frame, coord.builtin_frames.BaseEclipticFrame): return "pos.ecliptic.lon", "pos.ecliptic.lat" else: - return tuple("custom:{}".format(t) for t in axes_names) + return tuple(f"custom:{t}" for t in axes_names) @property def world_axis_object_classes(self): @@ -770,7 +767,7 @@ def _default_axis_physical_types(self, unit): "'spect.dopplerVeloc.radio'." ) else: - return ("custom:{}".format(unit[0].physical_type),) + return (f"custom:{unit[0].physical_type}",) @property def world_axis_object_classes(self): @@ -811,8 +808,10 @@ def __init__( name=None, axis_physical_types=None, ): - axes_names = axes_names or "{}({}; {}".format( - reference_frame.format, reference_frame.scale, reference_frame.location + axes_names = ( + axes_names + or f"{reference_frame.format}({reference_frame.scale}; " + f"{reference_frame.location}" ) pht = axis_physical_types or self._default_axis_physical_types() @@ -1081,4 +1080,4 @@ def _default_axis_physical_types(self, axes_names, axes_type): else: ph_type = axes_type - return tuple("custom:{}".format(t) for t in ph_type) + return tuple(f"custom:{t}" for t in ph_type) diff --git a/gwcs/selector.py b/gwcs/selector.py index bad78cb5..6dd4df9a 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -110,7 +110,7 @@ def get_unique_regions(regions): class LabelMapperArrayIndexingError(Exception): def __init__(self, message): - super(LabelMapperArrayIndexingError, self).__init__(message) + super().__init__(message) class _LabelMapper(Model): @@ -139,7 +139,7 @@ def __init__(self, mapper, no_label, inputs_mapping=None, name=None, **kwargs): self._no_label = no_label self._inputs_mapping = inputs_mapping self._mapper = mapper - super(_LabelMapper, self).__init__(name=name, **kwargs) + super().__init__(name=name, **kwargs) @property def mapper(self): @@ -193,7 +193,7 @@ def __init__(self, mapper, inputs_mapping=None, name=None, **kwargs): _no_label = 0 else: _no_label = "" - super(LabelMapperArray, self).__init__(mapper, _no_label, name=name, **kwargs) + super().__init__(mapper, _no_label, name=name, **kwargs) self.inputs = ("x", "y") self.outputs = ("label",) @@ -294,9 +294,7 @@ def __init__( raise TypeError("All transforms in mapper must have one output.") self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} - super(LabelMapperDict, self).__init__( - mapper, _no_label, inputs_mapping, name=name, **kwargs - ) + super().__init__(mapper, _no_label, inputs_mapping, name=name, **kwargs) self.outputs = ("labels",) @property @@ -394,9 +392,7 @@ def __init__(self, inputs, mapper, inputs_mapping=None, name=None, **kwargs): raise TypeError("All transforms in mapper must have one output.") self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} - super(LabelMapperRange, self).__init__( - mapper, _no_label, inputs_mapping, name=name, **kwargs - ) + super().__init__(mapper, _no_label, inputs_mapping, name=name, **kwargs) self.outputs = ("labels",) @property @@ -494,9 +490,7 @@ def evaluate(self, *args): continue res.shape = shape if len(np.nonzero(res)[0]) == 0: - warnings.warn( - "All data is outside the valid range - {0}.".format(self.name) - ) + warnings.warn(f"All data is outside the valid range - {self.name}.") return res @@ -577,7 +571,7 @@ def set_input(self, rid): if rid in self._selector: return self._selector[rid] else: - raise RegionError("Region {0} not found".format(rid)) + raise RegionError(f"Region {rid} not found") def inverse(self): if self.label_mapper.inverse is not None: @@ -716,9 +710,7 @@ def __init__( self._no_label = no_label self._inputs = inputs self._n_inputs = len(inputs) - self._outputs = tuple( - ["x{0}".format(ind) for ind in list(range(mapper.n_outputs))] - ) + self._outputs = tuple([f"x{ind}" for ind in list(range(mapper.n_outputs))]) if isinstance(inputs_mapping, tuple): inputs_mapping = astmodels.Mapping(inputs_mapping) elif inputs_mapping is not None and not isinstance( diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index 8d1b4b9b..d639d4ba 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -312,20 +312,20 @@ def test_high_level_wrapper(wcsobj, request): assert type(wc1) is type(wc2) - if isinstance(wc1, (list, tuple)): + if isinstance(wc1, list | tuple): for w1, w2 in zip(wc1, wc2): _compare_frame_output(w1, w2) else: _compare_frame_output(wc1, wc2) # we have just asserted that wc1 and wc2 are equal - if not isinstance(wc1, (list, tuple)): + if not isinstance(wc1, list | tuple): wc1 = (wc1,) pix_out1 = hlvl.world_to_pixel(*wc1) pix_out2 = wcsobj.invert(*wc1) - if not isinstance(pix_out2, (list, tuple)): + if not isinstance(pix_out2, list | tuple): pix_out2 = (pix_out2,) if wcsobj.forward_transform.uses_quantity: diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 9a78cc95..f26f1e0c 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -634,7 +634,7 @@ def test_high_level_api(): assert_allclose(k1, xv) -class TestImaging(object): +class TestImaging: def setup_class(self): hdr = fits.Header.fromtextfile( os.path.join(data_path, "acs.hdr"), endcard=False diff --git a/gwcs/utils.py b/gwcs/utils.py index 62cf6d71..0a1765d0 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -30,23 +30,23 @@ class UnsupportedTransformError(Exception): def __init__(self, message): - super(UnsupportedTransformError, self).__init__(message) + super().__init__(message) class UnsupportedProjectionError(Exception): def __init__(self, code): - message = "Unsupported projection: {0}".format(code) - super(UnsupportedProjectionError, self).__init__(message) + message = f"Unsupported projection: {code}" + super().__init__(message) class RegionError(Exception): def __init__(self, message): - super(RegionError, self).__init__(message) + super().__init__(message) class CoordinateFrameError(Exception): def __init__(self, message): - super(CoordinateFrameError, self).__init__(message) + super().__init__(message) def _toindex(value): @@ -157,9 +157,7 @@ def get_projcode(wcs_info): return None projcode = wcs_info["CTYPE"][sky_axes[0]][5:8].upper() if projcode not in projections.projcodes: - raise UnsupportedProjectionError( - "Projection code %s, not recognized" % projcode - ) + raise UnsupportedProjectionError(f"Projection code {projcode}, not recognized") return projcode @@ -206,11 +204,11 @@ def read_wcs_from_header(header): crval = [] cdelt = [] for i in range(1, wcsaxes + 1): - ctype.append(header["CTYPE{0}".format(i)]) - cunit.append(header.get("CUNIT{0}".format(i), None)) - crpix.append(header.get("CRPIX{0}".format(i), 0.0)) - crval.append(header.get("CRVAL{0}".format(i), 0.0)) - cdelt.append(header.get("CDELT{0}".format(i), 1.0)) + ctype.append(header[f"CTYPE{i}"]) + cunit.append(header.get(f"CUNIT{i}", None)) + crpix.append(header.get(f"CRPIX{i}", 0.0)) + crval.append(header.get(f"CRVAL{i}", 0.0)) + cdelt.append(header.get(f"CDELT{i}", 1.0)) if "CD1_1" in header: wcs_info["has_cd"] = True @@ -221,9 +219,9 @@ def read_wcs_from_header(header): for j in range(1, wcsaxes + 1): try: if wcs_info["has_cd"]: - pc[i - 1, j - 1] = header["CD{0}_{1}".format(i, j)] + pc[i - 1, j - 1] = header[f"CD{i}_{j}"] else: - pc[i - 1, j - 1] = header["PC{0}_{1}".format(i, j)] + pc[i - 1, j - 1] = header[f"PC{i}_{j}"] except KeyError: if i == j: pc[i - 1, j - 1] = 1.0 @@ -289,13 +287,13 @@ def _is_skysys_consistent(ctype, sky_inmap): if ctype[sky_inmap[0]] == item[0]: if ctype[sky_inmap[1]] != item[1]: raise ValueError( - "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype) + "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) ) break elif ctype[sky_inmap[1]] == item[0]: if ctype[sky_inmap[0]] != item[1]: raise ValueError( - "Inconsistent ctype for sky coordinates {0} and {1}".format(*ctype) + "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) ) sky_inmap = sky_inmap[::-1] break @@ -446,7 +444,7 @@ def fitswcs_nonlinear(header): # Create the sky rotation transform sky_axes, _, _ = get_axes(wcs_info) if sky_axes: - phip, lonp = [wcs_info["CRVAL"][i] for i in sky_axes] + phip, lonp = (wcs_info["CRVAL"][i] for i in sky_axes) # TODO: write "def compute_lonpole(projcode, l)" # Set a default tvalue for now thetap = 180 diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 4bf07e47..a4da6378 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -35,7 +35,7 @@ from .utils import CoordinateFrameError from .wcstools import grid_from_bounding_box -__all__ = ["WCS", "Step", "NoConvergence"] +__all__ = ["WCS", "NoConvergence", "Step"] _ITER_INV_KWARGS = ["tolerance", "maxiter", "adaptive", "detect_divergence", "quiet"] @@ -183,8 +183,8 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) - super(WCS, self).__setattr__(_input_frame, inp_frame_obj) - super(WCS, self).__setattr__(_output_frame, outp_frame_obj) + super().__setattr__(_input_frame, inp_frame_obj) + super().__setattr__(_output_frame, outp_frame_obj) self._pipeline = [ (input_frame, forward_transform.copy()), @@ -196,12 +196,12 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): name, frame_obj = self._get_frame_name(item.frame) else: name, frame_obj = self._get_frame_name(item[0]) - super(WCS, self).__setattr__(name, frame_obj) + super().__setattr__(name, frame_obj) self._pipeline = forward_transform else: raise TypeError( "Expected forward_transform to be a model or a " - "(frame, transform) list, got {0}".format(type(forward_transform)) + f"(frame, transform) list, got {type(forward_transform)}" ) else: # Initialize a WCS without a forward_transform - allows building a @@ -212,8 +212,8 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): ) _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) - super(WCS, self).__setattr__(_input_frame, inp_frame_obj) - super(WCS, self).__setattr__(_output_frame, outp_frame_obj) + super().__setattr__(_input_frame, inp_frame_obj) + super().__setattr__(_output_frame, outp_frame_obj) self._pipeline = [(_input_frame, None), (_output_frame, None)] def get_transform(self, from_frame, to_frame): @@ -264,29 +264,27 @@ def set_transform(self, from_frame, to_frame, transform): if not self._pipeline: if from_name != self._input_frame: raise CoordinateFrameError( - "Expected 'from_frame' to be {0}".format(self._input_frame) + f"Expected 'from_frame' to be {self._input_frame}" ) if to_frame != self._output_frame: raise CoordinateFrameError( - "Expected 'to_frame' to be {0}".format(self._output_frame) + f"Expected 'to_frame' to be {self._output_frame}" ) try: from_ind = self._get_frame_index(from_name) except ValueError: raise CoordinateFrameError( - "Frame {0} is not in the available frames".format(from_name) + f"Frame {from_name} is not in the available frames" ) try: to_ind = self._get_frame_index(to_name) except ValueError: raise CoordinateFrameError( - "Frame {0} is not in the available frames".format(to_name) + f"Frame {to_name} is not in the available frames" ) if from_ind + 1 != to_ind: - raise ValueError( - "Frames {0} and {1} are not in sequence".format(from_name, to_name) - ) + raise ValueError(f"Frames {from_name} and {to_name} are not in sequence") self._pipeline[from_ind].transform = transform @property @@ -325,7 +323,7 @@ def backward_transform(self): backward = self.forward_transform.inverse except NotImplementedError as err: raise NotImplementedError( - "Could not construct backward transform. \n{0}".format(err) + f"Could not construct backward transform. \n{err}" ) try: backward.inverse @@ -465,7 +463,7 @@ def _call_forward( raise NotImplementedError("WCS.forward_transform is not implemented.") # Validate that the input type matches what the transform expects - input_is_quantity = any((isinstance(a, u.Quantity) for a in args)) + input_is_quantity = any(isinstance(a, u.Quantity) for a in args) if not input_is_quantity and transform.uses_quantity: args = self._add_units_input(args, from_frame) if not transform.uses_quantity and input_is_quantity: @@ -587,7 +585,7 @@ def _call_backward( if transform is not None: # Validate that the input type matches what the transform expects - input_is_quantity = any((isinstance(a, u.Quantity) for a in args)) + input_is_quantity = any(isinstance(a, u.Quantity) for a in args) if not input_is_quantity and transform.uses_quantity: args = self._add_units_input(args, self.output_frame) if not transform.uses_quantity and input_is_quantity: @@ -630,7 +628,7 @@ def outside_footprint(self, world_arrays): *world_arrays, low_level_wcs=self ) for axtyp in axes_types: - ind = np.asarray((np.asarray(self.output_frame.axes_type) == axtyp)) + ind = np.asarray(np.asarray(self.output_frame.axes_type) == axtyp) for idim, (coord, phys) in enumerate(zip(world_arrays, axes_phys_types)): coord = _tofloat(coord) @@ -1268,8 +1266,8 @@ def correction(pix): if inddiv is None: raise NoConvergence( "'WCS.numerical_inverse' failed to " - "converge to the requested accuracy after {:d} " - "iterations.".format(k), + f"converge to the requested accuracy after {k:d} " + "iterations.", best_solution=pix, accuracy=np.abs(dpix), niter=k, @@ -1280,8 +1278,8 @@ def correction(pix): raise NoConvergence( "'WCS.numerical_inverse' failed to " "converge to the requested accuracy.\n" - "After {:d} iterations, the solution is diverging " - "at least for one input point.".format(k), + f"After {k:d} iterations, the solution is diverging " + "at least for one input point.", best_solution=pix, accuracy=np.abs(dpix), niter=k, @@ -1445,7 +1443,7 @@ def insert_frame(self, input_frame, transform, output_frame): + [Step(input_frame_obj, transform)] + self._pipeline[output_index:] ) - super(WCS, self).__setattr__(input_name, input_frame_obj) + super().__setattr__(input_name, input_frame_obj) else: split_step = self._pipeline[input_index] self._pipeline = ( @@ -1456,7 +1454,7 @@ def insert_frame(self, input_frame, transform, output_frame): ] + self._pipeline[input_index + 1 :] ) - super(WCS, self).__setattr__(output_name, output_frame_obj) + super().__setattr__(output_name, output_frame_obj) @property def unit(self): @@ -1613,10 +1611,10 @@ def __str__(self): return str(t) def __repr__(self): - fmt = "".format( - self.output_frame, self.input_frame, self.forward_transform + return ( + f"" ) - return fmt def footprint(self, bounding_box=None, center=False, axis_type="all"): """ @@ -1684,9 +1682,7 @@ def _order_clockwise(v): np.array([t.lower() for t in self.output_frame.axes_type]) == axis_type ) if not axtyp_ind.any(): - raise ValueError( - 'This WCS does not have axis of type "{}".'.format(axis_type) - ) + raise ValueError(f'This WCS does not have axis of type "{axis_type}".') if len(axtyp_ind) > 1: result = np.asarray([(r.min(), r.max()) for r in result[axtyp_ind]]) @@ -3433,7 +3429,7 @@ def frame(self): @frame.setter def frame(self, val): - if not isinstance(val, (cf.CoordinateFrame, str)): + if not isinstance(val, cf.CoordinateFrame | str): raise TypeError( '"frame" should be an instance of CoordinateFrame or a string.' ) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 32747604..483f3727 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -87,7 +87,7 @@ def wcs_from_fiducial( fiducial[ind], projection=projection ) except KeyError: - raise TypeError("Coordinate frame {0} is not supported".format(item)) + raise TypeError(f"Coordinate frame {item} is not supported") trans_from_fiducial.append(model) fiducial_transform = functools.reduce( lambda x, y: x & y, [tr for tr in trans_from_fiducial] @@ -99,9 +99,7 @@ def wcs_from_fiducial( fiducial, projection=projection ) except KeyError: - raise TypeError( - "Coordinate frame {0} is not supported".format(coordinate_frame) - ) + raise TypeError(f"Coordinate frame {coordinate_frame} is not supported") if transform is not None: forward_transform = transform | fiducial_transform @@ -361,15 +359,12 @@ def wcs_from_points( ) if not isinstance(projection, projections.Projection): - raise UnsupportedProjectionError( - "Unsupported projection code {0}".format(projection) - ) + raise UnsupportedProjectionError(f"Unsupported projection code {projection}") if polynomial_type not in supported_poly_types.keys(): raise ValueError( - "Unsupported polynomial_type: {}. " "Only one of {} is supported.".format( - polynomial_type, supported_poly_types.keys() - ) + f"Unsupported polynomial_type: {polynomial_type}. " + f"Only one of {supported_poly_types.keys()} is supported." ) skyrot = models.RotateCelestial2Native( diff --git a/pyproject.toml b/pyproject.toml index 661ff677..2f5076ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ select = [ "I", # isort (import sorting) # "N", # pep8-naming #"D", # pydocstyle (docstring style guide) - #"UP", # pyupgrade (upgrade code to modern python) + "UP", # pyupgrade (upgrade code to modern python) # "YTT", # flake8-2020 (system version info) # "ANN", # flake8-annotations (best practices for type annotations) #"S", # flake8-bandit (security checks) From 5c83eb28b9139b27fac9e23f7b8446d207d4775a Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:24:53 -0500 Subject: [PATCH 09/31] Add bugbear changes --- convert_schemas.py | 4 +- gwcs/api.py | 2 +- gwcs/converters/selector.py | 6 +- gwcs/converters/tests/test_selector.py | 4 +- gwcs/converters/tests/test_wcs.py | 2 +- gwcs/coordinate_frames.py | 12 ++-- gwcs/region.py | 2 +- gwcs/selector.py | 16 ++++-- gwcs/tests/test_api.py | 17 +++--- gwcs/tests/test_coordinate_systems.py | 10 ++-- gwcs/tests/test_region.py | 6 +- gwcs/tests/test_wcs.py | 18 +++--- gwcs/utils.py | 12 ++-- gwcs/wcs.py | 77 +++++++++++++++----------- gwcs/wcstools.py | 16 ++++-- pyproject.toml | 15 ++--- 16 files changed, 123 insertions(+), 96 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index 757e9971..f5fd58b9 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -408,7 +408,7 @@ def construct_mapping(self, node, deep=False): node.start_mark, f"found unacceptable key ({exc})", key_node.start_mark, - ) + ) from exc value = self.construct_object(value_node, deep=deep) mapping[key] = value return mapping @@ -418,7 +418,7 @@ def construct_mapping(self, node, deep=False): def main(src, dst): - for root, dirs, files in os.walk(src): + for root, _, files in os.walk(src): for fname in files: if not fname.endswith(".yaml"): continue diff --git a/gwcs/api.py b/gwcs/api.py index 3210fbda..ddb649d6 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -72,7 +72,7 @@ def _remove_quantity_output(self, result, frame): result = tuple( r.to_value(unit) if isinstance(r, u.Quantity) else r - for r, unit in zip(result, frame.unit) + for r, unit in zip(result, frame.unit, strict=False) ) # If we only have one output axes, we shouldn't return a tuple. diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index 91f07e63..da24921f 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -58,10 +58,10 @@ def from_yaml_tree_transform(self, node, tag, ctx): transforms = mapper.get("models") if isiterable(labels[0]): labels = [tuple(label) for label in labels] - dict_mapper = dict(zip(labels, transforms)) + dict_mapper = dict(zip(labels, transforms, strict=False)) return LabelMapperRange(inputs, dict_mapper, inputs_mapping) else: - dict_mapper = dict(zip(labels, transforms)) + dict_mapper = dict(zip(labels, transforms, strict=False)) return LabelMapperDict(inputs, dict_mapper, inputs_mapping, atol=atol) def to_yaml_tree_transform(self, model, tag, ctx): @@ -115,7 +115,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): label_mapper = node["label_mapper"] undefined_transform_value = node["undefined_transform_value"] sel = node["selector"] - sel = dict(zip(sel["labels"], sel["transforms"])) + sel = dict(zip(sel["labels"], sel["transforms"], strict=False)) return RegionsSelector( inputs, outputs, sel, label_mapper, undefined_transform_value ) diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index f0883c0e..330ebdb1 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -62,7 +62,7 @@ def assert_selector_roundtrip(s, tmpdir, version=None): elif isinstance(s, selector._LabelMapper): _assert_mapper_equal(s, rs) else: - assert False + raise AssertionError("Unknown selector type") def test_regions_selector(tmpdir): @@ -120,7 +120,7 @@ def test_LabelMapperRange(tmpdir): ] ) rmapper = {} - for k, v in zip(keys, m): + for k, v in zip(keys, m, strict=False): rmapper[tuple(k)] = v sel = selector.LabelMapperRange( ("x", "y"), rmapper, inputs_mapping=Mapping((0,), n_inputs=2) diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index 2fda38bd..3018de47 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -54,7 +54,7 @@ def _assert_wcs_equal(a, b): assert a.name == b.name # nosec assert a.pixel_shape == b.pixel_shape assert len(a.available_frames) == len(b.available_frames) # nosec - for a_step, b_step in zip(a.pipeline, b.pipeline): + for a_step, b_step in zip(a.pipeline, b.pipeline, strict=False): _assert_frame_equal(a_step.frame, b_step.frame) assert_model_equal(a_step.transform, b_step.transform) diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index c2a0b7a5..e44647d8 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -471,7 +471,9 @@ def __str__(self): return self.__class__.__name__ def _sort_property(self, property): - sorted_prop = sorted(zip(property, self.axes_order), key=lambda x: x[1]) + sorted_prop = sorted( + zip(property, self.axes_order, strict=False), key=lambda x: x[1] + ) return tuple([t[0] for t in sorted_prop]) @property @@ -527,7 +529,7 @@ def axis_physical_types(self): def world_axis_object_classes(self): return { f"{at}{i}" if i != 0 else at: (u.Quantity, (), {"unit": unit}) - for i, (at, unit) in enumerate(zip(self.axes_type, self.unit)) + for i, (at, unit) in enumerate(zip(self.axes_type, self.unit, strict=False)) } @property @@ -568,7 +570,7 @@ def to_high_level_coordinates(self, *values): # does not. values = [ v.to_value(unit) if hasattr(v, "to_value") else v - for v, unit in zip(values, self.unit) + for v, unit in zip(values, self.unit, strict=False) ] if not all( @@ -1057,9 +1059,11 @@ def __init__( unit=(u.pix, u.pix), axes_names=("x", "y"), name=None, - axes_type=["SPATIAL", "SPATIAL"], + axes_type=None, axis_physical_types=None, ): + if axes_type is None: + axes_type = ["SPATIAL", "SPATIAL"] pht = axis_physical_types or self._default_axis_physical_types( axes_names, axes_type ) diff --git a/gwcs/region.py b/gwcs/region.py index 1925f9c5..474248e9 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -236,7 +236,7 @@ def scan(self, data): y += 1 continue - for i, j in zip(xnew[::2], xnew[1::2]): + for i, j in zip(xnew[::2], xnew[1::2], strict=False): xstart = max(0, i + self._shiftx) xend = min(j + self._shiftx, nx - 1) data[ysh][xstart : xend + 1] = self._rid diff --git a/gwcs/selector.py b/gwcs/selector.py index 6dd4df9a..51ad1a35 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -202,7 +202,7 @@ def evaluate(self, *args): try: result = self._mapper[args[::-1]] except IndexError as e: - raise LabelMapperArrayIndexingError(e) + raise LabelMapperArrayIndexingError(e) from e return result @classmethod @@ -490,7 +490,9 @@ def evaluate(self, *args): continue res.shape = shape if len(np.nonzero(res)[0]) == 0: - warnings.warn(f"All data is outside the valid range - {self.name}.") + warnings.warn( + f"All data is outside the valid range - {self.name}.", stacklevel=2 + ) return res @@ -548,7 +550,7 @@ def __init__( self._input_units_allow_dimensionless = {key: False for key in self._inputs} super().__init__(n_models=1, name=name, **kwargs) # Validate uses_quantity at init time for nicer error message - self.uses_quantity + _ = self.uses_quantity @property def uses_quantity(self): @@ -579,11 +581,11 @@ def inverse(self): transforms_inv = {} for rid in self._selector: transforms_inv[rid] = self._selector[rid].inverse - except AttributeError: + except AttributeError as err: raise NotImplementedError( "The inverse of all regions must be defined" "for RegionsSelector to have an inverse." - ) + ) from err return self.__class__( self.outputs, self.inputs, transforms_inv, self.label_mapper.inverse ) @@ -605,7 +607,9 @@ def evaluate(self, *args): rids = self.label_mapper(*args).flatten() # Raise an error if all pixels are outside regions if (rids == self.label_mapper.no_label).all(): - warnings.warn("The input positions are not inside any region.") + warnings.warn( + "The input positions are not inside any region.", stacklevel=2 + ) # Create output arrays and set any pixels not within regions to # "undefined_transform_value" diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index d639d4ba..c5ae088a 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -133,13 +133,13 @@ def test_world_axis_units(wcs_ndim_types_units): assert wcsobj.world_axis_units == world_units -@pytest.mark.parametrize(("x", "y"), zip((x, xarr), (y, yarr))) +@pytest.mark.parametrize(("x", "y"), zip((x, xarr), (y, yarr), strict=False)) def test_pixel_to_world_values(gwcs_2d_spatial_shift, x, y): wcsobj = gwcs_2d_spatial_shift assert_allclose(wcsobj.pixel_to_world_values(x, y), wcsobj(x, y)) -@pytest.mark.parametrize(("x", "y"), zip((x, xarr), (y, yarr))) +@pytest.mark.parametrize(("x", "y"), zip((x, xarr), (y, yarr), strict=False)) def test_pixel_to_world_values_units_2d(gwcs_2d_shift_scale_quantity, x, y): wcsobj = gwcs_2d_shift_scale_quantity @@ -157,10 +157,10 @@ def test_pixel_to_world_values_units_2d(gwcs_2d_shift_scale_quantity, x, y): assert_allclose(u.Quantity(call_world).value, api_world) new_call_pixel = wcsobj.invert(*call_world) - [assert_allclose(n, p) for n, p in zip(new_call_pixel, call_pixel)] + [assert_allclose(n, p) for n, p in zip(new_call_pixel, call_pixel, strict=False)] new_api_pixel = wcsobj.world_to_pixel_values(*api_world) - [assert_allclose(n, p) for n, p in zip(new_api_pixel, api_pixel)] + [assert_allclose(n, p) for n, p in zip(new_api_pixel, api_pixel, strict=False)] @pytest.mark.parametrize(("x"), (x, xarr)) @@ -187,7 +187,7 @@ def test_pixel_to_world_values_units_1d(gwcs_1d_freq_quantity, x): assert_allclose(new_api_pixel, api_pixel) -@pytest.mark.parametrize(("x", "y"), zip((x, xarr), (y, yarr))) +@pytest.mark.parametrize(("x", "y"), zip((x, xarr), (y, yarr), strict=False)) def test_array_index_to_world_values(gwcs_2d_spatial_shift, x, y): wcsobj = gwcs_2d_spatial_shift assert_allclose(wcsobj.array_index_to_world_values(x, y), wcsobj(y, x)) @@ -292,7 +292,7 @@ def _compare_frame_output(wc1, wc2): assert wc1 == wc2 else: - assert False, f"Can't Compare {type(wc1)}" + raise AssertionError(f"Can't Compare {type(wc1)}") @fixture_all_wcses @@ -313,7 +313,7 @@ def test_high_level_wrapper(wcsobj, request): assert type(wc1) is type(wc2) if isinstance(wc1, list | tuple): - for w1, w2 in zip(wc1, wc2): + for w1, w2 in zip(wc1, wc2, strict=False): _compare_frame_output(w1, w2) else: _compare_frame_output(wc1, wc2) @@ -330,7 +330,8 @@ def test_high_level_wrapper(wcsobj, request): if wcsobj.forward_transform.uses_quantity: pix_out2 = tuple( - p.to_value(unit) for p, unit in zip(pix_out2, wcsobj.input_frame.unit) + p.to_value(unit) + for p, unit in zip(pix_out2, wcsobj.input_frame.unit, strict=False) ) np.testing.assert_allclose(pix_out1, pixel_input) diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index 6eb5c44d..a2612198 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -105,7 +105,7 @@ def coordinate_to_quantity(*inputs, frame): results = frame.from_high_level_coordinates(*inputs) if not isinstance(results, list): results = [results] - results = [r << unit for r, unit in zip(results, frame.unit)] + results = [r << unit for r, unit in zip(results, frame.unit, strict=False)] return results @@ -342,7 +342,7 @@ def test_coordinate_to_quantity_composite(inp): coords = coordinate_to_quantity(*inp, frame=comp) expected = (211 * u.AA, 0 * u.s, 0 * u.arcsec, 0 * u.arcsec) - for output, exp in zip(coords, expected): + for output, exp in zip(coords, expected, strict=False): assert_quantity_allclose(output, exp) @@ -365,7 +365,7 @@ def test_coordinate_to_quantity_composite_split(): coords = coordinate_to_quantity(*inp, frame=comp) expected = (0 * u.arcsec, 211 * u.AA, 0 * u.arcsec, 0 * u.s) - for output, exp in zip(coords, expected): + for output, exp in zip(coords, expected, strict=False): assert_quantity_allclose(output, exp) @@ -392,7 +392,7 @@ def test_coordinate_to_quantity_frame2d_composite(): coords = coordinate_to_quantity(*inp, frame=comp) expected = (211 * u.AA, 0 * u.s, 0 * u.one, 0 * u.one) - for output, exp in zip(coords, expected): + for output, exp in zip(coords, expected, strict=False): assert_quantity_allclose(output, exp) @@ -401,7 +401,7 @@ def test_coordinate_to_quantity_frame_2d(): inp = (1 * u.one, 2 * u.arcsec) expected = (1 * u.one, 2 * u.arcsec) result = coordinate_to_quantity(*inp, frame=frame) - for output, exp in zip(result, expected): + for output, exp in zip(result, expected, strict=False): assert_quantity_allclose(output, exp) diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index fcefc4e2..51cf8c00 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -119,7 +119,7 @@ def create_range_mapper(): ) rmapper = {} - for k, v in zip(keys, m): + for k, v in zip(keys, m, strict=False): rmapper[tuple(k)] = v sel = selector.LabelMapperRange( @@ -136,7 +136,7 @@ def create_scalar_mapper(): keys = [-1.95805483, -1.67833272, -1.39861060, -1.11888848, -8.39166358] dmapper = {} - for k, v in zip(keys, m): + for k, v in zip(keys, m, strict=False): dmapper[k] = v return dmapper @@ -216,7 +216,7 @@ def test_RegionsSelector(): inputs=("x", "y"), outputs=("x", "y"), label_mapper=mapper, selector=sel ) with pytest.raises(NotImplementedError): - reg_selector.inverse + _ = reg_selector.inverse mapper.inverse = mapper.copy() assert_allclose(reg_selector(2, 1), sel[1](2, 1)) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index f26f1e0c..82991219 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -211,7 +211,7 @@ def test_backward_transform(): poly = models.Polynomial1D(1, c0=4) w = wcs.WCS(forward_transform=poly & models.Scale(2), output_frame="sky") with pytest.raises(NotImplementedError): - w.backward_transform + _ = w.backward_transform # test backward transform poly.inverse = models.Shift(-4) @@ -970,7 +970,7 @@ def test_to_fits_tab_7d(gwcs_7d_complex_mapping): np.random.seed(1) npts = 100 pts = np.zeros((len(w.bounding_box) + 1, npts)) - for k, r in enumerate(w.bounding_box): + for k in range(len(w.bounding_box)): xmin, xmax = w.bounding_box[k] pts[k, :] = xmin + (xmax - xmin) * np.random.random(npts) @@ -1001,7 +1001,7 @@ def test_to_fits_mixed_4d(gwcs_spec_cel_time_4d): np.random.seed(1) npts = 100 pts = np.zeros((len(w.bounding_box), npts)) - for k, r in enumerate(w.bounding_box): + for k in range(len(w.bounding_box)): xmin, xmax = w.bounding_box[k] pts[k, :] = xmin + (xmax - xmin) * np.random.random(npts) @@ -1506,7 +1506,7 @@ def test_bounding_box_is_returned_F(): # Demonstrate that model_2d_shift does not have a bounding box with pytest.raises(NotImplementedError): - model_2d_shift.bounding_box + _ = model_2d_shift.bounding_box # Demonstrate that model_2d_shift_bbox does have a bounding box assert model_2d_shift_bbox.bounding_box == bbox_tuple @@ -1528,7 +1528,7 @@ def test_bounding_box_is_returned_F(): # Check that first access in this case will raise a warning with pytest.warns(wcs.GwcsBoundingBoxWarning): - gwcs_object_before.bounding_box + _ = gwcs_object_before.bounding_box # Check order is returned as F assert gwcs_object_before.bounding_box.order == "F" @@ -1547,12 +1547,12 @@ def test_no_bounding_box_if_read_from_file(tmp_path): # Check the warning is issued for the bounding box of this WCS object with pytest.warns(wcs.GwcsBoundingBoxWarning): - bad_wcs.bounding_box + _ = bad_wcs.bounding_box # Check that the warning is not issued again the second time with warnings.catch_warnings(): warnings.simplefilter("error") - bad_wcs.bounding_box + _ = bad_wcs.bounding_box # Write a bad wcs bounding box to an asdf file asdf_file = tmp_path / "bad_wcs.asdf" @@ -1567,7 +1567,7 @@ def test_no_bounding_box_if_read_from_file(tmp_path): # Check that no warning is issued for the bounding box of this WCS object with warnings.catch_warnings(): warnings.simplefilter("error") - wcs_from_file.bounding_box + _ = wcs_from_file.bounding_box def test_split_frame_wcs(): @@ -1612,7 +1612,7 @@ def test_split_frame_wcs(): expected_world = [20 * u.arcsec, 10 * u.nm, 45 * u.deg] # expected_world = [15*u.deg, 20*u.nm, 60*u.arcsec] - for expected, output in zip(expected_world, output_world): + for expected, output in zip(expected_world, output_world, strict=False): assert_allclose(output, expected.value) world_obj = iwcs.pixel_to_world(*input_pixel) diff --git a/gwcs/utils.py b/gwcs/utils.py index 0a1765d0..4d5373aa 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -85,7 +85,7 @@ def get_values(units, *args): Quantity inputs. """ if units is not None: - result = [a.to_value(unit) for a, unit in zip(args, units)] + result = [a.to_value(unit) for a, unit in zip(args, units, strict=False)] else: result = [a.value for a in args] return result @@ -470,11 +470,11 @@ def create_projection_transform(projcode): Projection transform. """ - projklassname = "Pix2Sky_" + projcode + projklassname = f"Pix2Sky_{projcode}" try: projklass = getattr(projections, projklassname) - except AttributeError: - raise UnsupportedProjectionError(projcode) + except AttributeError as err: + raise UnsupportedProjectionError(projcode) from err projparams = {} return projklass(**projparams) @@ -490,7 +490,9 @@ def is_high_level(*args, low_level_wcs): type_match = [ (type(arg), waoc[0]) - for arg, waoc in zip(args, low_level_wcs.world_axis_object_classes.values()) + for arg, waoc in zip( + args, low_level_wcs.world_axis_object_classes.values(), strict=False + ) ] types_are_high_level = [argt is t for argt, t in type_match] diff --git a/gwcs/wcs.py b/gwcs/wcs.py index a4da6378..3b1c5f39 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -272,16 +272,16 @@ def set_transform(self, from_frame, to_frame, transform): ) try: from_ind = self._get_frame_index(from_name) - except ValueError: + except ValueError as err: raise CoordinateFrameError( f"Frame {from_name} is not in the available frames" - ) + ) from err try: to_ind = self._get_frame_index(to_name) - except ValueError: + except ValueError as err: raise CoordinateFrameError( f"Frame {to_name} is not in the available frames" - ) + ) from err if from_ind + 1 != to_ind: raise ValueError(f"Frames {from_name} and {to_name} are not in sequence") @@ -324,9 +324,9 @@ def backward_transform(self): except NotImplementedError as err: raise NotImplementedError( f"Could not construct backward transform. \n{err}" - ) + ) from err try: - backward.inverse + _ = backward.inverse except NotImplementedError: # means "hasattr" won't work backward.inverse = self.forward_transform return backward @@ -391,7 +391,8 @@ def _get_frame_name(self, frame): def _add_units_input(self, arrays, frame): if frame is not None: return tuple( - u.Quantity(array, unit) for array, unit in zip(arrays, frame.unit) + u.Quantity(array, unit) + for array, unit in zip(arrays, frame.unit, strict=False) ) return arrays @@ -400,7 +401,7 @@ def _remove_units_input(self, arrays, frame): if frame is not None: return tuple( array.to_value(unit) if isinstance(array, u.Quantity) else array - for array, unit in zip(arrays, frame.unit) + for array, unit in zip(arrays, frame.unit, strict=False) ) return arrays @@ -630,7 +631,9 @@ def outside_footprint(self, world_arrays): for axtyp in axes_types: ind = np.asarray(np.asarray(self.output_frame.axes_type) == axtyp) - for idim, (coord, phys) in enumerate(zip(world_arrays, axes_phys_types)): + for idim, (coord, phys) in enumerate( + zip(world_arrays, axes_phys_types, strict=False) + ): coord = _tofloat(coord) if np.asarray(ind).sum() > 1: axis_range = footprint[:, idim] @@ -1293,7 +1296,7 @@ def correction(pix): valid = np.logical_not(invalid) in_bb = np.ones_like(invalid, dtype=np.bool_) - for c, (x1, x2) in zip(pix[valid].T, self.bounding_box): + for c, (x1, x2) in zip(pix[valid].T, self.bounding_box, strict=False): in_bb[valid] &= (c >= x1) & (c <= x2) pix[np.logical_not(in_bb)] = fill_value @@ -1410,20 +1413,20 @@ def insert_frame(self, input_frame, transform, output_frame): output_name, output_frame_obj = self._get_frame_name(output_frame) try: input_index = self._get_frame_index(input_frame) - except CoordinateFrameError: + except CoordinateFrameError as err: input_index = None if input_frame_obj is None: raise ValueError( - f"New coordinate frame {input_name} must " "be defined" - ) + f"New coordinate frame {input_name} must be defined" + ) from err try: output_index = self._get_frame_index(output_frame) - except CoordinateFrameError: + except CoordinateFrameError as err: output_index = None if output_frame_obj is None: raise ValueError( - f"New coordinate frame {output_name} must " "be defined" - ) + f"New coordinate frame {output_name} must be defined" + ) from err new_frames = [input_index, output_index].count(None) if new_frames == 0: @@ -1539,6 +1542,7 @@ def bounding_box(self): "The bounding_box will remain meaning the same but will be " "converted to F order for consistency in the GWCS.", GwcsBoundingBoxWarning, + stacklevel=2, ) self.bounding_box = bb.bounding_box(order="F") bb = self.bounding_box @@ -1958,8 +1962,10 @@ def _to_fits_sip( sky2pix_proj = getattr(projections, f"Sky2Pix_{projection}")( name=projection ) - except AttributeError: - raise ValueError("Unsupported FITS WCS sky projection: {projection}") + except AttributeError as err: + raise ValueError( + f"Unsupported FITS WCS sky projection: {projection}" + ) from err elif isinstance(projection, projections.Sky2PixProjection): sky2pix_proj = projection @@ -1969,11 +1975,13 @@ def _to_fits_sip( or not isinstance(projection, str) or len(projection) != 3 ): - raise ValueError("Unsupported FITS WCS sky projection: {sky2pix_proj}") + raise ValueError(f"Unsupported FITS WCS sky projection: {sky2pix_proj}") try: getattr(projections, f"Sky2Pix_{projection}")() - except AttributeError: - raise ValueError("Unsupported FITS WCS sky projection: {projection}") + except AttributeError as err: + raise ValueError( + f"Unsupported FITS WCS sky projection: {projection}" + ) from err else: raise TypeError( @@ -2500,11 +2508,11 @@ def to_fits_tab( try: sampling = np.broadcast_to(sampling, (self.pixel_n_dim,)) - except ValueError: + except ValueError as err: raise ValueError( "Number of sampling values either must be 1 " "or it must match the number of pixel axes." - ) + ) from err _, world_axes = self._separable_groups(detect_celestial=False) @@ -2692,11 +2700,11 @@ def to_fits( try: sampling = np.broadcast_to(sampling, (self.pixel_n_dim,)) - except ValueError: + except ValueError as err: raise ValueError( "Number of sampling values either must be 1 " "or it must match the number of pixel axes." - ) + ) from err world_axes_groups, _, celestial_group = self._separable_groups( detect_celestial=True @@ -2714,7 +2722,8 @@ def to_fits( warnings.warn( "SIP distortion is not supported when the number\n" "of axes in WCS is larger than 2. Setting 'degree'\n" - "to 1 and 'max_inv_pix_error' to None." + "to 1 and 'max_inv_pix_error' to None.", + stacklevel=2, ) degree = 1 max_inv_pix_error = None @@ -2917,7 +2926,7 @@ def _to_fits_tab( gcrds = [] cdelt = [] bb = [bounding_box[k] for k in input_axes] - for (xmin, xmax), s in zip(bb, sampling): + for (xmin, xmax), s in zip(bb, sampling, strict=False): npix = max(2, 1 + int(np.ceil(abs((xmax - xmin) / s)))) gcrds.append(np.linspace(xmin, xmax, npix)) cdelt.append((npix - 1) / (xmax - xmin) if xmin != xmax else 1) @@ -2953,7 +2962,7 @@ def _to_fits_tab( if hdr is None: hdr = fits.Header() - for m, axis_info in enumerate(world_axes_group): + for axis_info in world_axes_group: k = axis_info.axis widx = world_axes_idx.index(k) k1 = k + 1 @@ -3165,7 +3174,7 @@ def pow2(p, q): except (ValueError, linalg.LinAlgWarning, np.linalg.LinAlgError) as e: raise np.linalg.LinAlgError( f"Failed to fit SIP. Reported error:\n{e.args[0]}" - ) + ) from e if not np.all(np.isfinite([poly_coeff_x, poly_coeff_y])): raise np.linalg.LinAlgError( @@ -3247,7 +3256,7 @@ def _fit_2D_poly( if not np.isfinite(cond): # Ill-conditioned system if single_degree: - warnings.warn("The fit may be poorly conditioned.") + warnings.warn("The fit may be poorly conditioned.", stacklevel=2) cfx = cfx_i cfy = cfy_i fit_error = fit_error_i @@ -3273,12 +3282,12 @@ def _fit_2D_poly( fit_poly_x = Polynomial2D(degree=deg, c0_0=0.0) fit_poly_y = Polynomial2D(degree=deg, c0_0=0.0) - for cx, cy, (p, q) in zip(cfx, cfy, powers): + for cx, cy, (p, q) in zip(cfx, cfy, powers, strict=False): setattr(fit_poly_x, f"c{p:1d}_{q:1d}", cx) setattr(fit_poly_y, f"c{p:1d}_{q:1d}", cy) if fit_warning_msg: - warnings.warn(fit_warning_msg, linalg.LinAlgWarning) + warnings.warn(fit_warning_msg, linalg.LinAlgWarning, stacklevel=2) if fit_error <= max_error or single_degree: # Check to see if double sampling meets error requirement. @@ -3294,7 +3303,8 @@ def _fit_2D_poly( if max_resid > min(5.0 * fit_error, max_error): warnings.warn( "Double sampling check FAILED: Sampling may be too coarse for " - "the distortion model being fitted." + "the distortion model being fitted.", + stacklevel=2, ) # Residuals on the double-dense grid may be better estimates @@ -3459,6 +3469,7 @@ def __getitem__(self, ind): "Indexing a WCS.pipeline step is deprecated. " "Use the `frame` and `transform` attributes instead.", DeprecationWarning, + stacklevel=2, ) if ind not in (0, 1): raise IndexError("Allowed inices are 0 (frame) and 1 (transform).") diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 483f3727..c4d02b1f 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -86,8 +86,8 @@ def wcs_from_fiducial( model = frame2transform[item.__class__]( fiducial[ind], projection=projection ) - except KeyError: - raise TypeError(f"Coordinate frame {item} is not supported") + except KeyError as err: + raise TypeError(f"Coordinate frame {item} is not supported") from err trans_from_fiducial.append(model) fiducial_transform = functools.reduce( lambda x, y: x & y, [tr for tr in trans_from_fiducial] @@ -98,8 +98,10 @@ def wcs_from_fiducial( fiducial_transform = frame2transform[coordinate_frame.__class__]( fiducial, projection=projection ) - except KeyError: - raise TypeError(f"Coordinate frame {coordinate_frame} is not supported") + except KeyError as err: + raise TypeError( + f"Coordinate frame {coordinate_frame} is not supported" + ) from err if transform is not None: forward_transform = transform | fiducial_transform @@ -262,7 +264,7 @@ def _bbox_to_pixel(bbox): ) slices = [] - for d, s in zip(bb, step): + for d, s in zip(bb, step, strict=False): slices.append(slice(d[0], d[1] + s, s)) grid = np.mgrid[slices[::-1]][::-1] if ndim == 1: @@ -274,7 +276,7 @@ def wcs_from_points( xy, world_coords, proj_point="center", - projection=projections.Sky2Pix_TAN(), + projection=None, poly_degree=4, polynomial_type="polynomial", ): @@ -322,6 +324,8 @@ def wcs_from_points( """ from .wcs import WCS + projection = projections.Sky2Pix_TAN() if projection is None else projection + supported_poly_types = { "polynomial": models.Polynomial2D, "chebyshev": models.Chebyshev2D, diff --git a/pyproject.toml b/pyproject.toml index 2f5076ab..d9fec8c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,14 +118,12 @@ select = [ "F", # Pyflakes (part of default flake8) "W", "E", # pycodestyle (part of default flake8) "I", # isort (import sorting) - # "N", # pep8-naming - #"D", # pydocstyle (docstring style guide) "UP", # pyupgrade (upgrade code to modern python) - # "YTT", # flake8-2020 (system version info) - # "ANN", # flake8-annotations (best practices for type annotations) - #"S", # flake8-bandit (security checks) - # "BLE", # flake8-blind-except (prevent blind except statements) - #"B", # flake8-bugbear (prevent common gotcha bugs) + "YTT", # flake8-2020 (system version info) + #"ANN", # flake8-annotations (best practices for type annotations) + "S", # flake8-bandit (security checks) + "BLE", # flake8-blind-except (prevent blind except statements) + "B", # flake8-bugbear (prevent common gotcha bugs) # "A", # flake8-builtins (prevent shadowing of builtins) # "C4", # flake8-comprehensions (best practices for comprehensions) # "T10", # flake8-debugger (prevent debugger statements in code) @@ -159,3 +157,6 @@ select = [ # "LOG", "RUF", # ruff specific checks ] +ignore = [ + "S101", # Bandit: Use of assert detected +] From 532a4acca54c411eacf3730d60f67c369b90461e Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:29:58 -0500 Subject: [PATCH 10/31] Add builtin shadow checks --- convert_schemas.py | 12 +++---- docs/conf.py | 2 +- gwcs/coordinate_frames.py | 4 +-- gwcs/region.py | 2 +- gwcs/tests/test_bounding_box.py | 58 ++++++++++++++++----------------- pyproject.toml | 2 +- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index f5fd58b9..0de80fce 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -157,7 +157,7 @@ def format_type(schema, root): return f":doc:`{basename} <{ref}>`" else: - type = schema.get("type") + type = schema.get("type") # noqa: A001 if isinstance(type, list): parts = [" or ".join(type)] @@ -168,7 +168,7 @@ def format_type(schema, root): parts = [type] if type == "string": - range = format_range( + range = format_range( # noqa: A001 "*len*", "*len*", schema.get("minLength"), @@ -189,7 +189,7 @@ def format_type(schema, root): parts.append(")") elif type in ("integer", "number"): - range = format_range( + range = format_range( # noqa: A001 "*x*", "", schema.get("minimum"), @@ -202,7 +202,7 @@ def format_type(schema, root): # TODO: multipleOf elif type == "object": - range = format_range( + range = format_range( # noqa: A001 "*len*", "*len*", schema.get("minProperties"), @@ -225,7 +225,7 @@ def format_type(schema, root): parts.append("(") parts.append(format_type(items, root)) parts.append(")") - range = format_range( + range = format_range( # noqa: A001 "*len*", "*len*", schema.get("minItems"), @@ -372,7 +372,7 @@ def convert_schema_to_rst(src, dst): o = io.StringIO() - id = schema.get("id", "#") + id = schema.get("id", "#") # noqa: A001 name = os.path.basename(src[:-5]) if "title" in schema: name += ": " + schema["title"].strip() diff --git a/docs/conf.py b/docs/conf.py index 72cd277c..603e0e54 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -80,7 +80,7 @@ metadata = configuration["project"] project = metadata["name"] author = metadata["authors"][0]["name"] -copyright = f"{datetime.now().year}, {author}" +copyright = f"{datetime.now().year}, {author}" # noqa: A001 # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index e44647d8..3e91cc7b 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -470,9 +470,9 @@ def __str__(self): return self._name return self.__class__.__name__ - def _sort_property(self, property): + def _sort_property(self, prop): sorted_prop = sorted( - zip(property, self.axes_order, strict=False), key=lambda x: x[1] + zip(prop, self.axes_order, strict=False), key=lambda x: x[1] ) return tuple([t[0] for t in sorted_prop]) diff --git a/gwcs/region.py b/gwcs/region.py index 474248e9..26886f80 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -287,7 +287,7 @@ class Edge: """ - def __init__(self, name=None, start=None, stop=None, next=None): + def __init__(self, name=None, start=None, stop=None, next=None): # noqa: A002 self._start = None if start is not None: self._start = np.asarray(start) diff --git a/gwcs/tests/test_bounding_box.py b/gwcs/tests/test_bounding_box.py index c32d86c7..51130063 100644 --- a/gwcs/tests/test_bounding_box.py +++ b/gwcs/tests/test_bounding_box.py @@ -8,83 +8,83 @@ @pytest.mark.parametrize( - (("input", "output")), + (("input_", "output")), [((2, 4), (2, 4)), ((100, 200), (np.nan, np.nan)), ((x, x), (y, y))], ) -def test_2d_spatial(gwcs_2d_spatial_shift, input, output): +def test_2d_spatial(gwcs_2d_spatial_shift, input_, output): w = gwcs_2d_spatial_shift w.bounding_box = ((-0.5, 21), (4, 12)) - assert_array_equal(w.invert(*w(*input)), output) + assert_array_equal(w.invert(*w(*input_)), output) assert_array_equal( - w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + w.world_to_pixel_values(*w.pixel_to_world_values(*input_)), output ) - assert_array_equal(w.world_to_pixel(w.pixel_to_world(*input)), output) + assert_array_equal(w.world_to_pixel(w.pixel_to_world(*input_)), output) @pytest.mark.parametrize( - (("input", "output")), + (("input_", "output")), [((2, 4), (2, 4)), ((100, 200), (np.nan, np.nan)), ((x, x), (y, y))], ) -def test_2d_spatial_coordinate(gwcs_2d_quantity_shift, input, output): +def test_2d_spatial_coordinate(gwcs_2d_quantity_shift, input_, output): w = gwcs_2d_quantity_shift w.bounding_box = ((-0.5, 21), (4, 12)) - assert_array_equal(w.invert(*w(*input)), output) + assert_array_equal(w.invert(*w(*input_)), output) assert_array_equal( - w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + w.world_to_pixel_values(*w.pixel_to_world_values(*input_)), output ) - assert_array_equal(w.world_to_pixel(*w.pixel_to_world(*input)), output) + assert_array_equal(w.world_to_pixel(*w.pixel_to_world(*input_)), output) @pytest.mark.parametrize( - (("input", "output")), + (("input_", "output")), [((2, 4), (2, 4)), ((100, 200), (np.nan, np.nan)), ((x, x), (y, y))], ) -def test_2d_spatial_coordinate_reordered(gwcs_2d_spatial_reordered, input, output): +def test_2d_spatial_coordinate_reordered(gwcs_2d_spatial_reordered, input_, output): w = gwcs_2d_spatial_reordered w.bounding_box = ((-0.5, 21), (4, 12)) - assert_array_equal(w.invert(*w(*input)), output) + assert_array_equal(w.invert(*w(*input_)), output) assert_array_equal( - w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + w.world_to_pixel_values(*w.pixel_to_world_values(*input_)), output ) - assert_array_equal(w.world_to_pixel(w.pixel_to_world(*input)), output) + assert_array_equal(w.world_to_pixel(w.pixel_to_world(*input_)), output) @pytest.mark.parametrize( - (("input", "output")), [(2, 2), ((10, 200), (10, np.nan)), (x, (np.nan, 2, 4, 13))] + (("input_", "output")), [(2, 2), ((10, 200), (10, np.nan)), (x, (np.nan, 2, 4, 13))] ) -def test_1d_freq(gwcs_1d_freq, input, output): +def test_1d_freq(gwcs_1d_freq, input_, output): w = gwcs_1d_freq w.bounding_box = (-0.5, 21) - print(f"input {input}, {output}") - assert_array_equal(w.invert(w(input)), output) - assert_array_equal(w.world_to_pixel_values(w.pixel_to_world_values(input)), output) - assert_array_equal(w.world_to_pixel(w.pixel_to_world(input)), output) + print(f"input {input_}, {output}") + assert_array_equal(w.invert(w(input_)), output) + assert_array_equal(w.world_to_pixel_values(w.pixel_to_world_values(input_)), output) + assert_array_equal(w.world_to_pixel(w.pixel_to_world(input_)), output) @pytest.mark.parametrize( - (("input", "output")), + (("input_", "output")), [ ((2, 4, 5), (2, 4, 5)), ((100, 200, 5), (np.nan, np.nan, np.nan)), ((x, x, x), (y1, y1, y1)), ], ) -def test_3d_spatial_wave(gwcs_3d_spatial_wave, input, output): +def test_3d_spatial_wave(gwcs_3d_spatial_wave, input_, output): w = gwcs_3d_spatial_wave w.bounding_box = ((-0.5, 21), (4, 12), (3, 21)) - assert_array_equal(w.invert(*w(*input)), output) + assert_array_equal(w.invert(*w(*input_)), output) assert_array_equal( - w.world_to_pixel_values(*w.pixel_to_world_values(*input)), output + w.world_to_pixel_values(*w.pixel_to_world_values(*input_)), output ) - assert_array_equal(w.world_to_pixel(*w.pixel_to_world(*input)), output) + assert_array_equal(w.world_to_pixel(*w.pixel_to_world(*input_)), output) @pytest.mark.parametrize( - (("input", "output")), + (("input_", "output")), [ ((1, 2, 3, 4), (1.0, 2.0, 3.0, 4.0)), ((100, 3, 3, 3), (np.nan, 3, 3, 3)), @@ -99,7 +99,7 @@ def test_3d_spatial_wave(gwcs_3d_spatial_wave, input, output): ), ], ) -def test_gwcs_spec_cel_time_4d(gwcs_spec_cel_time_4d, input, output): +def test_gwcs_spec_cel_time_4d(gwcs_spec_cel_time_4d, input_, output): w = gwcs_spec_cel_time_4d - assert_allclose(w.invert(*w(*input, with_bounding_box=False)), output, atol=1e-8) + assert_allclose(w.invert(*w(*input_, with_bounding_box=False)), output, atol=1e-8) diff --git a/pyproject.toml b/pyproject.toml index d9fec8c5..9c1c51bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,7 @@ select = [ "S", # flake8-bandit (security checks) "BLE", # flake8-blind-except (prevent blind except statements) "B", # flake8-bugbear (prevent common gotcha bugs) - # "A", # flake8-builtins (prevent shadowing of builtins) + "A", # flake8-builtins (prevent shadowing of builtins) # "C4", # flake8-comprehensions (best practices for comprehensions) # "T10", # flake8-debugger (prevent debugger statements in code) #"EM", # flake8-errormessages (best practices for error messages) From a7ff4c3d0dbfa87a8ea339584f184072c27237e6 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:31:12 -0500 Subject: [PATCH 11/31] Add error message lint --- convert_schemas.py | 3 +- gwcs/api.py | 3 +- gwcs/converters/geometry.py | 12 +- gwcs/converters/selector.py | 9 +- gwcs/converters/spectroscopy.py | 10 +- gwcs/converters/tests/test_selector.py | 3 +- gwcs/converters/wcs.py | 3 +- gwcs/coordinate_frames.py | 29 ++-- gwcs/geometry.py | 16 +-- gwcs/region.py | 3 +- gwcs/selector.py | 40 ++++-- gwcs/spectroscopy.py | 3 +- gwcs/tests/test_api.py | 3 +- gwcs/utils.py | 28 ++-- gwcs/wcs.py | 189 ++++++++++++++----------- gwcs/wcstools.py | 43 +++--- pyproject.toml | 2 +- 17 files changed, 228 insertions(+), 171 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index 0de80fce..f6a05f57 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -403,8 +403,9 @@ def construct_mapping(self, node, deep=False): try: hash(key) except TypeError as exc: + msg = "while constructing a mapping" raise yaml.constructor.ConstructorError( - "while constructing a mapping", + msg, node.start_mark, f"found unacceptable key ({exc})", key_node.start_mark, diff --git a/gwcs/api.py b/gwcs/api.py index ddb649d6..f1bbf422 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -221,11 +221,12 @@ def pixel_shape(self, value): return wcs_naxes = self.input_frame.naxes if len(value) != wcs_naxes: - raise ValueError( + msg = ( "The number of data axes, " f"{wcs_naxes}, does not equal the " f"shape {len(value)}." ) + raise ValueError(msg) self._pixel_shape = tuple(value) diff --git a/gwcs/converters/geometry.py b/gwcs/converters/geometry.py index 3194af83..91589ac3 100644 --- a/gwcs/converters/geometry.py +++ b/gwcs/converters/geometry.py @@ -24,7 +24,8 @@ def from_yaml_tree_transform(self, node, tag, ctx): elif transform_type == "from_direction_cosines": return FromDirectionCosines() else: - raise TypeError(f"Unknown model_type {transform_type}") + msg = f"Unknown model_type {transform_type}" + raise TypeError(msg) def to_yaml_tree_transform(self, model, tag, ctx): from ..geometry import FromDirectionCosines, ToDirectionCosines @@ -34,7 +35,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): elif isinstance(model, ToDirectionCosines): transform_type = "to_direction_cosines" else: - raise TypeError(f"Model of type {model.__class__} is not supported.") + msg = f"Model of type {model.__class__} is not supported." + raise TypeError(msg) node = {"transform_type": transform_type} return node @@ -56,7 +58,8 @@ def from_yaml_tree_transform(self, node, tag, ctx): elif transform_type == "cartesian_to_spherical": return CartesianToSpherical(wrap_lon_at=wrap_lon_at) else: - raise TypeError(f"Unknown model_type {transform_type}") + msg = f"Unknown model_type {transform_type}" + raise TypeError(msg) def to_yaml_tree_transform(self, model, tag, ctx): from ..geometry import CartesianToSpherical, SphericalToCartesian @@ -66,7 +69,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): elif isinstance(model, CartesianToSpherical): transform_type = "cartesian_to_spherical" else: - raise TypeError(f"Model of type {model.__class__} is not supported.") + msg = f"Model of type {model.__class__} is not supported." + raise TypeError(msg) node = {"transform_type": transform_type, "wrap_lon_at": model.wrap_lon_at} return node diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index da24921f..54b09cf0 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -33,17 +33,19 @@ def from_yaml_tree_transform(self, node, tag, ctx): if inputs_mapping is not None and not isinstance( inputs_mapping, models.Mapping ): - raise TypeError( + msg = ( "inputs_mapping must be an instance" "of astropy.modeling.models.Mapping." ) + raise TypeError(msg) mapper = node["mapper"] atol = node.get("atol", 1e-8) no_label = node.get("no_label", np.nan) if isinstance(mapper, NDArrayType): if mapper.ndim != 2: - raise NotImplementedError("GWCS currently only supports 2D masks.") + msg = "GWCS currently only supports 2D masks." + raise NotImplementedError(msg) return LabelMapperArray(mapper, inputs_mapping) elif isinstance(mapper, Model): inputs = node.get("inputs") @@ -98,7 +100,8 @@ def to_yaml_tree_transform(self, model, tag, ctx): node["mapper"] = mapper node["inputs"] = list(model.inputs) else: - raise TypeError(f"Unrecognized type of LabelMapper - {model}") + msg = f"Unrecognized type of LabelMapper - {model}" + raise TypeError(msg) return node diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index 1b67c5e9..2ae38f9d 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -104,9 +104,8 @@ def from_yaml_tree_transform(self, node, tag, ctx): groove_density=groove_density, spectral_order=order ) else: - raise ValueError( - "Can't create a GratingEquation model with " f"output {output}" - ) + msg = "Can't create a GratingEquation model with " f"output {output}" + raise ValueError(msg) return model def to_yaml_tree_transform(self, model, tag, ctx): @@ -127,7 +126,6 @@ def to_yaml_tree_transform(self, model, tag, ctx): elif isinstance(model, WavelengthFromGratingEquation): node["output"] = "wavelength" else: - raise TypeError( - f"Can't serialize an instance of {model.__class__.__name__}" - ) + msg = f"Can't serialize an instance of {model.__class__.__name__}" + raise TypeError(msg) return node diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index 330ebdb1..5c43c9f8 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -62,7 +62,8 @@ def assert_selector_roundtrip(s, tmpdir, version=None): elif isinstance(s, selector._LabelMapper): _assert_mapper_equal(s, rs) else: - raise AssertionError("Unknown selector type") + msg = "Unknown selector type" + raise AssertionError(msg) def test_regions_selector(tmpdir): diff --git a/gwcs/converters/wcs.py b/gwcs/converters/wcs.py index 7ce69cd4..122335af 100644 --- a/gwcs/converters/wcs.py +++ b/gwcs/converters/wcs.py @@ -165,7 +165,8 @@ def from_yaml_tree(self, node, tag, ctx): from ..coordinate_frames import CompositeFrame if len(node) != 2: - raise ValueError("CompositeFrame has extra properties") + msg = "CompositeFrame has extra properties" + raise ValueError(msg) name = node["name"] frames = node["frames"] diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 3e91cc7b..1f1f23c9 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -220,7 +220,8 @@ def __post_init__(self, naxes): self.axes_type = tuple(self.axes_type) if len(self.axes_type) != naxes: - raise ValueError("Length of axes_type does not match number of axes.") + msg = "Length of axes_type does not match number of axes." + raise ValueError(msg) if self.unit is not None: if astutil.isiterable(self.unit): @@ -228,7 +229,8 @@ def __post_init__(self, naxes): else: unit = (self.unit,) if len(unit) != naxes: - raise ValueError("Number of units does not match number of axes.") + msg = "Number of units does not match number of axes." + raise ValueError(msg) else: self.unit = tuple(u.Unit(au) for au in unit) else: @@ -240,7 +242,8 @@ def __post_init__(self, naxes): else: self.axes_names = tuple(self.axes_names) if len(self.axes_names) != naxes: - raise ValueError("Number of axes names does not match number of axes.") + msg = "Number of axes names does not match number of axes." + raise ValueError(msg) else: self.axes_names = tuple([""] * naxes) @@ -248,11 +251,13 @@ def __post_init__(self, naxes): if isinstance(self.axis_physical_types, str): self.axis_physical_types = (self.axis_physical_types,) elif not isiterable(self.axis_physical_types): - raise TypeError( + msg = ( "axis_physical_types must be of type string or iterable of strings" ) + raise TypeError(msg) if len(self.axis_physical_types) != naxes: - raise ValueError(f'"axis_physical_types" must be of length {naxes}') + msg = f'"axis_physical_types" must be of length {naxes}' + raise ValueError(msg) ph_type = [] for axt in self.axis_physical_types: if axt not in VALID_UCDS and not axt.startswith("custom:"): @@ -433,7 +438,8 @@ def __init__( self._name = name if len(self._axes_order) != naxes: - raise ValueError("Length of axes_order does not match number of axes.") + msg = "Length of axes_order does not match number of axes." + raise ValueError(msg) if isinstance(axes_type, str): axes_type = (axes_type,) @@ -576,7 +582,8 @@ def to_high_level_coordinates(self, *values): if not all( [isinstance(v, numbers.Number) or type(v) is np.ndarray for v in values] ): - raise TypeError("All values should be a scalar number or a numpy array.") + msg = "All values should be a scalar number or a numpy array." + raise TypeError(msg) high_level = values_to_high_level_objects(*values, low_level_wcs=self) if len(high_level) == 1: @@ -904,11 +911,12 @@ def __init__(self, frames, name=None): ph_type += list(frame._prop.axis_physical_types) if len(np.unique(axes_order)) != len(axes_order): - raise ValueError( + msg = ( "Incorrect numbering of axes, " "axes_order should contain unique numbers, " f"got {axes_order}." ) + raise ValueError(msg) super().__init__( naxes, @@ -977,9 +985,8 @@ def world_axis_object_components(self): out[ao] = components[i] if any([o is None for o in out]): - raise ValueError( - "axes_order leads to incomplete world_axis_object_components" - ) + msg = "axes_order leads to incomplete world_axis_object_components" + raise ValueError(msg) return out diff --git a/gwcs/geometry.py b/gwcs/geometry.py index 1864325d..1fbc4d42 100644 --- a/gwcs/geometry.py +++ b/gwcs/geometry.py @@ -115,14 +115,14 @@ def wrap_lon_at(self): @wrap_lon_at.setter def wrap_lon_at(self, wrap_angle): if not (isinstance(wrap_angle, numbers.Integral) and wrap_angle in [180, 360]): - raise ValueError("'wrap_lon_at' must be an integer number: 180 or 360") + msg = "'wrap_lon_at' must be an integer number: 180 or 360" + raise ValueError(msg) self._wrap_lon_at = wrap_angle def evaluate(self, lon, lat): if isinstance(lon, u.Quantity) != isinstance(lat, u.Quantity): - raise TypeError( - "All arguments must be of the same type " "(i.e., quantity or not)." - ) + msg = "All arguments must be of the same type " "(i.e., quantity or not)." + raise TypeError(msg) lon = np.deg2rad(lon) lat = np.deg2rad(lat) @@ -194,15 +194,15 @@ def wrap_lon_at(self): @wrap_lon_at.setter def wrap_lon_at(self, wrap_angle): if not (isinstance(wrap_angle, numbers.Integral) and wrap_angle in [180, 360]): - raise ValueError("'wrap_lon_at' must be an integer number: 180 or 360") + msg = "'wrap_lon_at' must be an integer number: 180 or 360" + raise ValueError(msg) self._wrap_lon_at = wrap_angle def evaluate(self, x, y, z): nquant = [isinstance(i, u.Quantity) for i in (x, y, z)].count(True) if nquant in [1, 2]: - raise TypeError( - "All arguments must be of the same type " "(i.e., quantity or not)." - ) + msg = "All arguments must be of the same type " "(i.e., quantity or not)." + raise TypeError(msg) h = np.hypot(x, y) lat = np.rad2deg(np.arctan2(z, h)) diff --git a/gwcs/region.py b/gwcs/region.py index 26886f80..e9db4166 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -91,7 +91,8 @@ class Polygon(Region): def __init__(self, rid, vertices, coord_system="Cartesian"): if len(vertices) < 4: - raise ValueError("Expected vertices to be a list of minimum 4 tuples (x,y)") + msg = "Expected vertices to be a list of minimum 4 tuples (x,y)" + raise ValueError(msg) super().__init__(rid, coord_system) diff --git a/gwcs/selector.py b/gwcs/selector.py index 51ad1a35..c0b650fd 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -104,7 +104,8 @@ def get_unique_regions(regions): for key in regions.keys(): unique_regions.append(regions[key](key)) else: - raise TypeError("Unable to get unique regions.") + msg = "Unable to get unique regions." + raise TypeError(msg) return unique_regions @@ -154,7 +155,8 @@ def no_label(self): return self._no_label def evaluate(self, *args): - raise NotImplementedError("Subclasses should implement this method.") + msg = "Subclasses should implement this method." + raise NotImplementedError(msg) class LabelMapperArray(_LabelMapper): @@ -291,7 +293,8 @@ def __init__( self._inputs = inputs self._n_inputs = len(inputs) if not all([m.n_outputs == 1 for m in mapper.values()]): - raise TypeError("All transforms in mapper must have one output.") + msg = "All transforms in mapper must have one output." + raise TypeError(msg) self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} super().__init__(mapper, _no_label, inputs_mapping, name=name, **kwargs) @@ -384,12 +387,14 @@ class LabelMapperRange(_LabelMapper): def __init__(self, inputs, mapper, inputs_mapping=None, name=None, **kwargs): if self._has_overlapping(np.array(list(mapper.keys()))): - raise ValueError("Overlapping ranges of values are not supported.") + msg = "Overlapping ranges of values are not supported." + raise ValueError(msg) self._inputs = inputs self._n_inputs = len(inputs) _no_label = 0 if not all([m.n_outputs == 1 for m in mapper.values()]): - raise TypeError("All transforms in mapper must have one output.") + msg = "All transforms in mapper must have one output." + raise TypeError(msg) self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} super().__init__(mapper, _no_label, inputs_mapping, name=name, **kwargs) @@ -454,7 +459,8 @@ def _find_range(self, value_range, value): a, b = value_range[:, 0], value_range[:, 1] ind = np.logical_and(value >= a, value <= b).nonzero()[0] if ind.size > 1: - raise ValueError("There are overlapping ranges.") + msg = "There are overlapping ranges." + raise ValueError(msg) elif ind.size == 0: return None else: @@ -545,7 +551,8 @@ def __init__( self._selector = selector # copy.deepcopy(selector) if " " in selector.keys() or 0 in selector.keys(): - raise ValueError('"0" and " " are not allowed as keys.') + msg = '"0" and " " are not allowed as keys.' + raise ValueError(msg) self._input_units_strict = {key: False for key in self._inputs} self._input_units_allow_dimensionless = {key: False for key in self._inputs} super().__init__(n_models=1, name=name, **kwargs) @@ -561,10 +568,11 @@ def uses_quantity(self): elif not_all_uses_quantity: return False else: - raise ValueError( + msg = ( "You can not mix models which use quantity and do not use " "quantity inside a RegionSelector" ) + raise ValueError(msg) def set_input(self, rid): """ @@ -573,7 +581,8 @@ def set_input(self, rid): if rid in self._selector: return self._selector[rid] else: - raise RegionError(f"Region {rid} not found") + msg = f"Region {rid} not found" + raise RegionError(msg) def inverse(self): if self.label_mapper.inverse is not None: @@ -582,18 +591,20 @@ def inverse(self): for rid in self._selector: transforms_inv[rid] = self._selector[rid].inverse except AttributeError as err: - raise NotImplementedError( + msg = ( "The inverse of all regions must be defined" "for RegionsSelector to have an inverse." - ) from err + ) + raise NotImplementedError(msg) from err return self.__class__( self.outputs, self.inputs, transforms_inv, self.label_mapper.inverse ) else: - raise NotImplementedError( + msg = ( "The label mapper must have an inverse " "for RegionsSelector to have an inverse." ) + raise NotImplementedError(msg) def evaluate(self, *args): """ @@ -720,9 +731,8 @@ def __init__( elif inputs_mapping is not None and not isinstance( inputs_mapping, astmodels.Mapping ): - raise TypeError( - "inputs_mapping must be an instance of astropy.modeling.Mapping." - ) + msg = "inputs_mapping must be an instance of astropy.modeling.Mapping." + raise TypeError(msg) self._inputs_mapping = inputs_mapping self._mapper = mapper diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index 99a36eb1..59b531eb 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -130,7 +130,8 @@ def __init__(self, groove_density, spectral_order, **kwargs): def evaluate(self, wavelength, alpha_in, beta_in, groove_density, spectral_order): if alpha_in.shape != beta_in.shape: - raise ValueError("Expected input arrays to have the same shape.") + msg = "Expected input arrays to have the same shape." + raise ValueError(msg) if isinstance(groove_density, u.Quantity): alpha_in = u.Quantity(alpha_in) diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index c5ae088a..10bb5122 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -292,7 +292,8 @@ def _compare_frame_output(wc1, wc2): assert wc1 == wc2 else: - raise AssertionError(f"Can't Compare {type(wc1)}") + msg = f"Can't Compare {type(wc1)}" + raise AssertionError(msg) @fixture_all_wcses diff --git a/gwcs/utils.py b/gwcs/utils.py index 4d5373aa..afdef731 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -157,7 +157,8 @@ def get_projcode(wcs_info): return None projcode = wcs_info["CTYPE"][sky_axes[0]][5:8].upper() if projcode not in projections.projcodes: - raise UnsupportedProjectionError(f"Projection code {projcode}, not recognized") + msg = f"Projection code {projcode}, not recognized" + raise UnsupportedProjectionError(msg) return projcode @@ -256,7 +257,8 @@ def get_axes(header): elif isinstance(header, dict): wcs_info = header else: - raise TypeError("Expected a FITS Header or a dict.") + msg = "Expected a FITS Header or a dict." + raise TypeError(msg) # Split each CTYPE value at "-" and take the first part. # This should represent the coordinate system. @@ -286,15 +288,13 @@ def _is_skysys_consistent(ctype, sky_inmap): for item in sky_pairs.values(): if ctype[sky_inmap[0]] == item[0]: if ctype[sky_inmap[1]] != item[1]: - raise ValueError( - "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) - ) + msg = "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) + raise ValueError(msg) break elif ctype[sky_inmap[1]] == item[0]: if ctype[sky_inmap[0]] != item[1]: - raise ValueError( - "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) - ) + msg = "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) + raise ValueError(msg) sky_inmap = sky_inmap[::-1] break @@ -338,7 +338,8 @@ def make_fitswcs_transform(header): elif isinstance(header, dict): wcs_info = header else: - raise TypeError("Expected a FITS Header or a dict.") + msg = "Expected a FITS Header or a dict." + raise TypeError(msg) transforms = [] wcs_linear = fitswcs_linear(wcs_info) transforms.append(wcs_linear) @@ -363,7 +364,8 @@ def fitswcs_linear(header): elif isinstance(header, dict): wcs_info = header else: - raise TypeError("Expected a FITS Header or a dict.") + msg = "Expected a FITS Header or a dict." + raise TypeError(msg) pc = wcs_info["PC"] # get the part of the PC matrix corresponding to the imaging axes @@ -434,7 +436,8 @@ def fitswcs_nonlinear(header): elif isinstance(header, dict): wcs_info = header else: - raise TypeError("Expected a FITS Header or a dict.") + msg = "Expected a FITS Header or a dict." + raise TypeError(msg) transforms = [] projcode = get_projcode(wcs_info) @@ -501,10 +504,11 @@ def is_high_level(*args, low_level_wcs): return True if any(types_are_high_level): - raise TypeError( + msg = ( "Invalid types were passed, got " f"({', '.join(tm[0].__name__ for tm in type_match)}) expected " f"({', '.join(tm[1].__name__ for tm in type_match)})." ) + raise TypeError(msg) return False diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 3b1c5f39..43b1c248 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -176,10 +176,11 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): if forward_transform is not None: if isinstance(forward_transform, Model): if output_frame is None: - raise CoordinateFrameError( + msg = ( "An output_frame must be specified " "if forward_transform is a model." ) + raise CoordinateFrameError(msg) _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) @@ -199,17 +200,19 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): super().__setattr__(name, frame_obj) self._pipeline = forward_transform else: - raise TypeError( + msg = ( "Expected forward_transform to be a model or a " f"(frame, transform) list, got {type(forward_transform)}" ) + raise TypeError(msg) else: # Initialize a WCS without a forward_transform - allows building a # WCS programmatically. if output_frame is None: - raise CoordinateFrameError( + msg = ( "An output_frame must be specified " "if forward_transform is None." ) + raise CoordinateFrameError(msg) _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) super().__setattr__(_input_frame, inp_frame_obj) @@ -263,28 +266,25 @@ def set_transform(self, from_frame, to_frame, transform): to_name, to_obj = self._get_frame_name(to_frame) if not self._pipeline: if from_name != self._input_frame: - raise CoordinateFrameError( - f"Expected 'from_frame' to be {self._input_frame}" - ) + msg = f"Expected 'from_frame' to be {self._input_frame}" + raise CoordinateFrameError(msg) if to_frame != self._output_frame: - raise CoordinateFrameError( - f"Expected 'to_frame' to be {self._output_frame}" - ) + msg = f"Expected 'to_frame' to be {self._output_frame}" + raise CoordinateFrameError(msg) try: from_ind = self._get_frame_index(from_name) except ValueError as err: - raise CoordinateFrameError( - f"Frame {from_name} is not in the available frames" - ) from err + msg = f"Frame {from_name} is not in the available frames" + raise CoordinateFrameError(msg) from err try: to_ind = self._get_frame_index(to_name) except ValueError as err: - raise CoordinateFrameError( - f"Frame {to_name} is not in the available frames" - ) from err + msg = f"Frame {to_name} is not in the available frames" + raise CoordinateFrameError(msg) from err if from_ind + 1 != to_ind: - raise ValueError(f"Frames {from_name} and {to_name} are not in sequence") + msg = f"Frames {from_name} and {to_name} are not in sequence" + raise ValueError(msg) self._pipeline[from_ind].transform = transform @property @@ -322,9 +322,8 @@ def backward_transform(self): try: backward = self.forward_transform.inverse except NotImplementedError as err: - raise NotImplementedError( - f"Could not construct backward transform. \n{err}" - ) from err + msg = f"Could not construct backward transform. \n{err}" + raise NotImplementedError(msg) from err try: _ = backward.inverse except NotImplementedError: # means "hasattr" won't work @@ -342,7 +341,8 @@ def _get_frame_by_name(self, frame_name): step.frame for step in self._pipeline if step.frame.name == frame_name ] if len(frames) > 1: - raise ValueError(f"There is more than one frame named {frame_name}") + msg = f"There is more than one frame named {frame_name}" + raise ValueError(msg) if len(frames) == 0: return ValueError(f"No frame found matching {frame_name}") return frames[0] @@ -360,9 +360,8 @@ def _get_frame_index(self, frame): try: return frame_names.index(frame) except ValueError as e: - raise CoordinateFrameError( - f"Frame {frame} is not in the available frames" - ) from e + msg = f"Frame {frame} is not in the available frames" + raise CoordinateFrameError(msg) from e def _get_frame_name(self, frame): """ @@ -461,7 +460,8 @@ def _call_forward( to_frame = self.output_frame if transform is None: - raise NotImplementedError("WCS.forward_transform is not implemented.") + msg = "WCS.forward_transform is not implemented." + raise NotImplementedError(msg) # Validate that the input type matches what the transform expects input_is_quantity = any(isinstance(a, u.Quantity) for a in args) @@ -929,27 +929,30 @@ def numerical_inverse( """ # noqa: E501 if kwargs.pop("with_units", False): - raise ValueError( + msg = ( "Support for with_units in numerical_inverse has been removed, " "use inverse" ) + raise ValueError(msg) args_shape = np.shape(args) nargs = args_shape[0] arg_dim = len(args_shape) - 1 if nargs != self.world_n_dim: - raise ValueError( + msg = ( "Number of input coordinates is different from " "the number of defined world coordinates in the " f"WCS ({self.world_n_dim:d})" ) + raise ValueError(msg) if self.world_n_dim != self.pixel_n_dim: - raise NotImplementedError( + msg = ( "Support for iterative inverse for transformations with " "different number of inputs and outputs was not implemented." ) + raise NotImplementedError(msg) # initial guess: if nargs == 2 and self._approx_inverse is None: @@ -1267,10 +1270,13 @@ def correction(pix): # ############################################################ if (ind is not None or inddiv is not None) and not quiet: if inddiv is None: - raise NoConvergence( + msg = ( "'WCS.numerical_inverse' failed to " f"converge to the requested accuracy after {k:d} " - "iterations.", + "iterations." + ) + raise NoConvergence( + msg, best_solution=pix, accuracy=np.abs(dpix), niter=k, @@ -1278,11 +1284,14 @@ def correction(pix): divergent=None, ) else: - raise NoConvergence( + msg = ( "'WCS.numerical_inverse' failed to " "converge to the requested accuracy.\n" f"After {k:d} iterations, the solution is diverging " - "at least for one input point.", + "at least for one input point." + ) + raise NoConvergence( + msg, best_solution=pix, accuracy=np.abs(dpix), niter=k, @@ -1416,29 +1425,29 @@ def insert_frame(self, input_frame, transform, output_frame): except CoordinateFrameError as err: input_index = None if input_frame_obj is None: - raise ValueError( - f"New coordinate frame {input_name} must be defined" - ) from err + msg = f"New coordinate frame {input_name} must be defined" + raise ValueError(msg) from err try: output_index = self._get_frame_index(output_frame) except CoordinateFrameError as err: output_index = None if output_frame_obj is None: - raise ValueError( - f"New coordinate frame {output_name} must be defined" - ) from err + msg = f"New coordinate frame {output_name} must be defined" + raise ValueError(msg) from err new_frames = [input_index, output_index].count(None) if new_frames == 0: - raise ValueError( + msg = ( "Could not insert frame as both frames " f"{input_name} and {output_name} already exist" ) + raise ValueError(msg) elif new_frames == 2: - raise ValueError( + msg = ( "Could not insert frame as neither frame " f"{input_name} nor {output_name} exists" ) + raise ValueError(msg) if input_index is None: self._pipeline = ( @@ -1655,7 +1664,8 @@ def _order_clockwise(v): if bounding_box is None: if self.bounding_box is None: - raise TypeError("Need a valid bounding_box to compute the footprint.") + msg = "Need a valid bounding_box to compute the footprint." + raise TypeError(msg) bb = self.bounding_box.bounding_box(order="F") else: bb = bounding_box @@ -1686,7 +1696,8 @@ def _order_clockwise(v): np.array([t.lower() for t in self.output_frame.axes_type]) == axis_type ) if not axtyp_ind.any(): - raise ValueError(f'This WCS does not have axis of type "{axis_type}".') + msg = f'This WCS does not have axis of type "{axis_type}".' + raise ValueError(msg) if len(axtyp_ind) > 1: result = np.asarray([(r.min(), r.max()) for r in result[axtyp_ind]]) @@ -1838,7 +1849,8 @@ def to_fits_sip( """ _, _, celestial_group = self._separable_groups(detect_celestial=True) if celestial_group is None: - raise ValueError("The to_fits_sip requires an output celestial frame.") + msg = "The to_fits_sip requires an output celestial frame." + raise ValueError(msg) hdr = self._to_fits_sip( celestial_group=celestial_group, @@ -1949,12 +1961,12 @@ def _to_fits_sip( matrix_type = matrix_type.upper() if matrix_type not in ["CD", "PC-CDELT1", "PC-SUM1", "PC-DET1", "PC-SCALE"]: - raise ValueError(f"Unsupported 'matrix_type' value: {matrix_type!r}.") + msg = f"Unsupported 'matrix_type' value: {matrix_type!r}." + raise ValueError(msg) if npoints < 8: - raise ValueError( - "Number of sampling points is too small. 'npoints' must be >= 8." - ) + msg = "Number of sampling points is too small. 'npoints' must be >= 8." + raise ValueError(msg) if isinstance(projection, str): projection = projection.upper() @@ -1963,9 +1975,8 @@ def _to_fits_sip( name=projection ) except AttributeError as err: - raise ValueError( - f"Unsupported FITS WCS sky projection: {projection}" - ) from err + msg = f"Unsupported FITS WCS sky projection: {projection}" + raise ValueError(msg) from err elif isinstance(projection, projections.Sky2PixProjection): sky2pix_proj = projection @@ -1975,19 +1986,20 @@ def _to_fits_sip( or not isinstance(projection, str) or len(projection) != 3 ): - raise ValueError(f"Unsupported FITS WCS sky projection: {sky2pix_proj}") + msg = f"Unsupported FITS WCS sky projection: {sky2pix_proj}" + raise ValueError(msg) try: getattr(projections, f"Sky2Pix_{projection}")() except AttributeError as err: - raise ValueError( - f"Unsupported FITS WCS sky projection: {projection}" - ) from err + msg = f"Unsupported FITS WCS sky projection: {projection}" + raise ValueError(msg) from err else: - raise TypeError( + msg = ( "'projection' must be either a FITS WCS string projection code " "or an instance of astropy.modeling.projections.Pix2SkyProjection." ) + raise TypeError(msg) frame = celestial_group[0].frame @@ -2001,10 +2013,11 @@ def _to_fits_sip( input_axes = sorted(set(input_axes)) if len(input_axes) != 2: - raise ValueError( + msg = ( "Only CelestialFrame that correspond to two " "input axes are supported." ) + raise ValueError(msg) # Axis number for FITS axes. # iax? - image axes; nlon, nlat - celestial axes: @@ -2019,7 +2032,8 @@ def _to_fits_sip( # Determine reference points. if bounding_box is None and self.bounding_box is None: - raise ValueError("A bounding_box is needed to proceed.") + msg = "A bounding_box is needed to proceed." + raise ValueError(msg) if bounding_box is None: bounding_box = self.bounding_box @@ -2052,7 +2066,8 @@ def _to_fits_sip( # check that the bounding box has some reasonable size: if (xmax - xmin) < 1 or (ymax - ymin) < 1: - raise ValueError("Bounding box is too small for fitting a SIP polynomial") + msg = "Bounding box is too small for fitting a SIP polynomial" + raise ValueError(msg) lon, lat = transform(crpix1, crpix2) @@ -2320,10 +2335,11 @@ def find_frame(axis_number): if axis_number in frame.axes_order: return frame else: - raise RuntimeError( + msg = ( "Encountered an output axes that does not " "belong to any output coordinate frames." ) + raise RuntimeError(msg) # use correlation matrix to find separable axes: corr_mat = self.axis_correlation_matrix @@ -2408,10 +2424,11 @@ def find_frame(axis_number): or max(input_axes) + 1 != n_inputs or min(input_axes) < 0 ): - raise ValueError( + msg = ( "Input axes indices are inconsistent with the " "forward transformation." ) + raise ValueError(msg) if detect_celestial: return axes_groups, world_axes, celestial_group @@ -2488,7 +2505,8 @@ def to_fits_tab( """ if bounding_box is None: if self.bounding_box is None: - raise ValueError("Need a valid bounding_box to compute the footprint.") + msg = "Need a valid bounding_box to compute the footprint." + raise ValueError(msg) bounding_box = self.bounding_box else: @@ -2501,18 +2519,20 @@ def to_fits_tab( bounding_box = [bounding_box] if self.pixel_n_dim > self.world_n_dim: - raise RuntimeError( + msg = ( "The case when the number of input axes is larger than the " "number of output axes is not supported." ) + raise RuntimeError(msg) try: sampling = np.broadcast_to(sampling, (self.pixel_n_dim,)) except ValueError as err: - raise ValueError( + msg = ( "Number of sampling values either must be 1 " "or it must match the number of pixel axes." - ) from err + ) + raise ValueError(msg) from err _, world_axes = self._separable_groups(detect_celestial=False) @@ -2680,7 +2700,8 @@ def to_fits( """ if bounding_box is None: if self.bounding_box is None: - raise ValueError("Need a valid bounding_box to compute the footprint.") + msg = "Need a valid bounding_box to compute the footprint." + raise ValueError(msg) bounding_box = self.bounding_box else: @@ -2693,18 +2714,20 @@ def to_fits( bounding_box = [bounding_box] if self.pixel_n_dim > self.world_n_dim: - raise RuntimeError( + msg = ( "The case when the number of input axes is larger than the " "number of output axes is not supported." ) + raise RuntimeError(msg) try: sampling = np.broadcast_to(sampling, (self.pixel_n_dim,)) except ValueError as err: - raise ValueError( + msg = ( "Number of sampling values either must be 1 " "or it must match the number of pixel axes." - ) from err + ) + raise ValueError(msg) from err world_axes_groups, _, celestial_group = self._separable_groups( detect_celestial=True @@ -2968,7 +2991,8 @@ def _to_fits_tab( k1 = k + 1 ct = cf.get_ctype_from_ucd(self.world_axis_physical_types[k]) if len(ct) > 4: - raise ValueError("Axis type name too long.") + msg = "Axis type name too long." + raise ValueError(msg) hdr[f"CTYPE{k1:d}"] = ct + (4 - len(ct)) * "-" + "-TAB" hdr[f"CUNIT{k1:d}"] = self.world_axis_units[k] @@ -3172,14 +3196,12 @@ def pow2(p, q): poly_coeff_x = linalg.lu_solve(lu_piv, bx).astype(float) poly_coeff_y = linalg.lu_solve(lu_piv, by).astype(float) except (ValueError, linalg.LinAlgWarning, np.linalg.LinAlgError) as e: - raise np.linalg.LinAlgError( - f"Failed to fit SIP. Reported error:\n{e.args[0]}" - ) from e + msg = f"Failed to fit SIP. Reported error:\n{e.args[0]}" + raise np.linalg.LinAlgError(msg) from e if not np.all(np.isfinite([poly_coeff_x, poly_coeff_y])): - raise np.linalg.LinAlgError( - "Failed to fit SIP. Computed coefficients are not finite." - ) + msg = "Failed to fit SIP. Computed coefficients are not finite." + raise np.linalg.LinAlgError(msg) cond = np.linalg.cond(a.astype(float)) @@ -3216,11 +3238,13 @@ def _fit_2D_poly( elif hasattr(degree, "__iter__"): deglist = sorted(map(int, degree)) if deglist[0] < 1 or deglist[-1] > 9: - raise ValueError("Allowed values for SIP degree are [1...9]") + msg = "Allowed values for SIP degree are [1...9]" + raise ValueError(msg) else: degree = int(degree) if degree < 1 or degree > 9: - raise ValueError("Allowed values for SIP degree are [1...9]") + msg = "Allowed values for SIP degree are [1...9]" + raise ValueError(msg) deglist = [degree] single_degree = len(deglist) == 1 @@ -3440,9 +3464,8 @@ def frame(self): @frame.setter def frame(self, val): if not isinstance(val, cf.CoordinateFrame | str): - raise TypeError( - '"frame" should be an instance of CoordinateFrame or a string.' - ) + msg = '"frame" should be an instance of CoordinateFrame or a string.' + raise TypeError(msg) self._frame = val @@ -3453,9 +3476,8 @@ def transform(self): @transform.setter def transform(self, val): if val is not None and not isinstance(val, (Model)): - raise TypeError( - '"transform" should be an instance of astropy.modeling.Model.' - ) + msg = '"transform" should be an instance of astropy.modeling.Model.' + raise TypeError(msg) self._transform = val @property @@ -3472,7 +3494,8 @@ def __getitem__(self, ind): stacklevel=2, ) if ind not in (0, 1): - raise IndexError("Allowed inices are 0 (frame) and 1 (transform).") + msg = "Allowed inices are 0 (frame) and 1 (transform)." + raise IndexError(msg) if ind == 0: return self.frame return self.transform diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index c4d02b1f..9c954b28 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -67,9 +67,8 @@ def wcs_from_fiducial( if transform is not None: if not isinstance(transform, Model): - raise UnsupportedTransformError( - "Expected transform to be an instance" "of astropy.modeling.Model" - ) + msg = "Expected transform to be an instance" "of astropy.modeling.Model" + raise UnsupportedTransformError(msg) # transform_outputs = transform.n_outputs if isinstance(fiducial, coord.SkyCoord): @@ -87,7 +86,8 @@ def wcs_from_fiducial( fiducial[ind], projection=projection ) except KeyError as err: - raise TypeError(f"Coordinate frame {item} is not supported") from err + msg = f"Coordinate frame {item} is not supported" + raise TypeError(msg) from err trans_from_fiducial.append(model) fiducial_transform = functools.reduce( lambda x, y: x & y, [tr for tr in trans_from_fiducial] @@ -99,9 +99,8 @@ def wcs_from_fiducial( fiducial, projection=projection ) except KeyError as err: - raise TypeError( - f"Coordinate frame {coordinate_frame} is not supported" - ) from err + msg = f"Coordinate frame {coordinate_frame} is not supported" + raise TypeError(msg) from err if transform is not None: forward_transform = transform | fiducial_transform @@ -109,10 +108,11 @@ def wcs_from_fiducial( forward_transform = fiducial_transform if bounding_box is not None: if len(bounding_box) != forward_transform.n_outputs: - raise ValueError( + msg = ( "Expected the number of items in 'bounding_box' to be equal to the " "number of outputs of the forawrd transform." ) + raise ValueError(msg) forward_transform.bounding_box = bounding_box[::-1] if input_frame is None: input_frame = "detector" @@ -126,9 +126,8 @@ def wcs_from_fiducial( def _verify_projection(projection): if projection is None: - raise ValueError( - "Celestial coordinate frame requires a projection to be specified." - ) + msg = "Celestial coordinate frame requires a projection to be specified." + raise ValueError(msg) if not isinstance(projection, projections.Projection): raise UnsupportedProjectionError(projection) @@ -223,13 +222,13 @@ def _bbox_to_pixel(bbox): return (np.floor(bbox[0] + 0.5), np.ceil(bbox[1] - 0.5)) if selector is not None and not isinstance(bounding_box, CompoundBoundingBox): - raise ValueError("Cannot use selector with a non-CompoundBoundingBox") + msg = "Cannot use selector with a non-CompoundBoundingBox" + raise ValueError(msg) if isinstance(bounding_box, CompoundBoundingBox): if selector is None: - raise ValueError( - "selector must be set when bounding_box is a CompoundBoundingBox" - ) + msg = "selector must be set when bounding_box is a CompoundBoundingBox" + raise ValueError(msg) bounding_box = bounding_box[selector] @@ -259,9 +258,8 @@ def _bbox_to_pixel(bbox): step = np.repeat(step, ndim) if len(step) != len(bb): - raise ValueError( - "`step` must be a scalar, or tuple with length " "matching `bounding_box`" - ) + msg = "`step` must be a scalar, or tuple with length " "matching `bounding_box`" + raise ValueError(msg) slices = [] for d, s in zip(bb, step, strict=False): @@ -335,7 +333,8 @@ def wcs_from_points( x, y = xy if not isinstance(world_coords, coord.SkyCoord): - raise TypeError("`world_coords` must be an `~astropy.coordinates.SkyCoord`") + msg = "`world_coords` must be an `~astropy.coordinates.SkyCoord`" + raise TypeError(msg) try: lon, lat = world_coords.data.lon.deg, world_coords.data.lat.deg except AttributeError: @@ -363,13 +362,15 @@ def wcs_from_points( ) if not isinstance(projection, projections.Projection): - raise UnsupportedProjectionError(f"Unsupported projection code {projection}") + msg = f"Unsupported projection code {projection}" + raise UnsupportedProjectionError(msg) if polynomial_type not in supported_poly_types.keys(): - raise ValueError( + msg = ( f"Unsupported polynomial_type: {polynomial_type}. " f"Only one of {supported_poly_types.keys()} is supported." ) + raise ValueError(msg) skyrot = models.RotateCelestial2Native( crval[0].to_value(u.deg), crval[1].to_value(u.deg), 180 diff --git a/pyproject.toml b/pyproject.toml index 9c1c51bb..88551aeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,7 +127,7 @@ select = [ "A", # flake8-builtins (prevent shadowing of builtins) # "C4", # flake8-comprehensions (best practices for comprehensions) # "T10", # flake8-debugger (prevent debugger statements in code) - #"EM", # flake8-errormessages (best practices for error messages) + "EM", # flake8-errormessages (best practices for error messages) # "FA", # flake8-future-annotations (correct usage future annotations) # "ISC", # flake8-implicit-str-concat (prevent implicit string concat) # "ICN", # flake8-import-conventions (enforce import conventions) From ee21cf867e36c2f259490b5ec211c587f5a4fa20 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:40:05 -0500 Subject: [PATCH 12/31] Add pytest lint --- gwcs/tests/conftest.py | 10 ++++----- gwcs/tests/test_api.py | 6 +++--- gwcs/tests/test_coordinate_systems.py | 29 ++++++++++++++------------- gwcs/tests/test_geometry.py | 22 +++++++++----------- gwcs/tests/test_region.py | 4 ++-- gwcs/tests/test_utils.py | 2 +- gwcs/tests/test_wcs.py | 19 +++++++++--------- gwcs/wcs.py | 10 ++++----- pyproject.toml | 5 ++--- 9 files changed, 50 insertions(+), 57 deletions(-) diff --git a/gwcs/tests/conftest.py b/gwcs/tests/conftest.py index 03863d08..87d97c99 100644 --- a/gwcs/tests/conftest.py +++ b/gwcs/tests/conftest.py @@ -92,23 +92,22 @@ def sellmeier_zemax(): return examples.sellmeier_zemax() -@pytest.fixture(scope="function") +@pytest.fixture def gwcs_3d_galactic_spectral(): return examples.gwcs_3d_galactic_spectral() -@pytest.fixture(scope="function") +@pytest.fixture def gwcs_1d_spectral(): return examples.gwcs_1d_spectral() -@pytest.fixture(scope="function") +@pytest.fixture def gwcs_spec_cel_time_4d(): return examples.gwcs_spec_cel_time_4d() @pytest.fixture( - scope="function", params=[ (2, 1, 0), (2, 0, 1), @@ -123,7 +122,6 @@ def gwcs_cube_with_separable_spectral(request): @pytest.fixture( - scope="function", params=[ (2, 0, 1), (2, 1, 0), @@ -140,7 +138,7 @@ def gwcs_cube_with_separable_time(request): return examples.gwcs_cube_with_separable_time(axes_order) -@pytest.fixture(scope="function") +@pytest.fixture def gwcs_7d_complex_mapping(): return examples.gwcs_7d_complex_mapping() diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index 10bb5122..40400098 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -163,7 +163,7 @@ def test_pixel_to_world_values_units_2d(gwcs_2d_shift_scale_quantity, x, y): [assert_allclose(n, p) for n, p in zip(new_api_pixel, api_pixel, strict=False)] -@pytest.mark.parametrize(("x"), (x, xarr)) +@pytest.mark.parametrize(("x"), [x, xarr]) def test_pixel_to_world_values_units_1d(gwcs_1d_freq_quantity, x): wcsobj = gwcs_1d_freq_quantity @@ -456,7 +456,7 @@ def test_pixel_to_world_quantity(gwcs_2d_shift_scale, gwcs_2d_shift_scale_quanti assert_allclose(result1.data.lat, result2.data.lat) # test for pixel units - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 gwcs_2d_shift_scale.pixel_to_world(x * u.Jy, y * u.Jy) @@ -483,7 +483,7 @@ def test_array_index_to_world_quantity( assert_allclose(result0.data.lat, result1.data.lat) # test for pixel units - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 gwcs_2d_shift_scale.array_index_to_world(x * u.Jy, y * u.Jy) diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index a2612198..ecdc327f 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -205,17 +205,17 @@ def test_axes_type(): def test_length_attributes(): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cf.CoordinateFrame( naxes=2, unit=(u.deg), axes_type=("SPATIAL", "SPATIAL"), axes_order=(0, 1) ) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cf.CoordinateFrame( naxes=2, unit=(u.deg, u.deg), axes_type=("SPATIAL",), axes_order=(0, 1) ) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cf.CoordinateFrame( naxes=2, unit=(u.deg, u.deg), @@ -285,10 +285,10 @@ def test_coordinate_to_quantity_celestial(inp): assert_quantity_allclose(lon, 10 * u.deg) assert_quantity_allclose(lat, 20 * u.deg) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 coordinate_to_quantity(10 * u.deg, 2 * u.deg, 3 * u.deg, frame=cel) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 coordinate_to_quantity((1, 2), frame=cel) @@ -407,14 +407,14 @@ def test_coordinate_to_quantity_frame_2d(): def test_coordinate_to_quantity_error(): frame = cf.Frame2D(unit=(u.one, u.arcsec)) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 coordinate_to_quantity(1, frame=frame) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 coordinate_to_quantity((1, 1), 2, frame=frame) frame = cf.TemporalFrame(reference_frame=Time([], format="isot"), unit=u.s) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 coordinate_to_quantity(1, frame=frame) @@ -450,7 +450,7 @@ def test_axis_physical_types(): fr2d = cf.Frame2D(name="d", axis_physical_types=("pos.x", "pos.y")) assert fr2d.axis_physical_types == ("custom:pos.x", "custom:pos.y") - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cf.CelestialFrame( reference_frame=coord.ICRS(), axis_physical_types=("pos.eq.ra",) ) @@ -481,7 +481,7 @@ def test_axis_physical_types(): naxes=1, ) assert frame.axis_physical_types == ("custom:length",) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cf.CoordinateFrame( name="custom_frame", axes_type=("SPATIAL",), @@ -492,7 +492,7 @@ def test_axis_physical_types(): def test_base_frame(): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cf.CoordinateFrame( name="custom_frame", axes_type=("SPATIAL",), @@ -542,9 +542,10 @@ def test_ucd1_to_ctype(caplog): ctype_to_ucd=ctype_to_ucd, allowed_ucd_duplicates=cf._ALLOWED_UCD_DUPLICATES ) - assert caplog.record_tuples[-1][1] == logging.WARNING and caplog.record_tuples[-1][ - 2 - ].startswith("Found unsupported duplicate physical type") + assert caplog.record_tuples[-1][1] == logging.WARNING + assert caplog.record_tuples[-1][2].startswith( + "Found unsupported duplicate physical type" + ) for k, v in cf._ALLOWED_UCD_DUPLICATES.items(): assert inv_map.get(k, "") == v diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index 814a7fa8..99183bf6 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -28,7 +28,7 @@ def test_spherical_cartesian_inverse(): @pytest.mark.parametrize( - "testval, unit, wrap_at", + ("testval", "unit", "wrap_at"), product( [ (45.0, -90.0, (0.0, 0.0, -1.0)), @@ -71,7 +71,7 @@ def test_spherical_to_cartesian(testval, unit, wrap_at): @pytest.mark.parametrize( - "lon, lat, unit, wrap_at", + ("lon", "lat", "unit", "wrap_at"), list( product( [0, 45, 90, 135, 180, 225, 270, 315, 360], @@ -104,7 +104,7 @@ def test_cart2spher_at_pole(cart_to_spher): @pytest.mark.parametrize( - "lonlat, unit, wrap_at", + ("lonlat", "unit", "wrap_at"), list( product( [ @@ -146,7 +146,7 @@ def test_spher2cart_roundrip_arr(lonlat, unit, wrap_at): assert u.allclose(c2s(*s2c(lon, lat)), (olon, olat), atol=atol) -@pytest.mark.parametrize("unit1, unit2", [(u.deg, 1), (1, u.deg)]) +@pytest.mark.parametrize(("unit1", "unit2"), [(u.deg, 1), (1, u.deg)]) def test_spherical_to_cartesian_mixed_Q(spher_to_cart, unit1, unit2): with pytest.raises(TypeError) as arg_err: spher_to_cart(135.0 * unit1, 45.0 * unit2) @@ -157,7 +157,7 @@ def test_spherical_to_cartesian_mixed_Q(spher_to_cart, unit1, unit2): @pytest.mark.parametrize( - "x, y, z", + ("x", "y", "z"), sorted( list( set( @@ -180,21 +180,17 @@ def test_cartesian_to_spherical_mixed_Q(cart_to_spher, x, y, z): @pytest.mark.parametrize("wrap_at", ["1", 180.0, True, 180j, [180], -180, 0]) def test_c2s2c_wrong_wrap_type(spher_to_cart, cart_to_spher, wrap_at): err_msg = "'wrap_lon_at' must be an integer number: 180 or 360" - with pytest.raises(ValueError) as arg_err: + with pytest.raises(ValueError, match=err_msg): geometry.SphericalToCartesian(wrap_lon_at=wrap_at) - assert arg_err.value.args[0] == err_msg - with pytest.raises(ValueError) as arg_err: + with pytest.raises(ValueError, match=err_msg): spher_to_cart.wrap_lon_at = wrap_at - assert arg_err.value.args[0] == err_msg - with pytest.raises(ValueError) as arg_err: + with pytest.raises(ValueError, match=err_msg): geometry.CartesianToSpherical(wrap_lon_at=wrap_at) - assert arg_err.value.args[0] == err_msg - with pytest.raises(ValueError) as arg_err: + with pytest.raises(ValueError, match=err_msg): cart_to_spher.wrap_lon_at = wrap_at - assert arg_err.value.args[0] == err_msg def test_cartesian_spherical_asdf(tmpdir): diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 51cf8c00..98c44adc 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -200,7 +200,7 @@ def test_RegionsSelector(): mapper = selector.LabelMapperArray(labels) sel = {1: models.Shift(1) & models.Scale(1), 2: models.Shift(2) & models.Scale(2)} - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 # 0 can't be a key in ``selector`` selector.RegionsSelector( inputs=("x", "y"), @@ -259,7 +259,7 @@ def test_overalpping_ranges(): for key in keys: rmapper[tuple(key)] = models.Const1D(4) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 selector.LabelMapperRange(("x", "y"), rmapper, inputs_mapping=((0,))) diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index 36c47767..106c478f 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -19,8 +19,8 @@ def test_wrong_projcode(): + ctype = {"CTYPE": ["RA---TAM", "DEC--TAM"]} with pytest.raises(UnsupportedProjectionError): - ctype = {"CTYPE": ["RA---TAM", "DEC--TAM"]} gwutils.get_projcode(ctype) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 82991219..949e007d 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -280,7 +280,7 @@ def test_bounding_box(): pipeline = [("detector", trans3), ("sky", None)] w = wcs.WCS(pipeline) bb = ((-1, 10), (6, 15)) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 w.bounding_box = bb trans2 = models.Shift(10) & models.Scale(2) pipeline = [("detector", trans2), ("sky", None)] @@ -292,7 +292,7 @@ def test_bounding_box(): w = wcs.WCS(pipeline) w.bounding_box = (1, 5) assert w.bounding_box == w.forward_transform.bounding_box - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 w.bounding_box = ((1, 5), (2, 6)) @@ -333,7 +333,7 @@ def test_compound_bounding_box(): w(13, 13, 4) # Test attaching a invalid bounding box (not ignoring input 'x') - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 w.attach_compound_bounding_box(cbb, [("x", False)]) # Test that bounding_box with quantities can be assigned and evaluates @@ -375,7 +375,7 @@ def test_grid_from_bounding_box_step(): assert_allclose(x, x1) assert_allclose(y, y1) - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 grid_from_bounding_box(bb, step=(1, 2, 1)) @@ -778,9 +778,9 @@ def test_to_fits_sip(): assert_allclose(gwcsvalx, fitsvalx, atol=4e-11, rtol=0) assert_allclose(gwcsvaly, fitsvaly, atol=4e-11, rtol=0) - with pytest.raises(ValueError): - miriwcs.bounding_box = None - mirisip = miriwcs.to_fits_sip(bounding_box=None, max_inv_pix_error=0.1) + miriwcs.bounding_box = None + with pytest.raises(ValueError): # noqa: PT011 + _ = miriwcs.to_fits_sip(bounding_box=None, max_inv_pix_error=0.1) @pytest.mark.parametrize( @@ -918,8 +918,8 @@ def test_to_fits_tab_no_bb(gwcs_3d_galactic_spectral): w.bounding_box = None # FITS WCS -TAB: - with pytest.raises(ValueError): - hdr, bt = w.to_fits_tab() + with pytest.raises(ValueError): # noqa: PT011 + _, _ = w.to_fits_tab() def test_to_fits_tab_cube(gwcs_3d_galactic_spectral): @@ -1346,7 +1346,6 @@ def test_iter_inv(): quiet=False, with_bounding_box=False, ) - assert np.allclose((x, y), (xp, yp)) xp, yp = e.value.best_solution.T assert np.allclose((x[1:], y[1:]), (xp[1:], yp[1:])) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 43b1c248..7c6e320f 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1685,7 +1685,7 @@ def _order_clockwise(v): if center: vertices = utils._toindex(vertices) - result = np.asarray(self.__call__(*vertices, **{"with_bounding_box": False})) + result = np.asarray(self.__call__(*vertices, with_bounding_box=False)) axis_type = axis_type.lower() if axis_type == "spatial" and all_spatial: @@ -3389,8 +3389,8 @@ def _reform_poly_coefficients(fit_poly_x, fit_poly_y): invcdmat = npla.inv(np.array(cdmat)) degree = fit_poly_x.degree # Now loop through all remaining coefficients - for i in range(0, degree + 1): - for j in range(0, degree + 1): + for i in range(degree + 1): + for j in range(degree + 1): if (i + j > 1) and (i + j < degree + 1): old_x = getattr(fit_poly_x, f"c{i}_{j}").value old_y = getattr(fit_poly_y, f"c{i}_{j}").value @@ -3407,8 +3407,8 @@ def _store_2D_coefficients(hdr, poly_model, coeff_prefix, keeplinear=False): """ mindeg = int(not keeplinear) degree = poly_model.degree - for i in range(0, degree + 1): - for j in range(0, degree + 1): + for i in range(degree + 1): + for j in range(degree + 1): if (i + j) > mindeg and (i + j < degree + 1): hdr[f"{coeff_prefix}_{i}_{j}"] = getattr(poly_model, f"c{i}_{j}").value diff --git a/pyproject.toml b/pyproject.toml index 88551aeb..c3ec1462 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,11 +131,10 @@ select = [ # "FA", # flake8-future-annotations (correct usage future annotations) # "ISC", # flake8-implicit-str-concat (prevent implicit string concat) # "ICN", # flake8-import-conventions (enforce import conventions) - #"G", # flake8-logging-format (best practices for logging) # "INP", # flake8-no-pep420 (prevent use of PEP420, i.e. implicit name spaces) - #"PIE", # flake8-pie (misc suggested improvement linting) + "PIE", # flake8-pie (misc suggested improvement linting) # "T20", # flake8-print (prevent print statements in code) - #"PT", # flake8-pytest-style (best practices for pytest) + "PT", # flake8-pytest-style (best practices for pytest) #"Q", # flake8-quotes (best practices for quotes) # "RSE", # flake8-raise (best practices for raising exceptions) #"RET", # flake8-return (best practices for return statements) From 1906cf2fa4c5b46102da3266164c7dbf6a75b7ad Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:44:08 -0500 Subject: [PATCH 13/31] Add return lint --- convert_schemas.py | 171 +++++++++++++------------- gwcs/api.py | 3 +- gwcs/converters/geometry.py | 20 ++- gwcs/converters/selector.py | 26 ++-- gwcs/converters/spectroscopy.py | 6 +- gwcs/converters/tests/test_wcs.py | 7 +- gwcs/coordinate_frames.py | 23 ++-- gwcs/region.py | 13 +- gwcs/selector.py | 37 +++--- gwcs/spectroscopy.py | 6 +- gwcs/tests/test_coordinate_systems.py | 3 +- gwcs/tests/test_region.py | 3 +- gwcs/tests/test_wcs.py | 3 +- gwcs/utils.py | 5 +- gwcs/wcs.py | 59 ++++----- gwcs/wcstools.py | 5 +- pyproject.toml | 4 +- 17 files changed, 176 insertions(+), 218 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index f6a05f57..b7a7939b 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -142,105 +142,102 @@ def format_type(schema, root): if "anyOf" in schema: return " :soft:`or` ".join(format_type(x, root) for x in schema["anyOf"]) - elif "allOf" in schema: + if "allOf" in schema: return " :soft:`and` ".join(format_type(x, root) for x in schema["allOf"]) - elif "$ref" in schema: + if "$ref" in schema: ref = schema["$ref"] if ref.startswith("#/"): return f":ref:`{ref[2:]} <{root}/{ref[2:]}>`" - else: - basename = os.path.basename(ref) - if "tag:stsci.edu:asdf" in ref or "tag:astropy.org:astropy" in ref: - return f"`{basename} <{ref}>`" - else: - return f":doc:`{basename} <{ref}>`" + basename = os.path.basename(ref) + if "tag:stsci.edu:asdf" in ref or "tag:astropy.org:astropy" in ref: + return f"`{basename} <{ref}>`" + return f":doc:`{basename} <{ref}>`" - else: - type = schema.get("type") # noqa: A001 - if isinstance(type, list): - parts = [" or ".join(type)] + type = schema.get("type") # noqa: A001 + if isinstance(type, list): + parts = [" or ".join(type)] - elif type is None: - parts = ["any"] + elif type is None: + parts = ["any"] - else: - parts = [type] - - if type == "string": - range = format_range( # noqa: A001 - "*len*", - "*len*", - schema.get("minLength"), - schema.get("maxLength"), - False, - False, - ) - if range is not None or "pattern" in schema or "format" in schema: - parts.append("(") - if range is not None: - parts.append(range) - if "pattern" in schema: - pattern = schema["pattern"].encode("unicode_escape") - pattern = pattern.decode("ascii") - parts.append(f":soft:`regex` :regexp:`{pattern}`") - if "format" in schema: - parts.append(":soft:`format` {}".format(schema["format"])) - parts.append(")") - - elif type in ("integer", "number"): - range = format_range( # noqa: A001 - "*x*", - "", - schema.get("minimum"), - schema.get("maximum"), - schema.get("exclusiveMinimum"), - schema.get("exclusiveMaximum"), - ) - if range is not None: - parts.append(range) - # TODO: multipleOf - - elif type == "object": - range = format_range( # noqa: A001 - "*len*", - "*len*", - schema.get("minProperties"), - schema.get("maxProperties"), - False, - False, - ) - if range is not None: - parts.append(range) - # TODO: Dependencies - # TODO: Pattern properties - - elif type == "array": - items = schema.get("items") - if schema.get("items") and isinstance(items, dict): - if schema.get("uniqueItems"): - parts.append(":soft:`of unique`") - else: - parts.append(":soft:`of`") - parts.append("(") - parts.append(format_type(items, root)) - parts.append(")") - range = format_range( # noqa: A001 - "*len*", - "*len*", - schema.get("minItems"), - schema.get("maxItems"), - False, - False, - ) + else: + parts = [type] + + if type == "string": + range = format_range( # noqa: A001 + "*len*", + "*len*", + schema.get("minLength"), + schema.get("maxLength"), + False, + False, + ) + if range is not None or "pattern" in schema or "format" in schema: + parts.append("(") if range is not None: parts.append(range) + if "pattern" in schema: + pattern = schema["pattern"].encode("unicode_escape") + pattern = pattern.decode("ascii") + parts.append(f":soft:`regex` :regexp:`{pattern}`") + if "format" in schema: + parts.append(":soft:`format` {}".format(schema["format"])) + parts.append(")") + + elif type in ("integer", "number"): + range = format_range( # noqa: A001 + "*x*", + "", + schema.get("minimum"), + schema.get("maximum"), + schema.get("exclusiveMinimum"), + schema.get("exclusiveMaximum"), + ) + if range is not None: + parts.append(range) + # TODO: multipleOf + + elif type == "object": + range = format_range( # noqa: A001 + "*len*", + "*len*", + schema.get("minProperties"), + schema.get("maxProperties"), + False, + False, + ) + if range is not None: + parts.append(range) + # TODO: Dependencies + # TODO: Pattern properties + + elif type == "array": + items = schema.get("items") + if schema.get("items") and isinstance(items, dict): + if schema.get("uniqueItems"): + parts.append(":soft:`of unique`") + else: + parts.append(":soft:`of`") + parts.append("(") + parts.append(format_type(items, root)) + parts.append(")") + range = format_range( # noqa: A001 + "*len*", + "*len*", + schema.get("minItems"), + schema.get("maxItems"), + False, + False, + ) + if range is not None: + parts.append(range) - if "enum" in schema: - parts.append(":soft:`from`") - parts.append(json.dumps(schema["enum"])) + if "enum" in schema: + parts.append(":soft:`from`") + parts.append(json.dumps(schema["enum"])) - return " ".join(parts) + return " ".join(parts) def reindent(content, indent): diff --git a/gwcs/api.py b/gwcs/api.py index f1bbf422..e607952c 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -157,8 +157,7 @@ def array_shape(self): """ if self._pixel_shape is None: return None - else: - return self._pixel_shape[::-1] + return self._pixel_shape[::-1] @array_shape.setter def array_shape(self, value): diff --git a/gwcs/converters/geometry.py b/gwcs/converters/geometry.py index 91589ac3..ab7c571f 100644 --- a/gwcs/converters/geometry.py +++ b/gwcs/converters/geometry.py @@ -21,11 +21,10 @@ def from_yaml_tree_transform(self, node, tag, ctx): transform_type = node["transform_type"] if transform_type == "to_direction_cosines": return ToDirectionCosines() - elif transform_type == "from_direction_cosines": + if transform_type == "from_direction_cosines": return FromDirectionCosines() - else: - msg = f"Unknown model_type {transform_type}" - raise TypeError(msg) + msg = f"Unknown model_type {transform_type}" + raise TypeError(msg) def to_yaml_tree_transform(self, model, tag, ctx): from ..geometry import FromDirectionCosines, ToDirectionCosines @@ -37,8 +36,7 @@ def to_yaml_tree_transform(self, model, tag, ctx): else: msg = f"Model of type {model.__class__} is not supported." raise TypeError(msg) - node = {"transform_type": transform_type} - return node + return {"transform_type": transform_type} class SphericalCartesianConverter(TransformConverterBase): @@ -55,11 +53,10 @@ def from_yaml_tree_transform(self, node, tag, ctx): wrap_lon_at = node["wrap_lon_at"] if transform_type == "spherical_to_cartesian": return SphericalToCartesian(wrap_lon_at=wrap_lon_at) - elif transform_type == "cartesian_to_spherical": + if transform_type == "cartesian_to_spherical": return CartesianToSpherical(wrap_lon_at=wrap_lon_at) - else: - msg = f"Unknown model_type {transform_type}" - raise TypeError(msg) + msg = f"Unknown model_type {transform_type}" + raise TypeError(msg) def to_yaml_tree_transform(self, model, tag, ctx): from ..geometry import CartesianToSpherical, SphericalToCartesian @@ -72,5 +69,4 @@ def to_yaml_tree_transform(self, model, tag, ctx): msg = f"Model of type {model.__class__} is not supported." raise TypeError(msg) - node = {"transform_type": transform_type, "wrap_lon_at": model.wrap_lon_at} - return node + return {"transform_type": transform_type, "wrap_lon_at": model.wrap_lon_at} diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index 54b09cf0..a47bd15a 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -47,24 +47,22 @@ def from_yaml_tree_transform(self, node, tag, ctx): msg = "GWCS currently only supports 2D masks." raise NotImplementedError(msg) return LabelMapperArray(mapper, inputs_mapping) - elif isinstance(mapper, Model): + if isinstance(mapper, Model): inputs = node.get("inputs") return LabelMapper( inputs, mapper, inputs_mapping=inputs_mapping, no_label=no_label ) - else: - inputs = node.get("inputs", None) - if inputs is not None: - inputs = tuple(inputs) - labels = mapper.get("labels") - transforms = mapper.get("models") - if isiterable(labels[0]): - labels = [tuple(label) for label in labels] - dict_mapper = dict(zip(labels, transforms, strict=False)) - return LabelMapperRange(inputs, dict_mapper, inputs_mapping) - else: - dict_mapper = dict(zip(labels, transforms, strict=False)) - return LabelMapperDict(inputs, dict_mapper, inputs_mapping, atol=atol) + inputs = node.get("inputs", None) + if inputs is not None: + inputs = tuple(inputs) + labels = mapper.get("labels") + transforms = mapper.get("models") + if isiterable(labels[0]): + labels = [tuple(label) for label in labels] + dict_mapper = dict(zip(labels, transforms, strict=False)) + return LabelMapperRange(inputs, dict_mapper, inputs_mapping) + dict_mapper = dict(zip(labels, transforms, strict=False)) + return LabelMapperDict(inputs, dict_mapper, inputs_mapping, atol=atol) def to_yaml_tree_transform(self, model, tag, ctx): from ..selector import ( diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index 2ae38f9d..51601443 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -27,11 +27,10 @@ def from_yaml_tree_transform(self, node, tag, ctx): return SellmeierGlass(node["B_coef"], node["C_coef"]) def to_yaml_tree_transform(self, model, tag, ctx): - node = { + return { "B_coef": parameter_to_value(model.B_coef), "C_coef": parameter_to_value(model.C_coef), } - return node class SellmeierZemaxConverter(TransformConverterBase): @@ -53,7 +52,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): ) def to_yaml_tree_transform(self, model, tag, ctx): - node = { + return { "B_coef": parameter_to_value(model.B_coef), "C_coef": parameter_to_value(model.C_coef), "D_coef": parameter_to_value(model.D_coef), @@ -63,7 +62,6 @@ def to_yaml_tree_transform(self, model, tag, ctx): "pressure": parameter_to_value(model.pressure), "ref_pressure": parameter_to_value(model.ref_pressure), } - return node class Snell3DConverter(TransformConverterBase): diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index 3018de47..ca86d962 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -24,7 +24,7 @@ def _assert_frame_equal(a, b): assert type(a) is type(b) if a is None: - return + return None if not isinstance(a, cf.CoordinateFrame): return a == b @@ -34,6 +34,7 @@ def _assert_frame_equal(a, b): assert a.axes_names == b.axes_names # nosec assert a.unit == b.unit # nosec assert a.reference_frame == b.reference_frame # nosec + return None def assert_frame_roundtrip(frame, tmpdir, version=None): @@ -106,7 +107,7 @@ def test_composite_frame(tmpdir): def create_test_frames(): """Creates an array of frames to be used for testing.""" - frames = [ + return [ cf.CelestialFrame(reference_frame=coord.ICRS()), cf.CelestialFrame(reference_frame=coord.FK5(equinox=time.Time("2010-01-01"))), cf.CelestialFrame( @@ -148,8 +149,6 @@ def create_test_frames(): cf.TemporalFrame(time.Time("2011-01-01")), ] - return frames - def test_frames(tmpdir): frames = create_test_frames() diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 1f1f23c9..08921d13 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -158,7 +158,7 @@ def _ucd1_to_ctype_name_mapping(ctype_to_ucd, allowed_ucd_duplicates): if ucd not in allowed_ucd_duplicates: new_ucd.add(ucd) continue - elif ucd in allowed_ucd_duplicates: + if ucd in allowed_ucd_duplicates: inv_map[ucd] = allowed_ucd_duplicates[ucd] else: inv_map[ucd] = kwd @@ -231,8 +231,7 @@ def __post_init__(self, naxes): if len(unit) != naxes: msg = "Number of units does not match number of axes." raise ValueError(msg) - else: - self.unit = tuple(u.Unit(au) for au in unit) + self.unit = tuple(u.Unit(au) for au in unit) else: self.unit = tuple(u.dimensionless_unscaled for na in range(naxes)) @@ -685,17 +684,16 @@ def __init__( def _default_axis_physical_types(self, reference_frame, axes_names): if isinstance(reference_frame, coord.Galactic): return "pos.galactic.lon", "pos.galactic.lat" - elif isinstance( + if isinstance( reference_frame, coord.GeocentricTrueEcliptic | coord.GCRS | coord.PrecessedGeocentric, ): return "pos.bodyrc.lon", "pos.bodyrc.lat" - elif isinstance(reference_frame, coord.builtin_frames.BaseRADecFrame): + if isinstance(reference_frame, coord.builtin_frames.BaseRADecFrame): return "pos.eq.ra", "pos.eq.dec" - elif isinstance(reference_frame, coord.builtin_frames.BaseEclipticFrame): + if isinstance(reference_frame, coord.builtin_frames.BaseEclipticFrame): return "pos.ecliptic.lon", "pos.ecliptic.lat" - else: - return tuple(f"custom:{t}" for t in axes_names) + return tuple(f"custom:{t}" for t in axes_names) @property def world_axis_object_classes(self): @@ -763,11 +761,11 @@ def __init__( def _default_axis_physical_types(self, unit): if unit[0].physical_type == "frequency": return ("em.freq",) - elif unit[0].physical_type == "length": + if unit[0].physical_type == "length": return ("em.wl",) - elif unit[0].physical_type == "energy": + if unit[0].physical_type == "energy": return ("em.energy",) - elif unit[0].physical_type == "speed": + if unit[0].physical_type == "speed": return ("spect.dopplerVeloc",) logging.warning( "Physical type may be ambiguous. Consider " @@ -775,8 +773,7 @@ def _default_axis_physical_types(self, unit): "either 'spect.dopplerVeloc.optical' or " "'spect.dopplerVeloc.radio'." ) - else: - return (f"custom:{unit[0].physical_type}",) + return (f"custom:{unit[0].physical_type}",) @property def world_axis_object_classes(self): diff --git a/gwcs/region.py b/gwcs/region.py index e9db4166..42b32037 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -345,13 +345,12 @@ def compute_GET_entry(self): earr = np.asarray([self._start, self._stop]) if np.diff(earr[:, 1]).item() == 0: return None - else: - entry = [ - self._ymax, - self._yminx, - (np.diff(earr[:, 0]) / np.diff(earr[:, 1])).item(), - None, - ] + entry = [ + self._ymax, + self._yminx, + (np.diff(earr[:, 0]) / np.diff(earr[:, 1])).item(), + None, + ] return entry def compute_AET_entry(self, edge): diff --git a/gwcs/selector.py b/gwcs/selector.py index c0b650fd..1001a6de 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -435,8 +435,7 @@ def _has_overlapping(ranges): end = values[:, 1] if any((end - start)[:-1] > 0) or any(start[-1] > end): return True - else: - return False + return False # move this to utils? def _find_range(self, value_range, value): @@ -461,10 +460,9 @@ def _find_range(self, value_range, value): if ind.size > 1: msg = "There are overlapping ranges." raise ValueError(msg) - elif ind.size == 0: + if ind.size == 0: return None - else: - return ind.item() + return ind.item() def evaluate(self, *args): shape = args[0].shape @@ -565,14 +563,13 @@ def uses_quantity(self): not_all_uses_quantity = [not uq for uq in all_uses_quantity] if all(all_uses_quantity): return True - elif not_all_uses_quantity: + if not_all_uses_quantity: return False - else: - msg = ( - "You can not mix models which use quantity and do not use " - "quantity inside a RegionSelector" - ) - raise ValueError(msg) + msg = ( + "You can not mix models which use quantity and do not use " + "quantity inside a RegionSelector" + ) + raise ValueError(msg) def set_input(self, rid): """ @@ -580,9 +577,8 @@ def set_input(self, rid): """ if rid in self._selector: return self._selector[rid] - else: - msg = f"Region {rid} not found" - raise RegionError(msg) + msg = f"Region {rid} not found" + raise RegionError(msg) def inverse(self): if self.label_mapper.inverse is not None: @@ -599,12 +595,11 @@ def inverse(self): return self.__class__( self.outputs, self.inputs, transforms_inv, self.label_mapper.inverse ) - else: - msg = ( - "The label mapper must have an inverse " - "for RegionsSelector to have an inverse." - ) - raise NotImplementedError(msg) + msg = ( + "The label mapper must have an inverse " + "for RegionsSelector to have an inverse." + ) + raise NotImplementedError(msg) def evaluate(self, *args): """ diff --git a/gwcs/spectroscopy.py b/gwcs/spectroscopy.py index 59b531eb..41494328 100644 --- a/gwcs/spectroscopy.py +++ b/gwcs/spectroscopy.py @@ -251,13 +251,12 @@ def evaluate(wavelength, B_coef, C_coef): B1, B2, B3 = B_coef[0] C1, C2, C3 = C_coef[0] - n = np.sqrt( + return np.sqrt( 1.0 + B1 * wavelength**2 / (wavelength**2 - C1) + B2 * wavelength**2 / (wavelength**2 - C2) + B3 * wavelength**2 / (wavelength**2 - C3) ) - return n @property def input_units(self): @@ -400,5 +399,4 @@ def evaluate( nabs_obs = nabs_ref + delnabs # Define the relative index at the system's operating T and P. - n = nabs_obs / nair_obs - return n + return nabs_obs / nair_obs diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index ecdc327f..e6ffbfe5 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -105,8 +105,7 @@ def coordinate_to_quantity(*inputs, frame): results = frame.from_high_level_coordinates(*inputs) if not isinstance(results, list): results = [results] - results = [r << unit for r, unit in zip(results, frame.unit, strict=False)] - return results + return [r << unit for r, unit in zip(results, frame.unit, strict=False)] @pytest.mark.parametrize("inputs", inputs2) diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 98c44adc..3cfaf660 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -122,10 +122,9 @@ def create_range_mapper(): for k, v in zip(keys, m, strict=False): rmapper[tuple(k)] = v - sel = selector.LabelMapperRange( + return selector.LabelMapperRange( ("x", "y"), rmapper, inputs_mapping=models.Mapping((0,), n_inputs=2) ) - return sel def create_scalar_mapper(): diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 949e007d..535f1be8 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -65,8 +65,7 @@ def asdf_open_memory_mapping_kwarg(memmap: bool) -> dict: if minversion("asdf", "3.1.0"): return {"memmap": memmap} - else: - return {"copy_arrays": not memmap} + return {"copy_arrays": not memmap} # Test initializing a WCS diff --git a/gwcs/utils.py b/gwcs/utils.py index afdef731..a495c443 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -68,8 +68,7 @@ def _toindex(value): >>> _toindex(np.array([1.5, 2.49999])) array([2, 2]) """ - indx = np.asarray(np.floor(np.asarray(value) + 0.5), dtype=int) - return indx + return np.asarray(np.floor(np.asarray(value) + 0.5), dtype=int) def get_values(units, *args): @@ -291,7 +290,7 @@ def _is_skysys_consistent(ctype, sky_inmap): msg = "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) raise ValueError(msg) break - elif ctype[sky_inmap[1]] == item[0]: + if ctype[sky_inmap[1]] == item[0]: if ctype[sky_inmap[0]] != item[1]: msg = "Inconsistent ctype for sky coordinates {} and {}".format(*ctype) raise ValueError(msg) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 7c6e320f..a62824e4 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1283,21 +1283,20 @@ def correction(pix): slow_conv=ind, divergent=None, ) - else: - msg = ( - "'WCS.numerical_inverse' failed to " - "converge to the requested accuracy.\n" - f"After {k:d} iterations, the solution is diverging " - "at least for one input point." - ) - raise NoConvergence( - msg, - best_solution=pix, - accuracy=np.abs(dpix), - niter=k, - slow_conv=ind, - divergent=inddiv, - ) + msg = ( + "'WCS.numerical_inverse' failed to " + "converge to the requested accuracy.\n" + f"After {k:d} iterations, the solution is diverging " + "at least for one input point." + ) + raise NoConvergence( + msg, + best_solution=pix, + accuracy=np.abs(dpix), + niter=k, + slow_conv=ind, + divergent=inddiv, + ) if with_bounding_box and self.bounding_box is not None: # find points outside the bounding box and replace their values @@ -1372,8 +1371,7 @@ def available_frames(self): step.frame if isinstance(step.frame, str) else step.frame.name for step in self._pipeline ] - else: - return None + return None def insert_transform(self, frame, transform, after=False): """ @@ -1442,7 +1440,7 @@ def insert_frame(self, input_frame, transform, output_frame): f"{input_name} and {output_name} already exist" ) raise ValueError(msg) - elif new_frames == 2: + if new_frames == 2: msg = ( "Could not insert frame as neither frame " f"{input_name} nor {output_name} exists" @@ -1488,8 +1486,7 @@ def output_frame(self): if not isinstance(frame, str): frame = frame.name return getattr(self, frame) - else: - return None + return None @property def input_frame(self): @@ -1499,8 +1496,7 @@ def input_frame(self): if not isinstance(frame, str): frame = frame.name return getattr(self, frame) - else: - return None + return None @property def name(self): @@ -1852,7 +1848,7 @@ def to_fits_sip( msg = "The to_fits_sip requires an output celestial frame." raise ValueError(msg) - hdr = self._to_fits_sip( + return self._to_fits_sip( celestial_group=celestial_group, keep_axis_position=False, bounding_box=bounding_box, @@ -1867,8 +1863,6 @@ def to_fits_sip( verbose=verbose, ) - return hdr - def _to_fits_sip( self, celestial_group, @@ -2432,8 +2426,7 @@ def find_frame(axis_number): if detect_celestial: return axes_groups, world_axes, celestial_group - else: - return axes_groups, world_axes + return axes_groups, world_axes def to_fits_tab( self, @@ -3273,9 +3266,8 @@ def _fit_2D_poly( if single_degree: # Nothing to do if failure is for the lowest degree raise e - else: - # Keep results from the previous iteration. Discard current fit - break + # Keep results from the previous iteration. Discard current fit + break if not np.isfinite(cond): # Ill-conditioned system @@ -3360,8 +3352,7 @@ def _compute_distance_residual(undist_x, undist_y, fit_poly_x, fit_poly_y): Compute the distance residuals and return the rms and maximum values. """ dist = np.sqrt((undist_x - fit_poly_x) ** 2 + (undist_y - fit_poly_y) ** 2) - max_resid = dist.max() - return max_resid + return dist.max() def _reform_poly_coefficients(fit_poly_x, fit_poly_y): @@ -3435,9 +3426,7 @@ def _fix_transform_inputs(transform, inputs): for k in range(1, transform.n_inputs): input_fixer &= Const1D(inputs[k]) if k in inputs else Identity(1) - transform = in_selector | input_fixer | transform - - return transform + return in_selector | input_fixer | transform class Step: diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 9c954b28..7e4ef4c4 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -154,10 +154,7 @@ def _spectral_transform(fiducial, **kwargs): def _frame2D_transform(fiducial, **kwargs): - fiducial_transform = functools.reduce( - lambda x, y: x & y, [models.Shift(val) for val in fiducial] - ) - return fiducial_transform + return functools.reduce(lambda x, y: x & y, [models.Shift(val) for val in fiducial]) frame2transform = { diff --git a/pyproject.toml b/pyproject.toml index c3ec1462..fa533c74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,9 +135,9 @@ select = [ "PIE", # flake8-pie (misc suggested improvement linting) # "T20", # flake8-print (prevent print statements in code) "PT", # flake8-pytest-style (best practices for pytest) - #"Q", # flake8-quotes (best practices for quotes) + "Q", # flake8-quotes (best practices for quotes) # "RSE", # flake8-raise (best practices for raising exceptions) - #"RET", # flake8-return (best practices for return statements) + "RET", # flake8-return (best practices for return statements) #"SLF", # flake8-self (prevent private member access) # "SLOT", # flake8-slots (require __slots__ for immutable classes) #"SIM", # flake8-simplify (suggest simplifications to code where possible) From 7006414df3843cf160814330792a9899f864263d Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:48:50 -0500 Subject: [PATCH 14/31] Add simplification linting --- convert_schemas.py | 10 ++----- gwcs/__init__.py | 7 ++--- gwcs/api.py | 5 +--- gwcs/coordinate_frames.py | 39 +++++++++++---------------- gwcs/region.py | 5 ++-- gwcs/selector.py | 13 ++++----- gwcs/tests/test_api.py | 5 +--- gwcs/tests/test_coordinate_systems.py | 5 ++-- gwcs/wcs.py | 7 ++--- gwcs/wcstools.py | 14 ++++------ pyproject.toml | 3 +-- 11 files changed, 39 insertions(+), 74 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index b7a7939b..b4843e82 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -106,19 +106,13 @@ def format_range( part += "≤" part += f" {maximum}" elif minimum is not None: - if var_end is not None: - part = f"{var_end} " - else: - part = "" + part = f"{var_end} " if var_end is not None else "" if exclusiveMinimum: part += f"> {minimum}" else: part += f"≥ {minimum}" elif maximum is not None: - if var_end is not None: - part = f"{var_end} " - else: - part = "" + part = f"{var_end} " if var_end is not None else "" if exclusiveMaximum: part += f"< {maximum}" else: diff --git a/gwcs/__init__.py b/gwcs/__init__.py index bd092eb1..cf3cda64 100644 --- a/gwcs/__init__.py +++ b/gwcs/__init__.py @@ -57,14 +57,11 @@ """ +import contextlib import importlib.metadata -try: +with contextlib.suppress(importlib.metadata.PackageNotFoundError): __version__ = importlib.metadata.version(__name__) -except importlib.metadata.PackageNotFoundError: # pragma: no cover - # package is not installed - pass # pragma: no cover - from .wcs import * # noqa from .wcstools import * # noqa diff --git a/gwcs/api.py b/gwcs/api.py index e607952c..5529a4fa 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -134,10 +134,7 @@ def world_to_array_index_values(self, *world_arrays): returned as rounded integers. """ results = self.world_to_pixel_values(*world_arrays) - if self.pixel_n_dim == 1: - results = (results,) - else: - results = results[::-1] + results = (results,) if self.pixel_n_dim == 1 else results[::-1] results = tuple(utils._toindex(result) for result in results) return results[0] if self.pixel_n_dim == 1 else results diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 08921d13..10be70c0 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -118,6 +118,7 @@ """ import abc +import contextlib import logging import numbers from collections import defaultdict @@ -158,10 +159,7 @@ def _ucd1_to_ctype_name_mapping(ctype_to_ucd, allowed_ucd_duplicates): if ucd not in allowed_ucd_duplicates: new_ucd.add(ucd) continue - if ucd in allowed_ucd_duplicates: - inv_map[ucd] = allowed_ucd_duplicates[ucd] - else: - inv_map[ucd] = kwd + inv_map[ucd] = allowed_ucd_duplicates.get(ucd, kwd) if new_ucd: logging.warning( @@ -224,10 +222,7 @@ def __post_init__(self, naxes): raise ValueError(msg) if self.unit is not None: - if astutil.isiterable(self.unit): - unit = tuple(self.unit) - else: - unit = (self.unit,) + unit = tuple(self.unit) if astutil.isiterable(self.unit) else (self.unit,) if len(unit) != naxes: msg = "Number of units does not match number of axes." raise ValueError(msg) @@ -648,17 +643,17 @@ def __init__( axis_physical_types=None, ): naxes = 2 - if reference_frame is not None: - if not isinstance(reference_frame, str): - if reference_frame.name.upper() in STANDARD_REFERENCE_FRAMES: - _axes_names = list( - reference_frame.representation_component_names.values() - ) - if "distance" in _axes_names: - _axes_names.remove("distance") - if axes_names is None: - axes_names = _axes_names - naxes = len(_axes_names) + if ( + reference_frame is not None + and not isinstance(reference_frame, str) + and reference_frame.name.upper() in STANDARD_REFERENCE_FRAMES + ): + _axes_names = list(reference_frame.representation_component_names.values()) + if "distance" in _axes_names: + _axes_names.remove("distance") + if axes_names is None: + axes_names = _axes_names + naxes = len(_axes_names) self.native_axes_order = tuple(range(naxes)) if axes_order is None: @@ -834,10 +829,8 @@ def __init__( ) self._attrs = {} for a in self.reference_frame.info._represent_as_dict_extra_attrs: - try: + with contextlib.suppress(AttributeError): self._attrs[a] = getattr(self.reference_frame, a) - except AttributeError: - pass def _default_axis_physical_types(self): return ("time",) @@ -943,7 +936,7 @@ def _wao_classes_rename_map(self): for frame in self.frames: # ensure the frame is in the mapper mapper[frame] - for key in frame.world_axis_object_classes.keys(): + for key in frame.world_axis_object_classes: if key in seen_names: new_key = f"{key}{seen_names.count(key)}" mapper[frame][key] = new_key diff --git a/gwcs/region.py b/gwcs/region.py index 42b32037..dc91560c 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -262,9 +262,8 @@ def update_AET(self, y, AET): if edge._start[1] != edge._stop[1] and edge._ymin == y: AET.append(edge) for edge in AET[::-1]: - if edge is not None: - if edge._ymax == y: - AET.remove(edge) + if edge is not None and edge._ymax == y: + AET.remove(edge) return AET def __contains__(self, px): diff --git a/gwcs/selector.py b/gwcs/selector.py index 1001a6de..a455c458 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -67,6 +67,7 @@ """ +import contextlib import warnings import numpy as np @@ -95,13 +96,11 @@ def get_unique_regions(regions): unique_regions.remove("") except ValueError: pass - try: + with contextlib.suppress(ValueError): unique_regions.remove("") - except ValueError: - pass elif isinstance(regions, dict): unique_regions = [] - for key in regions.keys(): + for key in regions: unique_regions.append(regions[key](key)) else: msg = "Unable to get unique regions." @@ -433,9 +432,7 @@ def _has_overlapping(ranges): values = np.array(values) start = np.roll(values[:, 0], -1) end = values[:, 1] - if any((end - start)[:-1] > 0) or any(start[-1] > end): - return True - return False + return bool(any((end - start)[:-1] > 0) or any(start[-1] > end)) # move this to utils? def _find_range(self, value_range, value): @@ -548,7 +545,7 @@ def __init__( self._undefined_transform_value = undefined_transform_value self._selector = selector # copy.deepcopy(selector) - if " " in selector.keys() or 0 in selector.keys(): + if " " in selector or 0 in selector: msg = '"0" and " " are not allowed as keys.' raise ValueError(msg) self._input_units_strict = {key: False for key in self._inputs} diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index 40400098..31b45752 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -285,10 +285,7 @@ def _compare_frame_output(wc1, wc2): elif isinstance(wc1, time.Time): assert u.allclose((wc1 - wc2).to(u.s), 0 * u.s) - elif isinstance(wc1, str): - assert wc1 == wc2 - - elif isinstance(wc1, coord.StokesCoord): + elif isinstance(wc1, str | coord.StokesCoord): assert wc1 == wc2 else: diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index e6ffbfe5..0d25b117 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -1,4 +1,5 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst +import contextlib import logging import astropy @@ -22,10 +23,8 @@ # Need to write a better test, using a dict {coord_frame: input_parameters} # For now remove OffsetFrame, issue #55 -try: +with contextlib.suppress(ValueError): coord_frames.remove("SkyOffsetFrame") -except ValueError: - pass icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_order=(0, 1)) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index a62824e4..6812e557 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1247,10 +1247,7 @@ def correction(pix): else: bad.append(idx) - if bad: - inddiv = np.array(bad, dtype=int) - else: - inddiv = None + inddiv = np.array(bad, dtype=int) if bad else None # Identify points that did not converge within 'maxiter' # iterations: @@ -2918,7 +2915,7 @@ def _to_fits_tab( # see what axes have been already populated in the header: used_hdr_axes = [] - for v in hdr["naxis*"].keys(): + for v in hdr["naxis*"]: try: used_hdr_axes.append(int(v.split("NAXIS")[1]) - 1) except ValueError: diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 7e4ef4c4..3f6e4300 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -65,10 +65,9 @@ def wcs_from_fiducial( """ from .wcs import WCS - if transform is not None: - if not isinstance(transform, Model): - msg = "Expected transform to be an instance" "of astropy.modeling.Model" - raise UnsupportedTransformError(msg) + if transform is not None and not isinstance(transform, Model): + msg = "Expected transform to be an instance" "of astropy.modeling.Model" + raise UnsupportedTransformError(msg) # transform_outputs = transform.n_outputs if isinstance(fiducial, coord.SkyCoord): @@ -245,10 +244,7 @@ def _bbox_to_pixel(bbox): bounding_box = (bounding_box,) else: ndim = len(bounding_box) - if center: - bb = tuple([_bbox_to_pixel(bb) for bb in bounding_box]) - else: - bb = bounding_box + bb = tuple([_bbox_to_pixel(bb) for bb in bounding_box]) if center else bounding_box step = np.atleast_1d(step) if ndim > 1 and len(step) == 1: @@ -362,7 +358,7 @@ def wcs_from_points( msg = f"Unsupported projection code {projection}" raise UnsupportedProjectionError(msg) - if polynomial_type not in supported_poly_types.keys(): + if polynomial_type not in supported_poly_types: msg = ( f"Unsupported polynomial_type: {polynomial_type}. " f"Only one of {supported_poly_types.keys()} is supported." diff --git a/pyproject.toml b/pyproject.toml index fa533c74..a5ef0065 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,9 +138,8 @@ select = [ "Q", # flake8-quotes (best practices for quotes) # "RSE", # flake8-raise (best practices for raising exceptions) "RET", # flake8-return (best practices for return statements) - #"SLF", # flake8-self (prevent private member access) # "SLOT", # flake8-slots (require __slots__ for immutable classes) - #"SIM", # flake8-simplify (suggest simplifications to code where possible) + "SIM", # flake8-simplify (suggest simplifications to code where possible) # "TID", # flake8-tidy-imports (prevent banned api and best import practices) # "TCH", # flake8-type-checking (move type checking imports into type checking blocks) # "INT", # flake8-gettext (when to use printf style strings) From d2c3220a3b037ee27c044b4d9d05ef3aa324c4bb Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 14:53:32 -0500 Subject: [PATCH 15/31] Add try/except lint --- gwcs/converters/tests/test_selector.py | 2 +- gwcs/tests/test_api.py | 2 +- gwcs/wcs.py | 20 +++++++++----------- pyproject.toml | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index 5c43c9f8..cf9b9382 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -63,7 +63,7 @@ def assert_selector_roundtrip(s, tmpdir, version=None): _assert_mapper_equal(s, rs) else: msg = "Unknown selector type" - raise AssertionError(msg) + raise TypeError(msg) def test_regions_selector(tmpdir): diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index 31b45752..6697b05d 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -290,7 +290,7 @@ def _compare_frame_output(wc1, wc2): else: msg = f"Can't Compare {type(wc1)}" - raise AssertionError(msg) + raise TypeError(msg) @fixture_all_wcses diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 6812e557..9660461f 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1570,14 +1570,11 @@ def bounding_box(self, value): if value is None: transform_0.bounding_box = value else: - try: - # Make sure the dimensions of the new bbox are correct. - if isinstance(value, CompoundBoundingBox): - bbox = CompoundBoundingBox.validate(transform_0, value, order="F") - else: - bbox = Bbox.validate(transform_0, value, order="F") - except Exception: - raise + # Make sure the dimensions of the new bbox are correct. + if isinstance(value, CompoundBoundingBox): + bbox = CompoundBoundingBox.validate(transform_0, value, order="F") + else: + bbox = Bbox.validate(transform_0, value, order="F") transform_0.bounding_box = bbox @@ -3048,9 +3045,10 @@ def _calc_approx_inv(self, max_inv_pix_error=5, inv_degree=None, npoints=16): self._approx_inverse = functools.partial( self.backward_transform, with_bounding_box=False ) - return except (NotImplementedError, KeyError): pass + else: + return if not isinstance(self.output_frame, cf.CelestialFrame): # The _calc_approx_inv method only works with celestial frame transforms @@ -3259,10 +3257,10 @@ def _fit_2D_poly( f"Maximum residual: {fit_error_i / plate_scale:.5g}" ) - except np.linalg.LinAlgError as e: + except np.linalg.LinAlgError: if single_degree: # Nothing to do if failure is for the lowest degree - raise e + raise # Keep results from the previous iteration. Discard current fit break diff --git a/pyproject.toml b/pyproject.toml index a5ef0065..85c53b4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,7 +136,7 @@ select = [ # "T20", # flake8-print (prevent print statements in code) "PT", # flake8-pytest-style (best practices for pytest) "Q", # flake8-quotes (best practices for quotes) - # "RSE", # flake8-raise (best practices for raising exceptions) + "RSE", # flake8-raise (best practices for raising exceptions) "RET", # flake8-return (best practices for return statements) # "SLOT", # flake8-slots (require __slots__ for immutable classes) "SIM", # flake8-simplify (suggest simplifications to code where possible) @@ -148,7 +148,7 @@ select = [ # "ERA", # eradicate (remove commented out code) # "PGH", # pygrep (simple grep checks) #"PL", # pylint (general linting, flake8 alternative) - #"TRY", # tryceratops (linting for try/except blocks) + "TRY", # tryceratops (linting for try/except blocks) # "FLY", # flynt (f-string conversion where possible) #"NPY", # NumPy-specific checks (recommendations from NumPy) #"PERF", # Perflint (performance linting) From 1e7c94c7d43b7e3b28092f6c0d07c2ad86ab37be Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 15:08:54 -0500 Subject: [PATCH 16/31] Add numpy linting --- gwcs/tests/test_region.py | 5 ++-- gwcs/tests/test_wcs.py | 58 +++++++++++++++++++-------------------- pyproject.toml | 2 +- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 3cfaf660..2df88ecc 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -274,7 +274,8 @@ def test_outside_range(): def test_unique_labels(): labels = (np.arange(10) * np.ones((1023, 10))).T - np.random.shuffle(labels) + rng = np.random.default_rng(0) + rng.shuffle(labels) expected = np.arange(1, 10) result = selector.get_unique_regions(labels) assert_equal(expected, result) @@ -289,7 +290,7 @@ def test_unique_labels(): "S200B1", "", ] * 1000 - np.random.shuffle(labels) + rng.shuffle(labels) expected = ["S100A1", "S1600", "S200A2", "S200B1", "S400A1"] result = selector.get_unique_regions(labels) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 535f1be8..e872d5b5 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -440,7 +440,7 @@ def test_grid_from_compound_bounding_box(): def test_wcs_from_points(): - np.random.seed(0) + rng = np.random.default_rng(0) hdr = fits.Header.fromtextfile(os.path.join(data_path, "acs.hdr"), endcard=False) with pytest.warns(astwcs.FITSFixedWarning) as caught_warnings: # this raises a warning unimportant for this testing the pix2world @@ -459,7 +459,7 @@ def test_wcs_from_points(): assert_allclose(newra, ra) assert_allclose(newdec, dec) - n = np.random.randn(ra.size) + n = rng.standard_normal(ra.size) n.shape = ra.shape nra = n * 10**-2 ndec = n * 10**-2 @@ -936,10 +936,10 @@ def test_to_fits_tab_cube(gwcs_3d_galactic_spectral): # test points: (xmin, xmax), (ymin, ymax), (zmin, zmax) = w.bounding_box - np.random.seed(1) - x = xmin + (xmax - xmin) * np.random.random(100) - y = ymin + (ymax - ymin) * np.random.random(100) - z = zmin + (zmax - zmin) * np.random.random(100) + rng = np.random.default_rng(1) + x = xmin + (xmax - xmin) * rng.random(100) + y = ymin + (ymax - ymin) * rng.random(100) + z = zmin + (zmax - zmin) * rng.random(100) # test: assert np.allclose( @@ -966,12 +966,12 @@ def test_to_fits_tab_7d(gwcs_7d_complex_mapping): fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) # test points: - np.random.seed(1) + rng = np.random.default_rng(1) npts = 100 pts = np.zeros((len(w.bounding_box) + 1, npts)) for k in range(len(w.bounding_box)): xmin, xmax = w.bounding_box[k] - pts[k, :] = xmin + (xmax - xmin) * np.random.random(npts) + pts[k, :] = xmin + (xmax - xmin) * rng.random(npts) world_crds = w(*pts[:-1, :]) @@ -997,12 +997,12 @@ def test_to_fits_mixed_4d(gwcs_spec_cel_time_4d): fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) # test points: - np.random.seed(1) + rng = np.random.default_rng(1) npts = 100 pts = np.zeros((len(w.bounding_box), npts)) for k in range(len(w.bounding_box)): xmin, xmax = w.bounding_box[k] - pts[k, :] = xmin + (xmax - xmin) * np.random.random(npts) + pts[k, :] = xmin + (xmax - xmin) * rng.random(npts) world_crds = w(*pts) @@ -1045,9 +1045,9 @@ def test_to_fits_1D_round_trip(gwcs_1d_spectral): fits_wcs = astwcs.WCS(hdulist[0].header, hdulist) # test points: - np.random.seed(1) + rng = np.random.default_rng(1) (xmin, xmax) = w.bounding_box.bounding_box() - x = xmin + (xmax - xmin) * np.random.random(100) + x = xmin + (xmax - xmin) * rng.random(100) # test forward transformation: wt = fits_wcs.wcs_pix2world(x, 0) @@ -1073,10 +1073,10 @@ def test_to_fits_sip_tab_cube(gwcs_cube_with_separable_spectral): # test points: (xmin, xmax), (ymin, ymax), (zmin, zmax) = w.bounding_box - np.random.seed(1) - x = xmin + (xmax - xmin) * np.random.random(100) - y = ymin + (ymax - ymin) * np.random.random(100) - z = zmin + (zmax - zmin) * np.random.random(100) + rng = np.random.default_rng(1) + x = xmin + (xmax - xmin) * rng.random(100) + y = ymin + (ymax - ymin) * rng.random(100) + z = zmin + (zmax - zmin) * rng.random(100) world_crds = w(x, y, z) @@ -1104,10 +1104,10 @@ def test_to_fits_tab_time_cube(gwcs_cube_with_separable_time): # test points: (xmin, xmax), (ymin, ymax), (zmin, zmax) = w.bounding_box - np.random.seed(1) - x = xmin + (xmax - xmin) * np.random.random(5) - y = ymin + (ymax - ymin) * np.random.random(5) - z = zmin + (zmax - zmin) * np.random.random(5) + rng = np.random.default_rng(1) + x = xmin + (xmax - xmin) * rng.random(5) + y = ymin + (ymax - ymin) * rng.random(5) + z = zmin + (zmax - zmin) * rng.random(5) world_crds = w(x, y, z) @@ -1139,9 +1139,9 @@ def test_to_fits_tab_miri_image(): # test points: (xmin, xmax), (ymin, ymax) = w.bounding_box - np.random.seed(1) - x = xmin + (xmax - xmin) * np.random.random(100) - y = ymin + (ymax - ymin) * np.random.random(100) + rng = np.random.default_rng(1) + x = xmin + (xmax - xmin) * rng.random(100) + y = ymin + (ymax - ymin) * rng.random(100) # test: assert np.allclose(w(x, y), fits_wcs.wcs_pix2world(x, y, 0), rtol=1e-6, atol=1e-7) @@ -1170,9 +1170,9 @@ def test_to_fits_tab_miri_lrs(): # test points: (xmin, xmax), (ymin, ymax) = w.bounding_box - np.random.seed(1) - x = xmin + (xmax - xmin) * np.random.random(100) - y = ymin + (ymax - ymin) * np.random.random(100) + rng = np.random.default_rng(1) + x = xmin + (xmax - xmin) * rng.random(100) + y = ymin + (ymax - ymin) * rng.random(100) # test: ref = np.array(w(x, y)) @@ -1180,7 +1180,7 @@ def test_to_fits_tab_miri_lrs(): m = np.cumprod(np.isfinite(ref), dtype=np.bool_, axis=0) assert hdr["WCSAXES"] == 3 - assert np.allclose(ref[m], tab[m], rtol=5e-6, atol=5e-6, equal_nan=True) + assert np.allclose(ref[m], tab[m], rtol=5e-5, atol=5e-6, equal_nan=True) def test_in_image(): @@ -1256,8 +1256,8 @@ def test_iter_inv(): ) # prepare to test a vector of points: - np.random.seed(10) - x, y = 2047 * np.random.random((2, 10000)) # "truth" + rng = np.random.default_rng(10) + x, y = 2047 * rng.random((2, 10000)) # "truth" # test adaptive: xp, yp = w.invert( diff --git a/pyproject.toml b/pyproject.toml index 85c53b4e..b0ca53aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,7 +150,7 @@ select = [ #"PL", # pylint (general linting, flake8 alternative) "TRY", # tryceratops (linting for try/except blocks) # "FLY", # flynt (f-string conversion where possible) - #"NPY", # NumPy-specific checks (recommendations from NumPy) + "NPY", # NumPy-specific checks (recommendations from NumPy) #"PERF", # Perflint (performance linting) # "LOG", "RUF", # ruff specific checks From 22e30a3f326c5e887ed4a95a5986968e727636e2 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 15:23:36 -0500 Subject: [PATCH 17/31] Add performance linting --- convert_schemas.py | 4 +--- gwcs/converters/selector.py | 8 ++------ gwcs/selector.py | 4 +--- gwcs/tests/test_region.py | 5 +---- gwcs/utils.py | 10 ++++------ gwcs/wcs.py | 7 ++++--- pyproject.toml | 2 +- 7 files changed, 14 insertions(+), 26 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index b4843e82..4e40118f 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -239,9 +239,7 @@ def reindent(content, indent): Reindent a string to the given number of spaces. """ content = textwrap.dedent(content) - lines = [] - for line in content.split("\n"): - lines.append(indent + line) + lines = [indent + line for line in content.split("\n")] return "\n".join(lines) diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index a47bd15a..625b88d0 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -88,9 +88,7 @@ def to_yaml_tree_transform(self, model, tag, ctx): mapper = OrderedDict() labels = list(model.mapper) - transforms = [] - for k in labels: - transforms.append(model.mapper[k]) + transforms = [model.mapper[k] for k in labels] if isiterable(labels[0]): labels = [list(label) for label in labels] mapper["labels"] = labels @@ -125,9 +123,7 @@ def to_yaml_tree_transform(self, model, tag, ctx): selector = OrderedDict() node = OrderedDict() labels = list(model.selector) - values = [] - for label in labels: - values.append(model.selector[label]) + values = [model.selector[label] for label in labels] selector["labels"] = labels selector["transforms"] = values node["inputs"] = list(model.inputs) diff --git a/gwcs/selector.py b/gwcs/selector.py index a455c458..01d64898 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -426,9 +426,7 @@ def _has_overlapping(ranges): start = ranges[:, 0] end = ranges[:, 1] start.sort() - values = [] - for v in start: - values.append([v, d[v]]) + values = [[v, d[v]] for v in start] values = np.array(values) start = np.roll(values[:, 0], -1) end = values[:, 1] diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 2df88ecc..94ed9c57 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -134,10 +134,7 @@ def create_scalar_mapper(): m.append(models.Polynomial2D(2, c0_0=c0_0, c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) keys = [-1.95805483, -1.67833272, -1.39861060, -1.11888848, -8.39166358] - dmapper = {} - for k, v in zip(keys, m, strict=False): - dmapper[k] = v - return dmapper + return {k: v for k, v in zip(keys, m, strict=False)} def test_LabelMapperDict(): diff --git a/gwcs/utils.py b/gwcs/utils.py index a495c443..14bc2576 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -217,12 +217,10 @@ def read_wcs_from_header(header): pc = np.zeros((wcsaxes, wcsaxes)) for i in range(1, wcsaxes + 1): for j in range(1, wcsaxes + 1): - try: - if wcs_info["has_cd"]: - pc[i - 1, j - 1] = header[f"CD{i}_{j}"] - else: - pc[i - 1, j - 1] = header[f"PC{i}_{j}"] - except KeyError: + key = f"CD{i}_{j}" if wcs_info["has_cd"] else f"PC{i}_{j}" + if key in header: + pc[i - 1, j - 1] = header[key] + else: if i == j: pc[i - 1, j - 1] = 1.0 else: diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 9660461f..d7668c7c 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -2913,11 +2913,12 @@ def _to_fits_tab( # see what axes have been already populated in the header: used_hdr_axes = [] for v in hdr["naxis*"]: - try: - used_hdr_axes.append(int(v.split("NAXIS")[1]) - 1) - except ValueError: + value = v.split("NAXIS")[1] + if not value: continue + used_hdr_axes.append(int(value) - 1) + degenerate_axis_start = max( self.pixel_n_dim + 1, max(used_hdr_axes) + 1 if used_hdr_axes else 1 ) diff --git a/pyproject.toml b/pyproject.toml index b0ca53aa..6edcffb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,7 +151,7 @@ select = [ "TRY", # tryceratops (linting for try/except blocks) # "FLY", # flynt (f-string conversion where possible) "NPY", # NumPy-specific checks (recommendations from NumPy) - #"PERF", # Perflint (performance linting) + "PERF", # Perflint (performance linting) # "LOG", "RUF", # ruff specific checks ] From 6ffba0c15ff55b7996789b815425c5f6401202d8 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 15:28:31 -0500 Subject: [PATCH 18/31] implicit string lint --- gwcs/geometry.py | 4 ++-- gwcs/wcs.py | 4 +--- gwcs/wcstools.py | 11 ++++++----- pyproject.toml | 10 +++++----- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/gwcs/geometry.py b/gwcs/geometry.py index 1fbc4d42..d1eb3928 100644 --- a/gwcs/geometry.py +++ b/gwcs/geometry.py @@ -121,7 +121,7 @@ def wrap_lon_at(self, wrap_angle): def evaluate(self, lon, lat): if isinstance(lon, u.Quantity) != isinstance(lat, u.Quantity): - msg = "All arguments must be of the same type " "(i.e., quantity or not)." + msg = "All arguments must be of the same type (i.e., quantity or not)." raise TypeError(msg) lon = np.deg2rad(lon) @@ -201,7 +201,7 @@ def wrap_lon_at(self, wrap_angle): def evaluate(self, x, y, z): nquant = [isinstance(i, u.Quantity) for i in (x, y, z)].count(True) if nquant in [1, 2]: - msg = "All arguments must be of the same type " "(i.e., quantity or not)." + msg = "All arguments must be of the same type (i.e., quantity or not)." raise TypeError(msg) h = np.hypot(x, y) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index d7668c7c..3b120a83 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -209,9 +209,7 @@ def _initialize_wcs(self, forward_transform, input_frame, output_frame): # Initialize a WCS without a forward_transform - allows building a # WCS programmatically. if output_frame is None: - msg = ( - "An output_frame must be specified " "if forward_transform is None." - ) + msg = "An output_frame must be specified if forward_transform is None." raise CoordinateFrameError(msg) _input_frame, inp_frame_obj = self._get_frame_name(input_frame) _output_frame, outp_frame_obj = self._get_frame_name(output_frame) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 3f6e4300..efb87aba 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -66,7 +66,7 @@ def wcs_from_fiducial( from .wcs import WCS if transform is not None and not isinstance(transform, Model): - msg = "Expected transform to be an instance" "of astropy.modeling.Model" + msg = "Expected transform to be an instanceof astropy.modeling.Model" raise UnsupportedTransformError(msg) # transform_outputs = transform.n_outputs @@ -251,7 +251,7 @@ def _bbox_to_pixel(bbox): step = np.repeat(step, ndim) if len(step) != len(bb): - msg = "`step` must be a scalar, or tuple with length " "matching `bounding_box`" + msg = "`step` must be a scalar, or tuple with length matching `bounding_box`" raise ValueError(msg) slices = [] @@ -348,11 +348,12 @@ def wcs_from_points( crval = (midpoint_sc.data.lon, midpoint_sc.data.lat) frame = sc1.frame else: - raise ValueError( + msg = ( "`proj_point` must be set to 'center', or an" - + "`~astropy.coordinates.SkyCoord` object with " - + "a pair of points." + "`~astropy.coordinates.SkyCoord` object with " + "a pair of points." ) + raise ValueError(msg) if not isinstance(projection, projections.Projection): msg = f"Unsupported projection code {projection}" diff --git a/pyproject.toml b/pyproject.toml index 6edcffb2..f3cf861d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,11 +126,11 @@ select = [ "B", # flake8-bugbear (prevent common gotcha bugs) "A", # flake8-builtins (prevent shadowing of builtins) # "C4", # flake8-comprehensions (best practices for comprehensions) - # "T10", # flake8-debugger (prevent debugger statements in code) + "T10", # flake8-debugger (prevent debugger statements in code) "EM", # flake8-errormessages (best practices for error messages) # "FA", # flake8-future-annotations (correct usage future annotations) - # "ISC", # flake8-implicit-str-concat (prevent implicit string concat) - # "ICN", # flake8-import-conventions (enforce import conventions) + "ISC", # flake8-implicit-str-concat (prevent implicit string concat) + "ICN", # flake8-import-conventions (enforce import conventions) # "INP", # flake8-no-pep420 (prevent use of PEP420, i.e. implicit name spaces) "PIE", # flake8-pie (misc suggested improvement linting) # "T20", # flake8-print (prevent print statements in code) @@ -138,7 +138,6 @@ select = [ "Q", # flake8-quotes (best practices for quotes) "RSE", # flake8-raise (best practices for raising exceptions) "RET", # flake8-return (best practices for return statements) - # "SLOT", # flake8-slots (require __slots__ for immutable classes) "SIM", # flake8-simplify (suggest simplifications to code where possible) # "TID", # flake8-tidy-imports (prevent banned api and best import practices) # "TCH", # flake8-type-checking (move type checking imports into type checking blocks) @@ -149,7 +148,7 @@ select = [ # "PGH", # pygrep (simple grep checks) #"PL", # pylint (general linting, flake8 alternative) "TRY", # tryceratops (linting for try/except blocks) - # "FLY", # flynt (f-string conversion where possible) + "FLY", # flynt (f-string conversion where possible) "NPY", # NumPy-specific checks (recommendations from NumPy) "PERF", # Perflint (performance linting) # "LOG", @@ -157,4 +156,5 @@ select = [ ] ignore = [ "S101", # Bandit: Use of assert detected + "ISC001", # conflicts with formatter at times ] From e4aa21a4ec5f39a0a60bc3048cabe7b02498697b Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 15:30:14 -0500 Subject: [PATCH 19/31] comprehension lint --- gwcs/coordinate_frames.py | 4 ++-- gwcs/selector.py | 4 ++-- gwcs/tests/test_api.py | 18 +++++++++--------- gwcs/tests/test_api_slicing.py | 14 +++++++------- gwcs/tests/test_geometry.py | 8 +++----- gwcs/tests/test_region.py | 2 +- gwcs/wcs.py | 2 +- gwcs/wcstools.py | 2 +- pyproject.toml | 2 +- 9 files changed, 27 insertions(+), 29 deletions(-) diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index 10be70c0..dc508bb7 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -574,7 +574,7 @@ def to_high_level_coordinates(self, *values): ] if not all( - [isinstance(v, numbers.Number) or type(v) is np.ndarray for v in values] + isinstance(v, numbers.Number) or type(v) is np.ndarray for v in values ): msg = "All values should be a scalar number or a numpy array." raise TypeError(msg) @@ -974,7 +974,7 @@ def world_axis_object_components(self): for i, ao in enumerate(frame.axes_order): out[ao] = components[i] - if any([o is None for o in out]): + if any(o is None for o in out): msg = "axes_order leads to incomplete world_axis_object_components" raise ValueError(msg) diff --git a/gwcs/selector.py b/gwcs/selector.py index 01d64898..9c15e672 100644 --- a/gwcs/selector.py +++ b/gwcs/selector.py @@ -291,7 +291,7 @@ def __init__( _no_label = 0 self._inputs = inputs self._n_inputs = len(inputs) - if not all([m.n_outputs == 1 for m in mapper.values()]): + if not all(m.n_outputs == 1 for m in mapper.values()): msg = "All transforms in mapper must have one output." raise TypeError(msg) self._input_units_strict = {key: False for key in self._inputs} @@ -391,7 +391,7 @@ def __init__(self, inputs, mapper, inputs_mapping=None, name=None, **kwargs): self._inputs = inputs self._n_inputs = len(inputs) _no_label = 0 - if not all([m.n_outputs == 1 for m in mapper.values()]): + if not all(m.n_outputs == 1 for m in mapper.values()): msg = "All transforms in mapper must have one output." raise TypeError(msg) self._input_units_strict = {key: False for key in self._inputs} diff --git a/gwcs/tests/test_api.py b/gwcs/tests/test_api.py index 6697b05d..916d5013 100644 --- a/gwcs/tests/test_api.py +++ b/gwcs/tests/test_api.py @@ -150,8 +150,8 @@ def test_pixel_to_world_values_units_2d(gwcs_2d_shift_scale_quantity, x, y): api_world = wcsobj.pixel_to_world_values(*api_pixel) # Check that call returns quantities and api doesn't - assert all(list(isinstance(a, u.Quantity) for a in call_world)) - assert all(list(not isinstance(a, u.Quantity) for a in api_world)) + assert all(isinstance(a, u.Quantity) for a in call_world) + assert all(not isinstance(a, u.Quantity) for a in api_world) # Check that they are the same (and implicitly in the same units) assert_allclose(u.Quantity(call_world).value, api_world) @@ -222,13 +222,13 @@ def test_world_axis_object_components_4d(gwcs_4d_identity_units): ("spectral", 0), ("temporal", 0), ] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) def test_world_axis_object_classes_2d(gwcs_2d_spatial_shift): waoc = gwcs_2d_spatial_shift.world_axis_object_classes assert waoc["celestial"][0] is coord.SkyCoord - assert waoc["celestial"][1] == tuple() + assert waoc["celestial"][1] == () assert "frame" in waoc["celestial"][2] assert "unit" in waoc["celestial"][2] assert isinstance(waoc["celestial"][2]["frame"], coord.ICRS) @@ -239,8 +239,8 @@ def test_world_axis_object_classes_2d_generic(gwcs_2d_quantity_shift): waoc = gwcs_2d_quantity_shift.world_axis_object_classes assert waoc["SPATIAL"][0] is u.Quantity assert waoc["SPATIAL1"][0] is u.Quantity - assert waoc["SPATIAL"][1] == tuple() - assert waoc["SPATIAL1"][1] == tuple() + assert waoc["SPATIAL"][1] == () + assert waoc["SPATIAL1"][1] == () assert "unit" in waoc["SPATIAL"][2] assert "unit" in waoc["SPATIAL1"][2] assert waoc["SPATIAL"][2]["unit"] == u.km @@ -250,7 +250,7 @@ def test_world_axis_object_classes_2d_generic(gwcs_2d_quantity_shift): def test_world_axis_object_classes_4d(gwcs_4d_identity_units): waoc = gwcs_4d_identity_units.world_axis_object_classes assert waoc["celestial"][0] is coord.SkyCoord - assert waoc["celestial"][1] == tuple() + assert waoc["celestial"][1] == () assert "frame" in waoc["celestial"][2] assert "unit" in waoc["celestial"][2] assert isinstance(waoc["celestial"][2]["frame"], coord.ICRS) @@ -258,7 +258,7 @@ def test_world_axis_object_classes_4d(gwcs_4d_identity_units): temporal = waoc["temporal"] assert temporal[0] is time.Time - assert temporal[1] == tuple() + assert temporal[1] == () assert temporal[2] == { "unit": u.s, "format": "isot", @@ -612,7 +612,7 @@ def test_world_axis_object_components_units(gwcs_3d_identity_units): world[1].to_value(wcs.output_frame.unit[2]), ] - assert not any([isinstance(o, u.Quantity) for o in values]) + assert not any(isinstance(o, u.Quantity) for o in values) np.testing.assert_allclose(values, expected_values) diff --git a/gwcs/tests/test_api_slicing.py b/gwcs/tests/test_api_slicing.py index 71e306ea..da0d004e 100644 --- a/gwcs/tests/test_api_slicing.py +++ b/gwcs/tests/test_api_slicing.py @@ -59,7 +59,7 @@ def test_ellipsis(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () @@ -125,7 +125,7 @@ def test_spectral_slice(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () @@ -197,7 +197,7 @@ def test_spectral_range(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () @@ -271,7 +271,7 @@ def test_celestial_slice(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () @@ -347,7 +347,7 @@ def test_celestial_range(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () @@ -427,7 +427,7 @@ def test_no_array_shape(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () @@ -507,7 +507,7 @@ def test_ellipsis_none_types(gwcs_3d_galactic_spectral): last_one = [c[2] for c in wcs.world_axis_object_components] assert first_two == [("celestial", 1), ("spectral", 0), ("celestial", 0)] - assert all([callable(last) for last in last_one]) + assert all(callable(last) for last in last_one) assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord assert wcs.world_axis_object_classes["celestial"][1] == () diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index 99183bf6..631d7300 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -159,11 +159,9 @@ def test_spherical_to_cartesian_mixed_Q(spher_to_cart, unit1, unit2): @pytest.mark.parametrize( ("x", "y", "z"), sorted( - list( - set( - tuple(permutations([1 * u.m, 1, 1])) - + tuple(permutations([1 * u.m, 1 * u.m, 1])) - ) + set( + tuple(permutations([1 * u.m, 1, 1])) + + tuple(permutations([1 * u.m, 1 * u.m, 1])) ), key=str, ), diff --git a/gwcs/tests/test_region.py b/gwcs/tests/test_region.py index 94ed9c57..baa655f7 100644 --- a/gwcs/tests/test_region.py +++ b/gwcs/tests/test_region.py @@ -134,7 +134,7 @@ def create_scalar_mapper(): m.append(models.Polynomial2D(2, c0_0=c0_0, c1_0=c1_0, c0_1=c0_1, c1_1=c1_1)) keys = [-1.95805483, -1.67833272, -1.39861060, -1.11888848, -8.39166358] - return {k: v for k, v in zip(keys, m, strict=False)} + return dict(zip(keys, m, strict=False)) def test_LabelMapperDict(): diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 3b120a83..5fe9dfc4 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1658,7 +1658,7 @@ def _order_clockwise(v): else: bb = bounding_box - all_spatial = all([t.lower() == "spatial" for t in self.output_frame.axes_type]) + all_spatial = all(t.lower() == "spatial" for t in self.output_frame.axes_type) if self.output_frame.naxes == 1: if isinstance(bb[0], u.Quantity): bb = np.asarray([b.value for b in bb]) * bb[0].unit diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index efb87aba..e101a374 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -89,7 +89,7 @@ def wcs_from_fiducial( raise TypeError(msg) from err trans_from_fiducial.append(model) fiducial_transform = functools.reduce( - lambda x, y: x & y, [tr for tr in trans_from_fiducial] + lambda x, y: x & y, list(trans_from_fiducial) ) else: # The case of one coordinate frame with more than 1 axes. diff --git a/pyproject.toml b/pyproject.toml index f3cf861d..68209e31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,7 +125,7 @@ select = [ "BLE", # flake8-blind-except (prevent blind except statements) "B", # flake8-bugbear (prevent common gotcha bugs) "A", # flake8-builtins (prevent shadowing of builtins) - # "C4", # flake8-comprehensions (best practices for comprehensions) + "C4", # flake8-comprehensions (best practices for comprehensions) "T10", # flake8-debugger (prevent debugger statements in code) "EM", # flake8-errormessages (best practices for error messages) # "FA", # flake8-future-annotations (correct usage future annotations) From 61a705c51c1a69443351ac0d24344395208aaaac Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 15:46:27 -0500 Subject: [PATCH 20/31] Add print and assert linting --- convert_schemas.py | 2 +- docs/conf.py | 2 +- gwcs/tests/test_bounding_box.py | 1 - gwcs/tests/test_coordinate_systems.py | 1 - gwcs/wcs.py | 19 +++++++++++-------- gwcs/wcstools.py | 4 +++- pyproject.toml | 11 +++++++---- 7 files changed, 23 insertions(+), 17 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index 4e40118f..79cfbd81 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -30,7 +30,7 @@ def write_if_different(filename, data): original_data = None if original_data != data: - print(f"Converting schema {os.path.basename(filename)}") + print(f"Converting schema {os.path.basename(filename)}") # noqa: T201 with open(filename, "wb") as fd: fd.write(data) diff --git a/docs/conf.py b/docs/conf.py index 603e0e54..39a573f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,7 +38,7 @@ try: from sphinx_astropy.conf.v1 import * # noqa except ImportError: - print( + print( # noqa: T201 "ERROR: the documentation requires the sphinx-astropy package to be installed" ) sys.exit(1) diff --git a/gwcs/tests/test_bounding_box.py b/gwcs/tests/test_bounding_box.py index 51130063..4566eae4 100644 --- a/gwcs/tests/test_bounding_box.py +++ b/gwcs/tests/test_bounding_box.py @@ -58,7 +58,6 @@ def test_2d_spatial_coordinate_reordered(gwcs_2d_spatial_reordered, input_, outp def test_1d_freq(gwcs_1d_freq, input_, output): w = gwcs_1d_freq w.bounding_box = (-0.5, 21) - print(f"input {input_}, {output}") assert_array_equal(w.invert(w(input_)), output) assert_array_equal(w.world_to_pixel_values(w.pixel_to_world_values(input_)), output) assert_array_equal(w.world_to_pixel(w.pixel_to_world(input_)), output) diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index 0d25b117..5e521c0d 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -581,7 +581,6 @@ def test_celestial_ordering(): def test_composite_ordering(): - print("boo") c1 = cf.CelestialFrame( reference_frame=coord.ICRS(), axes_order=(1, 0), diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 5fe9dfc4..41c29a78 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1,6 +1,7 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst import functools import itertools +import sys import warnings import astropy.io.fits as fits @@ -2091,7 +2092,7 @@ def _to_fits_sip( # The fitting section. if verbose: - print("\nFitting forward SIP ...") + sys.stdout.write("\nFitting forward SIP ...") fit_poly_x, fit_poly_y, max_resid = _fit_2D_poly( degree, max_pix_error, @@ -2122,7 +2123,7 @@ def _to_fits_sip( if max_inv_pix_error: if verbose: - print("\nFitting inverse SIP ...") + sys.stdout.write("\nFitting inverse SIP ...") fit_inv_poly_u, fit_inv_poly_v, max_inv_resid = _fit_2D_poly( inv_degree, max_inv_pix_error, @@ -2150,7 +2151,9 @@ def _to_fits_sip( hdr.insert(0, ("NAXIS", 2, "number of array dimensions")) hdr.insert(1, (f"NAXIS{iax1:d}", int(xmax) + 1)) hdr.insert(2, (f"NAXIS{iax2:d}", int(ymax) + 1)) - assert len(hdr["NAXIS*"]) == 3 + if len(hdr["NAXIS*"]) != 3: + msg = "NAXIS* should have 3 axes" + raise ValueError(msg) # list of celestial axes related keywords: cel_kwd = ["CRVAL", "CTYPE", "CUNIT"] @@ -3238,7 +3241,7 @@ def _fit_2D_poly( fit_error = np.inf if verbose and not single_degree: - print(f"Maximum specified SIP approximation error: {max_error}") + sys.stdout.write(f"Maximum specified SIP approximation error: {max_error}") max_error *= plate_scale fit_warning_msg = "Failed to achieve requested SIP approximation accuracy." @@ -3251,7 +3254,7 @@ def _fit_2D_poly( xin, yin, xout, yout, degree=deg, coord_pow=coord_pow ) if verbose and not single_degree: - print( + sys.stdout.write( f" - SIP degree: {deg}. " f"Maximum residual: {fit_error_i / plate_scale:.5g}" ) @@ -3305,7 +3308,7 @@ def _fit_2D_poly( xoutd, youtd, fit_poly_x(xind, yind), fit_poly_y(xind, yind) ) if verbose: - print( + sys.stdout.write( "* Maximum residual, double sampled grid: " f"{max_resid / plate_scale:.5g}" ) @@ -3324,9 +3327,9 @@ def _fit_2D_poly( if verbose: if single_degree: - print(f"Maximum residual: {fit_error / plate_scale:.5g}") + sys.stdout.write(f"Maximum residual: {fit_error / plate_scale:.5g}") else: - print( + sys.stdout.write( f"* Final SIP degree: {deg}. " f"Maximum residual: {fit_error / plate_scale:.5g}" ) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index e101a374..302f8762 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -335,7 +335,9 @@ def wcs_from_points( lon, lat = unit_sph.lon.deg, unit_sph.lat.deg if isinstance(proj_point, coord.SkyCoord): - assert proj_point.size == 1 + if proj_point.size != 1: + msg = "proj_point must be a SkyCoord object with a single point." + raise ValueError(msg) proj_point.transform_to(world_coords) crval = (proj_point.data.lon, proj_point.data.lat) frame = proj_point.frame diff --git a/pyproject.toml b/pyproject.toml index 68209e31..af8b1f1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,12 +128,11 @@ select = [ "C4", # flake8-comprehensions (best practices for comprehensions) "T10", # flake8-debugger (prevent debugger statements in code) "EM", # flake8-errormessages (best practices for error messages) - # "FA", # flake8-future-annotations (correct usage future annotations) "ISC", # flake8-implicit-str-concat (prevent implicit string concat) "ICN", # flake8-import-conventions (enforce import conventions) - # "INP", # flake8-no-pep420 (prevent use of PEP420, i.e. implicit name spaces) + "INP", # flake8-no-pep420 (prevent use of PEP420, i.e. implicit name spaces) "PIE", # flake8-pie (misc suggested improvement linting) - # "T20", # flake8-print (prevent print statements in code) + "T20", # flake8-print (prevent print statements in code) "PT", # flake8-pytest-style (best practices for pytest) "Q", # flake8-quotes (best practices for quotes) "RSE", # flake8-raise (best practices for raising exceptions) @@ -155,6 +154,10 @@ select = [ "RUF", # ruff specific checks ] ignore = [ - "S101", # Bandit: Use of assert detected "ISC001", # conflicts with formatter at times ] + +[tool.ruff.lint.extend-per-file-ignores] +"gwcs/tests/*" = ["S101"] +"gwcs/converters/tests/*" = ["S101"] +"docs/conf.py" = ["INP001"] From 12ed74a4c8397654d117fe08434814eae872f817 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 15:59:26 -0500 Subject: [PATCH 21/31] Add import linting --- gwcs/converters/geometry.py | 8 ++++---- gwcs/converters/selector.py | 6 +++--- gwcs/converters/spectroscopy.py | 10 +++++----- gwcs/converters/tests/test_selector.py | 4 ++-- gwcs/converters/tests/test_transforms.py | 4 ++-- gwcs/converters/tests/test_wcs.py | 20 ++++++++------------ gwcs/converters/wcs.py | 20 ++++++++++---------- gwcs/tests/conftest.py | 2 +- gwcs/tests/test_coordinate_systems.py | 4 ++-- gwcs/tests/test_geometry.py | 2 +- gwcs/tests/test_spectroscopy_models.py | 4 ++-- gwcs/tests/test_utils.py | 5 +++-- gwcs/tests/utils.py | 4 ++-- pyproject.toml | 2 +- 14 files changed, 46 insertions(+), 49 deletions(-) diff --git a/gwcs/converters/geometry.py b/gwcs/converters/geometry.py index ab7c571f..084b906b 100644 --- a/gwcs/converters/geometry.py +++ b/gwcs/converters/geometry.py @@ -16,7 +16,7 @@ class DirectionCosinesConverter(TransformConverterBase): ) def from_yaml_tree_transform(self, node, tag, ctx): - from ..geometry import FromDirectionCosines, ToDirectionCosines + from gwcs.geometry import FromDirectionCosines, ToDirectionCosines transform_type = node["transform_type"] if transform_type == "to_direction_cosines": @@ -27,7 +27,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): raise TypeError(msg) def to_yaml_tree_transform(self, model, tag, ctx): - from ..geometry import FromDirectionCosines, ToDirectionCosines + from gwcs.geometry import FromDirectionCosines, ToDirectionCosines if isinstance(model, FromDirectionCosines): transform_type = "from_direction_cosines" @@ -47,7 +47,7 @@ class SphericalCartesianConverter(TransformConverterBase): ) def from_yaml_tree_transform(self, node, tag, ctx): - from ..geometry import CartesianToSpherical, SphericalToCartesian + from gwcs.geometry import CartesianToSpherical, SphericalToCartesian transform_type = node["transform_type"] wrap_lon_at = node["wrap_lon_at"] @@ -59,7 +59,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): raise TypeError(msg) def to_yaml_tree_transform(self, model, tag, ctx): - from ..geometry import CartesianToSpherical, SphericalToCartesian + from gwcs.geometry import CartesianToSpherical, SphericalToCartesian if isinstance(model, SphericalToCartesian): transform_type = "spherical_to_cartesian" diff --git a/gwcs/converters/selector.py b/gwcs/converters/selector.py index 625b88d0..c6603b46 100644 --- a/gwcs/converters/selector.py +++ b/gwcs/converters/selector.py @@ -22,7 +22,7 @@ class LabelMapperConverter(TransformConverterBase): ) def from_yaml_tree_transform(self, node, tag, ctx): - from ..selector import ( + from gwcs.selector import ( LabelMapper, LabelMapperArray, LabelMapperDict, @@ -65,7 +65,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): return LabelMapperDict(inputs, dict_mapper, inputs_mapping, atol=atol) def to_yaml_tree_transform(self, model, tag, ctx): - from ..selector import ( + from gwcs.selector import ( LabelMapper, LabelMapperArray, LabelMapperDict, @@ -107,7 +107,7 @@ class RegionsSelectorConverter(TransformConverterBase): types = ("gwcs.selector.RegionsSelector",) def from_yaml_tree_transform(self, node, tag, ctx): - from ..selector import RegionsSelector + from gwcs.selector import RegionsSelector inputs = node["inputs"] outputs = node["outputs"] diff --git a/gwcs/converters/spectroscopy.py b/gwcs/converters/spectroscopy.py index 51601443..b5ef9f1e 100644 --- a/gwcs/converters/spectroscopy.py +++ b/gwcs/converters/spectroscopy.py @@ -22,7 +22,7 @@ class SellmeierGlassConverter(TransformConverterBase): types = ("gwcs.spectroscopy.SellmeierGlass",) def from_yaml_tree_transform(self, node, tag, ctx): - from ..spectroscopy import SellmeierGlass + from gwcs.spectroscopy import SellmeierGlass return SellmeierGlass(node["B_coef"], node["C_coef"]) @@ -38,7 +38,7 @@ class SellmeierZemaxConverter(TransformConverterBase): types = ("gwcs.spectroscopy.SellmeierZemax",) def from_yaml_tree_transform(self, node, tag, ctx): - from ..spectroscopy import SellmeierZemax + from gwcs.spectroscopy import SellmeierZemax return SellmeierZemax( node["temperature"], @@ -69,7 +69,7 @@ class Snell3DConverter(TransformConverterBase): types = ("gwcs.spectroscopy.Snell3D",) def from_yaml_tree_transform(self, node, tag, ctx): - from ..spectroscopy import Snell3D + from gwcs.spectroscopy import Snell3D return Snell3D() @@ -85,7 +85,7 @@ class GratingEquationConverter(TransformConverterBase): ) def from_yaml_tree_transform(self, node, tag, ctx): - from ..spectroscopy import ( + from gwcs.spectroscopy import ( AnglesFromGratingEquation3D, WavelengthFromGratingEquation, ) @@ -107,7 +107,7 @@ def from_yaml_tree_transform(self, node, tag, ctx): return model def to_yaml_tree_transform(self, model, tag, ctx): - from ..spectroscopy import ( + from gwcs.spectroscopy import ( AnglesFromGratingEquation3D, WavelengthFromGratingEquation, ) diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index cf9b9382..b0e6b2c3 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -4,8 +4,8 @@ from astropy.modeling.models import Mapping, Polynomial2D, Scale, Shift from numpy.testing import assert_array_equal -from ... import selector -from ...tests.test_region import create_scalar_mapper +from gwcs import selector +from gwcs.tests.test_region import create_scalar_mapper def _assert_mapper_equal(a, b): diff --git a/gwcs/converters/tests/test_transforms.py b/gwcs/converters/tests/test_transforms.py index 3fb49832..4def9f03 100644 --- a/gwcs/converters/tests/test_transforms.py +++ b/gwcs/converters/tests/test_transforms.py @@ -10,8 +10,8 @@ assert_model_roundtrip, ) -from ... import geometry -from ... import spectroscopy as sp +from gwcs import geometry +from gwcs import spectroscopy as sp sell_glass = sp.SellmeierGlass( B_coef=[0.58339748, 0.46085267, 3.8915394], diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index ca86d962..a389f243 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -1,21 +1,17 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst import os.path -import pytest - -astropy = pytest.importorskip("astropy", minversion="3.0") - -import asdf # noqa: E402 -from asdf_astropy.testing.helpers import ( # noqa: E402 +import asdf +from asdf_astropy.testing.helpers import ( assert_model_equal, ) -from astropy import coordinates as coord # noqa: E402 -from astropy import time # noqa: E402 -from astropy import units as u # noqa: E402 -from astropy.modeling import models # noqa: E402 +from astropy import coordinates as coord +from astropy import time +from astropy import units as u +from astropy.modeling import models -from ... import coordinate_frames as cf # noqa: E402 -from ... import wcs # noqa: E402 +from gwcs import coordinate_frames as cf +from gwcs import wcs def _assert_frame_equal(a, b): diff --git a/gwcs/converters/wcs.py b/gwcs/converters/wcs.py index 122335af..42dcd380 100644 --- a/gwcs/converters/wcs.py +++ b/gwcs/converters/wcs.py @@ -22,7 +22,7 @@ class WCSConverter(Converter): types = ("gwcs.wcs.WCS",) def from_yaml_tree(self, node, tag, ctx): - from ..wcs import WCS, GwcsBoundingBoxWarning + from gwcs.wcs import WCS, GwcsBoundingBoxWarning gwcsobj = WCS(node["steps"], name=node["name"]) if "pixel_shape" in node: @@ -49,7 +49,7 @@ class StepConverter(Converter): types = ("gwcs.wcs.Step",) def from_yaml_tree(self, node, tag, ctx): - from ..wcs import Step + from gwcs.wcs import Step return Step(frame=node["frame"], transform=node.get("transform", None)) @@ -85,7 +85,7 @@ def _from_yaml_tree(self, node, tag, ctx): return kwargs def _to_yaml_tree(self, frame, tag, ctx): - from ..coordinate_frames import CoordinateFrame + from gwcs.coordinate_frames import CoordinateFrame node = {} @@ -114,7 +114,7 @@ def _to_yaml_tree(self, frame, tag, ctx): return node def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import CoordinateFrame + from gwcs.coordinate_frames import CoordinateFrame node = self._from_yaml_tree(node, tag, ctx) return CoordinateFrame(**node) @@ -128,7 +128,7 @@ class Frame2DConverter(FrameConverter): types = ("gwcs.coordinate_frames.Frame2D",) def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import Frame2D + from gwcs.coordinate_frames import Frame2D node = self._from_yaml_tree(node, tag, ctx) return Frame2D(**node) @@ -139,7 +139,7 @@ class CelestialFrameConverter(FrameConverter): types = ("gwcs.coordinate_frames.CelestialFrame",) def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import CelestialFrame + from gwcs.coordinate_frames import CelestialFrame node = self._from_yaml_tree(node, tag, ctx) return CelestialFrame(**node) @@ -150,7 +150,7 @@ class SpectralFrameConverter(FrameConverter): types = ("gwcs.coordinate_frames.SpectralFrame",) def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import SpectralFrame + from gwcs.coordinate_frames import SpectralFrame node = self._from_yaml_tree(node, tag, ctx) @@ -162,7 +162,7 @@ class CompositeFrameConverter(FrameConverter): types = ("gwcs.coordinate_frames.CompositeFrame",) def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import CompositeFrame + from gwcs.coordinate_frames import CompositeFrame if len(node) != 2: msg = "CompositeFrame has extra properties" @@ -182,7 +182,7 @@ class TemporalFrameConverter(FrameConverter): types = ("gwcs.coordinate_frames.TemporalFrame",) def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import TemporalFrame + from gwcs.coordinate_frames import TemporalFrame node = self._from_yaml_tree(node, tag, ctx) return TemporalFrame(**node) @@ -193,7 +193,7 @@ class StokesFrameConverter(FrameConverter): types = ("gwcs.coordinate_frames.StokesFrame",) def from_yaml_tree(self, node, tag, ctx): - from ..coordinate_frames import StokesFrame + from gwcs.coordinate_frames import StokesFrame node = self._from_yaml_tree(node, tag, ctx) return StokesFrame(**node) diff --git a/gwcs/tests/conftest.py b/gwcs/tests/conftest.py index 87d97c99..1a0a3f45 100644 --- a/gwcs/tests/conftest.py +++ b/gwcs/tests/conftest.py @@ -4,7 +4,7 @@ import pytest -from .. import examples, geometry +from gwcs import examples, geometry @pytest.fixture diff --git a/gwcs/tests/test_coordinate_systems.py b/gwcs/tests/test_coordinate_systems.py index 5e521c0d..fd6cf834 100644 --- a/gwcs/tests/test_coordinate_systems.py +++ b/gwcs/tests/test_coordinate_systems.py @@ -14,8 +14,8 @@ from astropy.wcs.wcsapi.fitswcs import CTYPE_TO_UCD1 from numpy.testing import assert_allclose -from .. import WCS -from .. import coordinate_frames as cf +from gwcs import WCS +from gwcs import coordinate_frames as cf astropy_version = astropy.__version__ diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index 631d7300..fcb76820 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -14,7 +14,7 @@ assert_model_roundtrip, ) -from .. import geometry +from gwcs import geometry _INV_SQRT2 = 1.0 / np.sqrt(2.0) diff --git a/gwcs/tests/test_spectroscopy_models.py b/gwcs/tests/test_spectroscopy_models.py index daea4244..cad302eb 100644 --- a/gwcs/tests/test_spectroscopy_models.py +++ b/gwcs/tests/test_spectroscopy_models.py @@ -4,8 +4,8 @@ from astropy.modeling.models import Identity from numpy.testing import assert_allclose -from .. import geometry -from .. import spectroscopy as sp +from gwcs import geometry +from gwcs import spectroscopy as sp def test_angles_grating_equation(): diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index 106c478f..3f6648a9 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -11,8 +11,9 @@ from astropy.tests.helper import assert_quantity_allclose from numpy.testing import assert_allclose -from .. import utils as gwutils -from ..utils import UnsupportedProjectionError +from gwcs import utils as gwutils +from gwcs.utils import UnsupportedProjectionError + from . import data data_path = os.path.split(os.path.abspath(data.__file__))[0] diff --git a/gwcs/tests/utils.py b/gwcs/tests/utils.py index 60878b28..7cdb317c 100644 --- a/gwcs/tests/utils.py +++ b/gwcs/tests/utils.py @@ -10,8 +10,8 @@ Shift, ) -from .. import coordinate_frames as cf -from ..wcs import WCS +from gwcs import coordinate_frames as cf +from gwcs.wcs import WCS def _gwcs_from_hst_fits_wcs(header, hdu=None): diff --git a/pyproject.toml b/pyproject.toml index af8b1f1c..b7837d26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,7 +138,7 @@ select = [ "RSE", # flake8-raise (best practices for raising exceptions) "RET", # flake8-return (best practices for return statements) "SIM", # flake8-simplify (suggest simplifications to code where possible) - # "TID", # flake8-tidy-imports (prevent banned api and best import practices) + "TID", # flake8-tidy-imports (prevent banned api and best import practices) # "TCH", # flake8-type-checking (move type checking imports into type checking blocks) # "INT", # flake8-gettext (when to use printf style strings) # "ARG", # flake8-unused-arguments (prevent unused arguments) From 796197894a5b4f711f681ef986db50aa9c0a6f53 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 16:03:08 -0500 Subject: [PATCH 22/31] Add commented code linting --- gwcs/examples.py | 1 - gwcs/tests/test_wcs.py | 6 +----- gwcs/utils.py | 7 ------- gwcs/wcs.py | 9 --------- gwcs/wcstools.py | 1 - pyproject.toml | 7 +++---- 6 files changed, 4 insertions(+), 27 deletions(-) diff --git a/gwcs/examples.py b/gwcs/examples.py index ef9fbc61..8a2d505a 100644 --- a/gwcs/examples.py +++ b/gwcs/examples.py @@ -611,7 +611,6 @@ def gwcs_7d_complex_mapping(): unit=(u.pix, u.pix, u.pix, u.pix, u.pix, u.pix), ) - # pipeline = [('detector', wcs_forward), (comp_frm, None)] w = wcs.WCS( forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame ) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index e872d5b5..4b586783 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -1575,7 +1575,7 @@ def test_split_frame_wcs(): # that we are pretending that this model is ordered lon, lat because that's # what the projections require in astropy. - # Input is (lat, wave, lon) + # Input is lat, wave, lon # lat: multiply by 20 arcsec, lon: multiply by 15 deg # result should be 20 arcsec, 10nm, 45 deg spatial = models.Multiply(20 * u.arcsec / u.pix) & models.Multiply( @@ -1592,11 +1592,8 @@ def test_split_frame_wcs(): reference_frame=coord.ICRS(), axes_names=("lon", "lat"), ) - # celestial_frame = cf.CelestialFrame(axes_order=(2, 0), unit=(u.arcsec, u.deg), - # reference_frame=coord.ICRS()) spectral_frame = cf.SpectralFrame(axes_order=(1,), unit=u.nm, axes_names="wave") output_frame = cf.CompositeFrame([spectral_frame, celestial_frame]) - # output_frame = cf.CompositeFrame([celestial_frame, spectral_frame]) input_frame = cf.CoordinateFrame( 3, ["PIXEL"] * 3, axes_order=list(range(3)), unit=[u.pix] * 3 @@ -1609,7 +1606,6 @@ def test_split_frame_wcs(): assert_allclose(output_pixel, u.Quantity(input_pixel).to_value(u.pix)) expected_world = [20 * u.arcsec, 10 * u.nm, 45 * u.deg] - # expected_world = [15*u.deg, 20*u.nm, 60*u.arcsec] for expected, output in zip(expected_world, output_world, strict=False): assert_allclose(output, expected.value) diff --git a/gwcs/utils.py b/gwcs/utils.py index 14bc2576..d07772e8 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -192,8 +192,6 @@ def read_wcs_from_header(header): wcs_info["RADESYS"] = header.get("RADESYS", "ICRS") wcs_info["VAFACTOR"] = header.get("VAFACTOR", 1) wcs_info["NAXIS"] = header.get("NAXIS", 0) - # date keyword? - # wcs_info['DATEOBS'] = header.get('DATE-OBS', 'DATEOBS') wcs_info["EQUINOX"] = header.get("EQUINOX", None) wcs_info["EPOCH"] = header.get("EPOCH", None) wcs_info["DATEOBS"] = header.get("MJD-OBS", header.get("DATE-OBS", None)) @@ -392,11 +390,6 @@ def fitswcs_linear(header): # if wcsaxes == 2: rotation = astmodels.AffineTransformation2D(matrix=pc, name="pc_matrix") - # elif wcsaxes == 3 : - # rotation = AffineTransformation3D(matrix=matrix) - # else: - # raise DimensionsError("WCSLinearTransform supports only 2 or 3 dimensions, " - # "{0} given".format(wcsaxes)) translation_models = [ astmodels.Shift(-(shift - 1), name="crpix" + str(i + 1)) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 41c29a78..eba04ac0 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -1467,7 +1467,6 @@ def unit(self): """The unit of the coordinates in the output coordinate system.""" if self._pipeline: try: - # return getattr(self, self._pipeline[-1][0].name).unit return self._pipeline[-1].frame.unit except AttributeError: return None @@ -2030,8 +2029,6 @@ def _to_fits_sip( k: bb_center[k] for k in set(range(self.pixel_n_dim)).difference(input_axes) } - # transform = fix_inputs(self.forward_transform, fixi_dict) - # This is a workaround to the bug in https://github.com/astropy/astropy/issues/11360 # Once that bug is fixed, the code below can be replaced with fix_inputs # statement commented out immediately above. transform = _fix_transform_inputs(self.forward_transform, fixi_dict) @@ -3014,12 +3011,6 @@ def _to_fits_tab( hdr[f"PC{k1:d}_{m1:d}"] = 1.0 hdr[f"CDELT{k1:d}"] = 1 - # Uncomment 3 lines below to enable use of degenerate axes: - # hdr['NAXIS'] = hdr['NAXIS'] + 1 - # naxisi_max = max(int(k[5:]) for k in hdr['naxis*'] if k[5:].strip()) - # hdr.insert(f'NAXIS{naxisi_max:d}', (f'NAXIS{m1:d}', 1), after=True) - # NOTE: in this case make sure NAXIS=WCSAXES - coord = coord[None, :] # structured array (data) for binary table HDU: diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 302f8762..20445b6e 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -69,7 +69,6 @@ def wcs_from_fiducial( msg = "Expected transform to be an instanceof astropy.modeling.Model" raise UnsupportedTransformError(msg) - # transform_outputs = transform.n_outputs if isinstance(fiducial, coord.SkyCoord): coordinate_frame = CelestialFrame( reference_frame=fiducial.frame, diff --git a/pyproject.toml b/pyproject.toml index b7837d26..540ab12c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -139,11 +139,10 @@ select = [ "RET", # flake8-return (best practices for return statements) "SIM", # flake8-simplify (suggest simplifications to code where possible) "TID", # flake8-tidy-imports (prevent banned api and best import practices) - # "TCH", # flake8-type-checking (move type checking imports into type checking blocks) - # "INT", # flake8-gettext (when to use printf style strings) + "INT", # flake8-gettext (when to use printf style strings) # "ARG", # flake8-unused-arguments (prevent unused arguments) #"PTH", # flake8-use-pathlib (prefer pathlib over os.path) - # "ERA", # eradicate (remove commented out code) + "ERA", # eradicate (remove commented out code) # "PGH", # pygrep (simple grep checks) #"PL", # pylint (general linting, flake8 alternative) "TRY", # tryceratops (linting for try/except blocks) @@ -160,4 +159,4 @@ ignore = [ [tool.ruff.lint.extend-per-file-ignores] "gwcs/tests/*" = ["S101"] "gwcs/converters/tests/*" = ["S101"] -"docs/conf.py" = ["INP001"] +"docs/conf.py" = ["INP001", "ERA001"] From 546d0f6c55ecfc670e5c9027fff92d8315e0c3f4 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 16:05:19 -0500 Subject: [PATCH 23/31] blanket noqa linting --- docs/conf.py | 2 +- gwcs/__init__.py | 8 ++++---- pyproject.toml | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 39a573f9..996a25d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,7 +36,7 @@ import importlib.metadata try: - from sphinx_astropy.conf.v1 import * # noqa + from sphinx_astropy.conf.v1 import * # noqa: F403 except ImportError: print( # noqa: T201 "ERROR: the documentation requires the sphinx-astropy package to be installed" diff --git a/gwcs/__init__.py b/gwcs/__init__.py index cf3cda64..651e178b 100644 --- a/gwcs/__init__.py +++ b/gwcs/__init__.py @@ -63,7 +63,7 @@ with contextlib.suppress(importlib.metadata.PackageNotFoundError): __version__ = importlib.metadata.version(__name__) -from .wcs import * # noqa -from .wcstools import * # noqa -from .coordinate_frames import * # noqa -from .selector import * # noqa +from .coordinate_frames import * # noqa: F403 +from .selector import * # noqa: F403 +from .wcs import * # noqa: F403 +from .wcstools import * # noqa: F403 diff --git a/pyproject.toml b/pyproject.toml index 540ab12c..3e8ba2a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,10 +140,9 @@ select = [ "SIM", # flake8-simplify (suggest simplifications to code where possible) "TID", # flake8-tidy-imports (prevent banned api and best import practices) "INT", # flake8-gettext (when to use printf style strings) - # "ARG", # flake8-unused-arguments (prevent unused arguments) #"PTH", # flake8-use-pathlib (prefer pathlib over os.path) "ERA", # eradicate (remove commented out code) - # "PGH", # pygrep (simple grep checks) + "PGH", # pygrep (simple grep checks) #"PL", # pylint (general linting, flake8 alternative) "TRY", # tryceratops (linting for try/except blocks) "FLY", # flynt (f-string conversion where possible) From 2e963fb8f4c86074fa47960af32d06175e3cb62a Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 16:17:58 -0500 Subject: [PATCH 24/31] Add pylint linting --- convert_schemas.py | 7 +++---- gwcs/api.py | 6 +----- gwcs/coordinate_frames.py | 8 +++---- gwcs/region.py | 6 ++---- gwcs/utils.py | 7 +++---- gwcs/wcs.py | 44 +++++++++++++++++++-------------------- pyproject.toml | 6 +++++- 7 files changed, 38 insertions(+), 46 deletions(-) diff --git a/convert_schemas.py b/convert_schemas.py index 79cfbd81..a811a72c 100644 --- a/convert_schemas.py +++ b/convert_schemas.py @@ -272,10 +272,9 @@ def recurse(o, name, schema, path, level, required=False): o.write(f".. _{os.path.join(*path)}:\n\n") if level == 0: write_header(o, name, level) - else: - if name != "items": - o.write(indent) - o.write(f":entry:`{name}`\n\n") + elif name != "items": + o.write(indent) + o.write(f":entry:`{name}`\n\n") o.write(indent) if path[0].startswith("tag:stsci.edu:asdf"): diff --git a/gwcs/api.py b/gwcs/api.py index 5529a4fa..f66a3fa6 100644 --- a/gwcs/api.py +++ b/gwcs/api.py @@ -184,11 +184,7 @@ def pixel_bounds(self): # Iterate over the bounding box and convert from quantity if required. bounding_box = list(bounding_box) for i, bb_axes in enumerate(bounding_box): - bb = [] - for lim in bb_axes: - if isinstance(lim, u.Quantity): - lim = lim.value - bb.append(lim) + bb = [lim.value if isinstance(lim, u.Quantity) else lim for lim in bb_axes] bounding_box[i] = tuple(bb) diff --git a/gwcs/coordinate_frames.py b/gwcs/coordinate_frames.py index dc508bb7..db71300d 100644 --- a/gwcs/coordinate_frames.py +++ b/gwcs/coordinate_frames.py @@ -948,8 +948,8 @@ def _wao_renamed_components_iter(self): mapper = self._wao_classes_rename_map for frame in self.frames: renamed_components = [] - for comp in frame._native_world_axis_object_components: - comp = list(comp) + for component in frame._native_world_axis_object_components: + comp = list(component) rename = mapper[frame].get(comp[0]) if rename: comp[0] = rename @@ -962,9 +962,7 @@ def _wao_renamed_classes_iter(self): for frame in self.frames: for key, value in frame.world_axis_object_classes.items(): rename = mapper[frame].get(key) - if rename: - key = rename - yield key, value + yield rename if rename else key, value @property def world_axis_object_components(self): diff --git a/gwcs/region.py b/gwcs/region.py index dc91560c..a630fa76 100644 --- a/gwcs/region.py +++ b/gwcs/region.py @@ -106,10 +106,8 @@ def __init__(self, rid, vertices, coord_system="Cartesian"): self._shifty = 0 for vertex in vertices: x, y = vertex - if x < self._shiftx: - self._shiftx = x - if y < self._shifty: - self._shifty = y + self._shiftx = min(x, self._shiftx) + self._shifty = min(y, self._shifty) v = [(i - self._shiftx, j - self._shifty) for i, j in vertices] # convert to integer coordinates: diff --git a/gwcs/utils.py b/gwcs/utils.py index d07772e8..58f261ce 100644 --- a/gwcs/utils.py +++ b/gwcs/utils.py @@ -218,11 +218,10 @@ def read_wcs_from_header(header): key = f"CD{i}_{j}" if wcs_info["has_cd"] else f"PC{i}_{j}" if key in header: pc[i - 1, j - 1] = header[key] + elif i == j: + pc[i - 1, j - 1] = 1.0 else: - if i == j: - pc[i - 1, j - 1] = 1.0 - else: - pc[i - 1, j - 1] = 0.0 + pc[i - 1, j - 1] = 0.0 wcs_info["CTYPE"] = ctype wcs_info["CUNIT"] = cunit wcs_info["CRPIX"] = crpix diff --git a/gwcs/wcs.py b/gwcs/wcs.py index eba04ac0..728a5d0c 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -4,11 +4,11 @@ import sys import warnings -import astropy.io.fits as fits import astropy.units as u import numpy as np import numpy.linalg as npla from astropy import utils as astutil +from astropy.io import fits from astropy.modeling import fix_inputs, projections from astropy.modeling.bounding_box import CompoundBoundingBox from astropy.modeling.bounding_box import ModelBoundingBox as Bbox @@ -630,10 +630,10 @@ def outside_footprint(self, world_arrays): for axtyp in axes_types: ind = np.asarray(np.asarray(self.output_frame.axes_type) == axtyp) - for idim, (coord, phys) in enumerate( + for idim, (coordinate, phys) in enumerate( zip(world_arrays, axes_phys_types, strict=False) ): - coord = _tofloat(coord) + coord = _tofloat(coordinate) if np.asarray(ind).sum() > 1: axis_range = footprint[:, idim] else: @@ -678,9 +678,9 @@ def out_of_bounds(self, pixel_arrays, fill_value=np.nan): if np.isscalar(pix): pixel_arrays[idim] = np.nan else: - pix = pixel_arrays[idim].astype(float, copy=True) - pix[outside] = np.nan - pixel_arrays[idim] = pix + pix_ = pixel_arrays[idim].astype(float, copy=True) + pix_[outside] = np.nan + pixel_arrays[idim] = pix_ if self.input_frame.naxes == 1: pixel_arrays = pixel_arrays[0] return pixel_arrays @@ -2241,13 +2241,12 @@ def _to_fits_sip( axis_rename[f"{kwd:s}{iold:d}"] = f"{kwd:s}{inew:d}" # construct new header cards with remapped axes: - new_cards = [] - for c in hdr.cards: - if c[0] in axis_rename: - c = fits.Card( - keyword=axis_rename[c.keyword], value=c.value, comment=c.comment - ) - new_cards.append(c) + new_cards = [ + fits.Card(keyword=axis_rename[c.keyword], value=c.value, comment=c.comment) + if c[0] in axis_rename + else c + for c in hdr.cards + ] hdr = fits.Header(new_cards) hdr["WCSAXES"] = 2 @@ -2320,12 +2319,11 @@ def find_frame(axis_number): for frame in frames: if axis_number in frame.axes_order: return frame - else: - msg = ( - "Encountered an output axes that does not " - "belong to any output coordinate frames." - ) - raise RuntimeError(msg) + msg = ( + "Encountered an output axes that does not " + "belong to any output coordinate frames." + ) + raise RuntimeError(msg) # use correlation matrix to find separable axes: corr_mat = self.axis_correlation_matrix @@ -2361,17 +2359,17 @@ def find_frame(axis_number): # Find the frame to which the first axis in the group belongs. # Most likely this frame will be the frame of all other axes in # this group; if not, we will update it later. - s = sorted(s) - frame = find_frame(s[0]) + axis = sorted(s) + frame = find_frame(axis[0]) celestial = ( detect_celestial - and len(s) == 2 + and len(axis) == 2 and len(frame.axes_order) == 2 and isinstance(frame, cf.CelestialFrame) ) - for axno in s: + for axno in axis: if axno not in frame.axes_order: frame = find_frame(axno) celestial = False # Celestial axes must belong to the same frame diff --git a/pyproject.toml b/pyproject.toml index 3e8ba2a7..8dd3de43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,7 +143,7 @@ select = [ #"PTH", # flake8-use-pathlib (prefer pathlib over os.path) "ERA", # eradicate (remove commented out code) "PGH", # pygrep (simple grep checks) - #"PL", # pylint (general linting, flake8 alternative) + "PL", # pylint (general linting, flake8 alternative) "TRY", # tryceratops (linting for try/except blocks) "FLY", # flynt (f-string conversion where possible) "NPY", # NumPy-specific checks (recommendations from NumPy) @@ -153,6 +153,10 @@ select = [ ] ignore = [ "ISC001", # conflicts with formatter at times + "PLR2004", # magic values (this should be dealt with at some point) + "PLR0912", # Too many branches in function + "PLR0913", # Too many arguments in function + "PLR0915", # Too many statements in function ] [tool.ruff.lint.extend-per-file-ignores] From 170e774f5684f5e748f07e3cb364e59e0b51f507 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Thu, 2 Jan 2025 16:32:53 -0500 Subject: [PATCH 25/31] Add pathlib linting --- docs/conf.py | 2 +- gwcs/converters/tests/test_selector.py | 24 ++++++++-------- gwcs/converters/tests/test_transforms.py | 4 +-- gwcs/converters/tests/test_wcs.py | 35 ++++++++++++------------ gwcs/tests/test_geometry.py | 6 ++-- gwcs/tests/test_utils.py | 6 ++-- gwcs/tests/test_wcs.py | 22 +++++++-------- pyproject.toml | 5 ++-- 8 files changed, 51 insertions(+), 53 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 996a25d3..10fbff69 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -75,7 +75,7 @@ # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does -with open(Path(__file__).parent.parent / "pyproject.toml", "rb") as metadata_file: +with (Path(__file__).parent.parent / "pyproject.toml").open("rb") as metadata_file: configuration = tomllib.load(metadata_file) metadata = configuration["project"] project = metadata["name"] diff --git a/gwcs/converters/tests/test_selector.py b/gwcs/converters/tests/test_selector.py index b0e6b2c3..fab5e36f 100644 --- a/gwcs/converters/tests/test_selector.py +++ b/gwcs/converters/tests/test_selector.py @@ -45,12 +45,12 @@ def _assert_selector_equal(a, b): assert_array_equal(a.undefined_transform_value, b.undefined_transform_value) -def assert_selector_roundtrip(s, tmpdir, version=None): +def assert_selector_roundtrip(s, tmp_path, version=None): """ Assert that a selector can be written to an ASDF file and read back in without losing any of its essential properties. """ - path = str(tmpdir / "test.asdf") + path = tmp_path / "test.asdf" with asdf.AsdfFile({"selector": s}, version=version) as af: af.write_to(path) @@ -66,7 +66,7 @@ def assert_selector_roundtrip(s, tmpdir, version=None): raise TypeError(msg) -def test_regions_selector(tmpdir): +def test_regions_selector(tmp_path): m1 = Mapping([0, 1, 1]) | Shift(1) & Shift(2) & Shift(3) m2 = Mapping([0, 1, 1]) | Scale(2) & Scale(3) & Scale(3) sel = {1: m1, 2: m2} @@ -77,32 +77,32 @@ def test_regions_selector(tmpdir): rs = selector.RegionsSelector( inputs=("x", "y"), outputs=("ra", "dec", "lam"), selector=sel, label_mapper=mask ) - assert_selector_roundtrip(rs, tmpdir) + assert_selector_roundtrip(rs, tmp_path) -def test_LabelMapperArray_str(tmpdir): +def test_LabelMapperArray_str(tmp_path): a = np.array( [["label1", "", "label2"], ["label1", "", ""], ["label1", "label2", "label2"]] ) mask = selector.LabelMapperArray(a) - assert_selector_roundtrip(mask, tmpdir) + assert_selector_roundtrip(mask, tmp_path) -def test_labelMapperArray_int(tmpdir): +def test_labelMapperArray_int(tmp_path): a = np.array([[1, 0, 2], [1, 0, 0], [1, 2, 2]]) mask = selector.LabelMapperArray(a) - assert_selector_roundtrip(mask, tmpdir) + assert_selector_roundtrip(mask, tmp_path) -def test_LabelMapperDict(tmpdir): +def test_LabelMapperDict(tmp_path): dmapper = create_scalar_mapper() sel = selector.LabelMapperDict( ("x", "y"), dmapper, inputs_mapping=Mapping((0,), n_inputs=2), atol=1e-3 ) - assert_selector_roundtrip(sel, tmpdir) + assert_selector_roundtrip(sel, tmp_path) -def test_LabelMapperRange(tmpdir): +def test_LabelMapperRange(tmp_path): m = [] for i in np.arange(9) * 0.1: c0_0, c1_0, c0_1, c1_1 = np.ones((4,)) * i @@ -126,4 +126,4 @@ def test_LabelMapperRange(tmpdir): sel = selector.LabelMapperRange( ("x", "y"), rmapper, inputs_mapping=Mapping((0,), n_inputs=2) ) - assert_selector_roundtrip(sel, tmpdir) + assert_selector_roundtrip(sel, tmp_path) diff --git a/gwcs/converters/tests/test_transforms.py b/gwcs/converters/tests/test_transforms.py index 4def9f03..a8aa0aad 100644 --- a/gwcs/converters/tests/test_transforms.py +++ b/gwcs/converters/tests/test_transforms.py @@ -49,5 +49,5 @@ @pytest.mark.parametrize(("model"), transforms) -def test_transforms(tmpdir, model): - assert_model_roundtrip(model, tmpdir) +def test_transforms(tmp_path, model): + assert_model_roundtrip(model, tmp_path) diff --git a/gwcs/converters/tests/test_wcs.py b/gwcs/converters/tests/test_wcs.py index a389f243..4d9e3d36 100644 --- a/gwcs/converters/tests/test_wcs.py +++ b/gwcs/converters/tests/test_wcs.py @@ -1,5 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import os.path import asdf from asdf_astropy.testing.helpers import ( @@ -33,12 +32,12 @@ def _assert_frame_equal(a, b): return None -def assert_frame_roundtrip(frame, tmpdir, version=None): +def assert_frame_roundtrip(frame, tmp_path, version=None): """ Assert that a frame can be written to an ASDF file and read back in without losing any of its essential properties. """ - path = str(tmpdir / "test.asdf") + path = tmp_path / "test.asdf" with asdf.AsdfFile({"frame": frame}, version=version) as af: af.write_to(path) @@ -56,8 +55,8 @@ def _assert_wcs_equal(a, b): assert_model_equal(a_step.transform, b_step.transform) -def assert_wcs_roundtrip(wcs, tmpdir, version=None): - path = str(tmpdir / "test.asdf") +def assert_wcs_roundtrip(wcs, tmp_path, version=None): + path = tmp_path / "test.asdf" with asdf.AsdfFile({"wcs": wcs}, version=version) as af: af.write_to(path) @@ -66,7 +65,7 @@ def assert_wcs_roundtrip(wcs, tmpdir, version=None): _assert_wcs_equal(wcs, af["wcs"]) -def test_create_wcs(tmpdir): +def test_create_wcs(tmp_path): m1 = models.Shift(12.4) & models.Shift(-2) icrs = cf.CelestialFrame(name="icrs", reference_frame=coord.ICRS()) det = cf.Frame2D(name="detector", axes_order=(0, 1)) @@ -76,13 +75,13 @@ def test_create_wcs(tmpdir): gw4 = wcs.WCS(output_frame=icrs, input_frame=det, forward_transform=m1) gw4.pixel_shape = (100, 200) - assert_wcs_roundtrip(gw1, tmpdir) - assert_wcs_roundtrip(gw2, tmpdir) - assert_wcs_roundtrip(gw3, tmpdir) - assert_wcs_roundtrip(gw4, tmpdir) + assert_wcs_roundtrip(gw1, tmp_path) + assert_wcs_roundtrip(gw2, tmp_path) + assert_wcs_roundtrip(gw3, tmp_path) + assert_wcs_roundtrip(gw4, tmp_path) -def test_composite_frame(tmpdir): +def test_composite_frame(tmp_path): icrs = coord.ICRS() fk5 = coord.FK5() cel1 = cf.CelestialFrame(reference_frame=icrs) @@ -95,9 +94,9 @@ def test_composite_frame(tmpdir): comp2 = cf.CompositeFrame([cel2, spec2]) comp = cf.CompositeFrame([comp1, cf.SpectralFrame(axes_order=(3,), unit=(u.m,))]) - assert_frame_roundtrip(comp, tmpdir) - assert_frame_roundtrip(comp1, tmpdir) - assert_frame_roundtrip(comp2, tmpdir) + assert_frame_roundtrip(comp, tmp_path) + assert_frame_roundtrip(comp1, tmp_path) + assert_frame_roundtrip(comp2, tmp_path) def create_test_frames(): @@ -146,13 +145,13 @@ def create_test_frames(): ] -def test_frames(tmpdir): +def test_frames(tmp_path): frames = create_test_frames() for f in frames: - assert_frame_roundtrip(f, tmpdir) + assert_frame_roundtrip(f, tmp_path) -def test_references(tmpdir): +def test_references(tmp_path): m1 = models.Shift(12.4) & models.Shift(-2) icrs = cf.CelestialFrame(name="icrs", reference_frame=coord.ICRS()) det = cf.Frame2D(name="detector", axes_order=(0, 1)) @@ -166,7 +165,7 @@ def test_references(tmpdir): tree = {"wcs1": gw1, "wcs2": gw2} af = asdf.AsdfFile(tree) - output_path = os.path.join(str(tmpdir), "test.asdf") + output_path = tmp_path / "test.asdf" af.write_to(output_path) with asdf.open(output_path) as af: diff --git a/gwcs/tests/test_geometry.py b/gwcs/tests/test_geometry.py index fcb76820..52caa3bc 100644 --- a/gwcs/tests/test_geometry.py +++ b/gwcs/tests/test_geometry.py @@ -191,13 +191,13 @@ def test_c2s2c_wrong_wrap_type(spher_to_cart, cart_to_spher, wrap_at): cart_to_spher.wrap_lon_at = wrap_at -def test_cartesian_spherical_asdf(tmpdir): +def test_cartesian_spherical_asdf(tmp_path): s2c0 = geometry.SphericalToCartesian(wrap_lon_at=360) c2s0 = geometry.CartesianToSpherical(wrap_lon_at=180) # asdf round-trip test: - assert_model_roundtrip(c2s0, tmpdir) - assert_model_roundtrip(s2c0, tmpdir) + assert_model_roundtrip(c2s0, tmp_path) + assert_model_roundtrip(s2c0, tmp_path) # create file object f = asdf.AsdfFile({"c2s": c2s0, "s2c": s2c0}) diff --git a/gwcs/tests/test_utils.py b/gwcs/tests/test_utils.py index 3f6648a9..e7118878 100644 --- a/gwcs/tests/test_utils.py +++ b/gwcs/tests/test_utils.py @@ -1,5 +1,5 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import os.path +from pathlib import Path import numpy as np import pytest @@ -16,7 +16,7 @@ from . import data -data_path = os.path.split(os.path.abspath(data.__file__))[0] +data_path = Path(data.__file__).parent.absolute() def test_wrong_projcode(): @@ -31,7 +31,7 @@ def test_wrong_projcode2(): def test_fits_transform(): - hdr = fits.Header.fromfile(os.path.join(data_path, "simple_wcs2.hdr")) + hdr = fits.Header.fromfile(data_path / "simple_wcs2.hdr") gw1 = gwutils.make_fitswcs_transform(hdr) w1 = fitswcs.WCS(hdr) assert_allclose(gw1(1, 2), w1.wcs_pix2world(1, 2, 0), atol=10**-8) diff --git a/gwcs/tests/test_wcs.py b/gwcs/tests/test_wcs.py index 4b586783..69f938d8 100644 --- a/gwcs/tests/test_wcs.py +++ b/gwcs/tests/test_wcs.py @@ -1,6 +1,6 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import os.path import warnings +from pathlib import Path import asdf import numpy as np @@ -24,7 +24,7 @@ from gwcs.utils import CoordinateFrameError from gwcs.wcstools import grid_from_bounding_box, wcs_from_fiducial, wcs_from_points -data_path = os.path.split(os.path.abspath(data.__file__))[0] +data_path = Path(data.__file__).parent.absolute() m1 = models.Shift(12.4) & models.Shift(-2) @@ -441,7 +441,7 @@ def test_grid_from_compound_bounding_box(): def test_wcs_from_points(): rng = np.random.default_rng(0) - hdr = fits.Header.fromtextfile(os.path.join(data_path, "acs.hdr"), endcard=False) + hdr = fits.Header.fromtextfile(data_path / "acs.hdr", endcard=False) with pytest.warns(astwcs.FITSFixedWarning) as caught_warnings: # this raises a warning unimportant for this testing the pix2world # FITSFixedWarning(u'The WCS transformation has more axes (2) than @@ -635,9 +635,7 @@ def test_high_level_api(): class TestImaging: def setup_class(self): - hdr = fits.Header.fromtextfile( - os.path.join(data_path, "acs.hdr"), endcard=False - ) + hdr = fits.Header.fromtextfile(data_path / "acs.hdr", endcard=False) with pytest.warns(astwcs.FITSFixedWarning) as caught_warnings: # this raises a warning unimportant for this testing the pix2world # FITSFixedWarning(u'The WCS transformation has more axes (2) than @@ -752,7 +750,7 @@ def test_to_fits_sip(): y, x = np.mgrid[:1024:10, :1024:10] xflat = np.ravel(x[1:-1, 1:-1]) yflat = np.ravel(y[1:-1, 1:-1]) - fn = os.path.join(data_path, "miriwcs.asdf") + fn = data_path / "miriwcs.asdf" with asdf.open( fn, lazy_load=False, @@ -1122,7 +1120,7 @@ def test_to_fits_tab_time_cube(gwcs_cube_with_separable_time): def test_to_fits_tab_miri_image(): # gWCS: - fn = os.path.join(data_path, "miriwcs.asdf") + fn = data_path / "miriwcs.asdf" with asdf.open( fn, lazy_load=False, @@ -1148,7 +1146,7 @@ def test_to_fits_tab_miri_image(): def test_to_fits_tab_miri_lrs(): - fn = os.path.join(data_path, "miri_lrs_wcs.asdf") + fn = data_path / "miri_lrs_wcs.asdf" with asdf.open( fn, lazy_load=False, @@ -1237,7 +1235,7 @@ def test_in_image(): def test_iter_inv(): - fn = os.path.join(data_path, "nircamwcs.asdf") + fn = data_path / "nircamwcs.asdf" with asdf.open( fn, lazy_load=False, @@ -1397,7 +1395,7 @@ def test_initialize_wcs_with_list(): def test_sip_roundtrip(): - hdr = fits.Header.fromtextfile(os.path.join(data_path, "acs.hdr"), endcard=False) + hdr = fits.Header.fromtextfile(data_path / "acs.hdr", endcard=False) nx = ny = 1024 hdr["naxis"] = 2 @@ -1432,7 +1430,7 @@ def test_sip_roundtrip(): def test_spatial_spectral_stokes(): """Converts a FITS WCS to GWCS and compares results.""" - hdr = fits.Header.fromfile(os.path.join(data_path, "stokes.txt")) + hdr = fits.Header.fromfile(data_path / "stokes.txt") with warnings.catch_warnings(): warnings.simplefilter("ignore", category=astwcs.FITSFixedWarning) aw = astwcs.WCS(hdr) diff --git a/pyproject.toml b/pyproject.toml index 8dd3de43..83056e38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ text_file_format = "rst" addopts = [ "--color=yes", "--doctest-rst", + "-p no:legacypath", ] norecursedirs = [ "build", @@ -140,7 +141,7 @@ select = [ "SIM", # flake8-simplify (suggest simplifications to code where possible) "TID", # flake8-tidy-imports (prevent banned api and best import practices) "INT", # flake8-gettext (when to use printf style strings) - #"PTH", # flake8-use-pathlib (prefer pathlib over os.path) + "PTH", # flake8-use-pathlib (prefer pathlib over os.path) "ERA", # eradicate (remove commented out code) "PGH", # pygrep (simple grep checks) "PL", # pylint (general linting, flake8 alternative) @@ -148,7 +149,6 @@ select = [ "FLY", # flynt (f-string conversion where possible) "NPY", # NumPy-specific checks (recommendations from NumPy) "PERF", # Perflint (performance linting) - # "LOG", "RUF", # ruff specific checks ] ignore = [ @@ -163,3 +163,4 @@ ignore = [ "gwcs/tests/*" = ["S101"] "gwcs/converters/tests/*" = ["S101"] "docs/conf.py" = ["INP001", "ERA001"] +"convert_schemas.py" = ["PTH"] From ea03b100cd0808e863e9bbb8d17041534ae9c3f1 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Fri, 3 Jan 2025 09:35:39 -0500 Subject: [PATCH 26/31] Update pytest config --- pyproject.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83056e38..3f9d4da9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,13 +75,22 @@ upload-dir = "docs/_build/html" show-response = 1 [tool.pytest.ini_options] -minversion = 4.6 +minversion = 6 doctest_plus = true doctest_rst = true text_file_format = "rst" +log_cli_level = "INFO" +xfail_strict = true +testpaths = [ + "gwcs", + "docs", +] addopts = [ + "-ra", "--color=yes", "--doctest-rst", + "--strict-config", + "--strict-markers", "-p no:legacypath", ] norecursedirs = [ From 9394a808ce6a951695aba01d93a27bbede1dc3d3 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Fri, 3 Jan 2025 09:38:17 -0500 Subject: [PATCH 27/31] Update CI --- .bandit.yaml | 3 --- .flake8 | 11 ----------- .github/workflows/ci.yml | 1 - tox.ini | 15 ++++----------- 4 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 .bandit.yaml delete mode 100644 .flake8 diff --git a/.bandit.yaml b/.bandit.yaml deleted file mode 100644 index 523a15e5..00000000 --- a/.bandit.yaml +++ /dev/null @@ -1,3 +0,0 @@ -exclude_dirs: - - gwcs/tests - - gwcs/tags/tests diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 3d67ac16..00000000 --- a/.flake8 +++ /dev/null @@ -1,11 +0,0 @@ -# flake8 does not support pyproject.toml (https://github.com/PyCQA/flake8/issues/234) - -[flake8] -select = F, W, E101, E111, E112, E113, E401, E402, E501, E711, E722 -max-line-length = 110 -exclude = conftest.py, schemas, tags, .git, __pycache__, docs, build, dist, .tox, .eggs -# E265: # has no space after -# E501: line too long -# F403: unable to detect undefined names -# F405: may be defined from * imports -ignore = E265,E501,F403,F405,W503,W504 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddc44420..0dee03af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,6 @@ jobs: with: envs: | - linux: check-style - - linux: check-security test: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 with: diff --git a/tox.ini b/tox.ini index e2fb37f1..47f1d99f 100644 --- a/tox.ini +++ b/tox.ini @@ -23,20 +23,13 @@ dkist_repo = https://github.com/DKISTDC/dkist.git ndcube_repo = https://github.com/sunpy/ndcube.git [testenv:check-style] -description = check code style, e.g. with flake8 +description = Run all style and file checks with pre-commit skip_install = true deps = - flake8 + pre-commit commands = - flake8 . {posargs} - -[testenv:check-security] -description = run bandit to check security compliance -skip_install = true -deps = - bandit>=1.7 -commands = - bandit -r -ll -c .bandit.yaml gwcs + pre-commit install-hooks + pre-commit run {posargs:--color always --all-files --show-diff-on-failure} [testenv:docs] description = invoke sphinx-build to build the HTML docs From 14e7e3b7d0ab36dfb41e052530782118cf206b7e Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Fri, 3 Jan 2025 09:42:06 -0500 Subject: [PATCH 28/31] Update changes --- CHANGES.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 17d51151..dfba0929 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,4 @@ -0.22.0 (2024-12-19) +0.23.0 (unreleased) ------------------- - Coordinate frames now have a "native" order and then are sorted based on ``axes_order``. [#457] @@ -11,6 +11,12 @@ - Inputs to ``CelestialFrame``, such as ``axes_names`` are now explicitly in lon, lat order and will re sorted based on ``axes_order=``. [#457] +- Implement code linting and automatic formatting. [#544] + + +0.22.0 (2024-12-19) +------------------- + - Replace usages of ``copy_arrays`` with ``memmap`` [#503] - Fix an issue with units in ``wcs_from_points``. [#507] @@ -21,7 +27,6 @@ - Add support for compound bounding boxes and ignored bounding box entries. [#519] - - Add ``gwcs.examples`` module, based on the examples located in the testing ``conftest.py``. [#521] - Force ``bounding_box`` to always be returned as a ``F`` ordered box. [#522] From 686697619196b893798215df2bb649b71ee55d0c Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Fri, 3 Jan 2025 09:49:22 -0500 Subject: [PATCH 29/31] Fix broken CI --- gwcs/wcs.py | 6 ++---- pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index 728a5d0c..bc498872 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -3478,13 +3478,11 @@ def __getitem__(self, ind): def __str__(self): return ( f"{self.frame_name}\t " - f"{getattr(self.transform, 'name', 'None') or - type(self.transform).__name__}" + f"{getattr(self.transform, 'name', 'None') or type(self.transform).__name__}" # noqa: E501 ) def __repr__(self): return ( f"Step(frame={self.frame_name}, " - f"transform={getattr(self.transform, 'name', 'None') or - type(self.transform).__name__})" + f"transform={getattr(self.transform, 'name', 'None') or type(self.transform).__name__})" # noqa: E501 ) diff --git a/pyproject.toml b/pyproject.toml index 3f9d4da9..1b0b61cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ docs = [ ] test = [ "ci-watson>=0.3.0", - "pytest>=7.0.0", + "pytest>=8.0.0", "pytest-astropy>=0.11.0", ] @@ -75,7 +75,7 @@ upload-dir = "docs/_build/html" show-response = 1 [tool.pytest.ini_options] -minversion = 6 +minversion = 8 doctest_plus = true doctest_rst = true text_file_format = "rst" From 0238953fc4dbcad492d089b45b8f3922b49d636b Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Fri, 3 Jan 2025 10:14:38 -0500 Subject: [PATCH 30/31] Fix docs --- gwcs/wcs.py | 5 ++--- gwcs/wcstools.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gwcs/wcs.py b/gwcs/wcs.py index bc498872..6d452a35 100644 --- a/gwcs/wcs.py +++ b/gwcs/wcs.py @@ -520,8 +520,7 @@ def invert(self, *args, **kwargs): Parameters ---------- - args : float, array like, `~astropy.coordinates.SkyCoord` or - `~astropy.units.Unit` + args : float, array like, `~astropy.coordinates.SkyCoord` or `~astropy.units.Unit` Coordinates to be inverted. The number of arguments must be equal to the number of world coordinates given by ``world_n_dim``. @@ -551,7 +550,7 @@ def invert(self, *args, **kwargs): The return type will be `~astropy.units.Quantity` objects if the transform returns ``Quantity`` objects, else values. - """ + """ # noqa: E501 # must pop before calling the model with_units = kwargs.pop("with_units", False) diff --git a/gwcs/wcstools.py b/gwcs/wcstools.py index 20445b6e..6c281920 100644 --- a/gwcs/wcstools.py +++ b/gwcs/wcstools.py @@ -174,8 +174,7 @@ def grid_from_bounding_box(bounding_box, step=1, center=True, selector=None): Parameters ---------- - bounding_box : tuple | ~astropy.modeling.bounding_box.ModelBoundingBox | - ~astropy.modeling.bounding_box.CompoundBoundingBox + bounding_box : tuple | ~astropy.modeling.bounding_box.ModelBoundingBox | ~astropy.modeling.bounding_box.CompoundBoundingBox The bounding_box of a WCS object, `~gwcs.wcs.WCS.bounding_box`. step : scalar or tuple Step size for grid in each dimension. Scalar applies to all dimensions. @@ -211,7 +210,7 @@ def grid_from_bounding_box(bounding_box, step=1, center=True, selector=None): ------- x, y [, z]: ndarray Grid of points. - """ + """ # noqa: E501 def _bbox_to_pixel(bbox): return (np.floor(bbox[0] + 0.5), np.ceil(bbox[1] - 0.5)) From 877716d4e014147717b7873b7eb36f7cda16c868 Mon Sep 17 00:00:00 2001 From: William Jamieson Date: Mon, 6 Jan 2025 16:32:58 -0500 Subject: [PATCH 31/31] Fix review comment --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index dfba0929..52ac211d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -226,7 +226,7 @@ New Features - Added ``insert_frame`` method to modify the pipeline of a ``WCS`` object. [#299] - Added ``to_fits_tab`` method to generate FITS header and binary table - extension following FITS WCS ``-TAB`` conversion. [#295] + extension following FITS WCS ``-TAB`` convention. [#295] - Added ``in_image`` function for testing whether a point in world coordinates maps back to the domain of definition of the forward transformation. [#322]