diff --git a/.codecov.yaml b/.codecov.yaml new file mode 100644 index 0000000..8fe09b7 --- /dev/null +++ b/.codecov.yaml @@ -0,0 +1,11 @@ +comment: off +coverage: + status: + project: + default: + threshold: 0.2% + +codecov: + require_ci_to_pass: false + notify: + wait_for_ci: true diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..21f6880 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,30 @@ +[run] +omit = + glue_solar/conftest.py + glue_solar/*setup_package* + glue_solar/extern/* + glue_solar/version* + */glue_solar/conftest.py + */glue_solar/*setup_package* + */glue_solar/extern/* + */glue_solar/version* + +[report] +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + # Don't complain about packages we have installed + except ImportError + # Don't complain if tests don't hit assertions + raise AssertionError + raise NotImplementedError + # Don't complain about script hooks + def main(.*): + # Ignore branches that don't pertain to this version of Python + pragma: py{ignore_python_version} + # Don't complain about IPython completion helper + def _ipython_key_completions_ + # typing.TYPE_CHECKING is False at runtime + if TYPE_CHECKING: + # Ignore typing overloads + @overload diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a2c6683 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# https://editorconfig.org +root=true +# utf, UNIX-style new line + +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +trim_trailing_whitespace=true + +[*.{py,rst,md}] +indent_style=space +indent_size=4 + +[*.yml] +indent_style=space +indent_size=2 diff --git a/.github/workflows/ci_workflows.yml b/.github/workflows/ci_workflows.yml index 019d6b7..c962515 100644 --- a/.github/workflows/ci_workflows.yml +++ b/.github/workflows/ci_workflows.yml @@ -29,7 +29,7 @@ jobs: - linux: codestyle libraries: coverage: false - - linux: py39 + - linux: py312 test: needs: [core] @@ -51,8 +51,7 @@ jobs: brew: - enchant envs: | - - linux: py310-devdeps - - linux: py39-docs + - linux: py312-docs coverage: false - - macos: py38 - - windows: py39 + - macos: py312 + - windows: py312 diff --git a/.gitignore b/.gitignore index c59d279..8dc01ba 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ pip-wheel-metadata glue_solar/_version.py .tmp .qt_for_python +docs/generated/ diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..5b3f02a --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,14 @@ +[settings] +balanced_wrapping = true +skip = + docs/conf.py +default_section = THIRDPARTY +include_trailing_comma = true +known_astropy = astropy, asdf, sunpy +known_first_party = glue_solar +length_sort = false +length_sort_sections = stdlib +line_length = 110 +multi_line_output = 3 +no_lines_before = LOCALFOLDER +sections = STDLIB, THIRDPARTY, ASTROPY, FIRSTPARTY, LOCALFOLDER diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bac4175..8a65d0e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,31 +1,32 @@ +exclude: ".*(.csv|.fits|.fts|.fit|.header|.txt|tca.*|.json|.asdf)$|^CITATION.rst" repos: - - repo: https://github.com/myint/autoflake - rev: v1.4 + # This should be before any formatting hooks like isort + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.6.5" hooks: - - id: autoflake - args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variable'] - exclude: ".*(.fits|.fts|.fit|.txt|tca.*|extern.*|.rst|.md|__init__.py|docs/conf.py)$" - - repo: https://github.com/ambv/black - rev: 22.6.0 - hooks: - - id: black - - repo: https://github.com/timothycrosley/isort - rev: 5.10.1 + - id: ruff + args: ["--fix"] + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 hooks: - id: isort - args: ['--sp','setup.cfg'] - exclude: ".*(.fits|.fts|.fit|.txt|tca.*|extern.*|.rst|.md|docs/conf.py)$" - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.6.0 hooks: - id: check-ast - id: check-case-conflict - id: trailing-whitespace - exclude: ".*(.fits|.fts|.fit|.txt)$" - id: check-yaml - id: debug-statements - id: check-added-large-files + args: ["--enforce-all", "--maxkb=1054"] + exclude: "" - id: end-of-file-fixer - exclude: ".*(.fits|.fts|.fit|.txt|tca.*)$" - id: mixed-line-ending - exclude: ".*(.fits|.fts|.fit|.txt|tca.*)$" + - repo: https://github.com/crate-ci/typos + rev: v1.22.9 + hooks: + - id: typos +ci: + autofix_prs: false + autoupdate_schedule: "quarterly" diff --git a/.readthedocs.yml b/.readthedocs.yml index a8a72e6..9152ff1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,8 +1,9 @@ version: 2 + build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: - python: "3.9" + python: "mambaforge-latest" apt_packages: - libxkbcommon-x11-0 - libxcb-icccm4 @@ -12,6 +13,14 @@ build: - libxcb-render-util0 - libxcb-xfixes0 - libxcb-xinerama0 + jobs: + post_checkout: + - git fetch --unshallow || true + pre_install: + - git update-index --assume-unchanged .rtd-environment.yml docs/conf.py + +conda: + environment: .rtd-environment.yml sphinx: builder: html @@ -19,9 +28,9 @@ sphinx: fail_on_warning: false python: - install: - - method: pip - extra_requirements: + install: + - method: pip + extra_requirements: - all - docs - path: . + path: . diff --git a/.rtd-environment.yml b/.rtd-environment.yml new file mode 100644 index 0000000..fd564f2 --- /dev/null +++ b/.rtd-environment.yml @@ -0,0 +1,7 @@ +name: rtd_glue_solar +channels: + - conda-forge +dependencies: + - python=3.12 + - pip + - graphviz!=2.42.*,!=2.43.* diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..26ff6aa --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,37 @@ +target-version = "py310" +line-length = 110 +exclude = [ + ".git,", + "__pycache__", + "build", + "*version.py", +] + +[lint] +select = ["E", "F", "W", "UP", "PT"] +extend-ignore = [ + # pycodestyle (E, W) + "E501", # LineTooLong # TODO! fix + # pytest (PT) + "PT001", # Always use pytest.fixture() + "PT004", # Fixtures which don't return anything should have leading _ + "PT007", # Parametrize should be lists of tuples # TODO! fix + "PT011", # Too broad exception assert # TODO! fix + "PT023", # Always use () on pytest decorators +] + +[lint.per-file-ignores] +# Part of configuration, not a package. +"setup.py" = ["INP001"] +"conftest.py" = ["INP001"] +"docs/conf.py" = [ + "E402" # Module imports not at top of file +] +"docs/*.py" = [ + "INP001", # Implicit-namespace-package. The examples are not a package. +] +"__init__.py" = ["E402", "F401", "F403"] +"test_*.py" = ["B011", "D", "E402", "PGH001", "S101"] + +[lint.pydocstyle] +convention = "numpy" diff --git a/LICENSE.rst b/LICENSE.rst index 255e106..6e1bcb5 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,4 +1,4 @@ -Copyright (c) 2019-2022, Glue developers +Copyright (c) 2019-2024, SunPy and Glue developers All rights reserved. diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..6b141ba --- /dev/null +++ b/_typos.toml @@ -0,0 +1,3 @@ +default.extend-ignore-identifiers-re = [ + "NDCube", +] diff --git a/docs/api_reference.rst b/docs/api_reference.rst index 78b1278..007e5e5 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -1,4 +1,4 @@ -.. _api_reference: +.. _glue_solar_api_reference: ============= API Reference @@ -12,6 +12,3 @@ API Reference .. automodapi:: glue_solar.sources.maps :no-inheritance-diagram: - -.. automodapi:: glue_solar.pixel_extraction - :no-inheritance-diagram: diff --git a/docs/conf.py b/docs/conf.py index 7b3b702..e890fd9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,46 +1,189 @@ +""" +Configuration file for the Sphinx documentation builder. +""" +# -- stdlib imports ------------------------------------------------------------ +import os +import datetime + + +# -- Read the Docs Specific Configuration -------------------------------------- +# This needs to be done before sunpy is imported +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if on_rtd: + os.environ['SUNPY_CONFIGDIR'] = '/home/docs/' + os.environ['HOME'] = '/home/docs/' + os.environ['LANG'] = 'C' + os.environ['LC_ALL'] = 'C' + os.environ['PARFIVE_HIDE_PROGRESS'] = 'True' + # -- Project information ----------------------------------------------------- -project = "Glue Solar" -copyright = "2020-2022, The SunPy Developers and The Glue Developers" -author = "The SunPy Developers and The Glue Developers" -# -- General configuration --------------------------------------------------- +project = 'glue-solar' +author = 'The SunPy Developers and The Glue Developers' +copyright = f'{datetime.datetime.now().year}, {author}' + +# sphinxext-opengraph +ogp_image = "https://raw.githubusercontent.com/sunpy/sunpy-logo/master/generated/sunpy_logo_word.png" +ogp_use_first_image = True +ogp_description_length = 160 +ogp_custom_meta_tags = [ + '', +] + +# Suppress warnings about overriding directives as we overload some of the +# doctest extensions. +suppress_warnings = ['app.add_directive', ] + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named "sphinx.ext.*") or your custom +# ones. extensions = [ - "sphinx_automodapi.automodapi", - "sphinx_automodapi.smart_resolver", - "sphinx.ext.autodoc", - "sphinx.ext.coverage", - "sphinx.ext.doctest", - "sphinx.ext.inheritance_diagram", - "sphinx.ext.intersphinx", - "sphinx.ext.mathjax", - "sphinx.ext.napoleon", - "sphinx.ext.todo", - "sphinx.ext.viewcode", + 'matplotlib.sphinxext.plot_directive', + 'sphinx_automodapi.automodapi', + 'sphinx_automodapi.smart_resolver', + 'sphinx_changelog', + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.doctest', + 'sphinx.ext.inheritance_diagram', + 'sphinx.ext.intersphinx', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', + "sphinxext.opengraph", + 'sphinx_design', + 'sphinx_copybutton', + 'hoverxref.extension', ] -numpydoc_show_class_members = False -templates_path = ["_templates"] + +# Set automodapi to generate files inside the generated directory +automodapi_toctreedirnm = "generated/api" + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +html_extra_path = ['robots.txt'] + exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: source_suffix = ".rst" + +# The master toctree document. +master_doc = 'index' + +# The reST default role (used for this markup: `text`) to use for all +# documents. Set to the "smart" one. +default_role = 'obj' + +# Disable having a separate return type row +napoleon_use_rtype = False + +# Disable google style docstrings +napoleon_google_docstring = False + +# Disable the use of param, which prevents a distinct "Other Parameters" section +napoleon_use_param = False + +# Enable nitpicky mode, which forces links to be non-broken +nitpicky = True +# This is not used. See docs/nitpick-exceptions file for the actual listing. +nitpick_ignore = [] +for line in open('nitpick-exceptions'): + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + nitpick_ignore.append((dtype, target)) + + # -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "python": ( "https://docs.python.org/3/", - (None, "http://data.astropy.org/intersphinx/python3.inv"), + (None, "http://www.astropy.org/astropy-data/intersphinx/python3.inv"), ), "numpy": ( "https://numpy.org/doc/stable/", - (None, "http://data.astropy.org/intersphinx/numpy.inv"), + (None, "http://www.astropy.org/astropy-data/intersphinx/numpy.inv"), ), "scipy": ( "https://docs.scipy.org/doc/scipy/reference/", - (None, "http://data.astropy.org/intersphinx/scipy.inv"), - ), - "matplotlib": ( - "https://matplotlib.org/", - (None, "http://data.astropy.org/intersphinx/matplotlib.inv"), + (None, "http://www.astropy.org/astropy-data/intersphinx/scipy.inv"), ), + "aiapy": ("https://aiapy.readthedocs.io/en/stable/", None), "astropy": ("https://docs.astropy.org/en/stable/", None), - "sunpy": ("https://docs.sunpy.org/en/stable/", None), "glueviz": ("http://docs.glueviz.org/en/stable/", None), + "irispy": ("https://irispy-lmsal.readthedocs.io/en/stable/", None), + "matplotlib": ("https://matplotlib.org/stable", None), + "reproject": ("https://reproject.readthedocs.io/en/stable/", None), + "skimage": ("https://scikit-image.org/docs/stable/", None), + "sunkit_image": ("https://docs.sunpy.org/projects/sunkit-image/en/stable/", None), + "sunkit_instruments": ("https://docs.sunpy.org/projects/sunkit-instruments/en/stable/", None), + "sunpy": ("https://docs.sunpy.org/en/stable/", None), +} + +# -- Options for hoverxref ----------------------------------------------------- +if os.environ.get("READTHEDOCS"): + hoverxref_api_host = "https://readthedocs.org" + + if os.environ.get("PROXIED_API_ENDPOINT"): + # Use the proxied API endpoint + # A RTD thing to avoid a CSRF block when docs are using a custom domain + hoverxref_api_host = "/_" + +hoverxref_auto_ref = False +hoverxref_domains = ["py"] +hoverxref_mathjax = True +hoverxref_modal_hover_delay = 500 +hoverxref_tooltip_maxwidth = 600 # RTD main window is 696px +hoverxref_intersphinx = list(intersphinx_mapping.keys()) +hoverxref_role_types = { + # Roles within the py domain + "attr": "tooltip", + "class": "tooltip", + "const": "tooltip", + "data": "tooltip", + "exc": "tooltip", + "func": "tooltip", + "meth": "tooltip", + "mod": "tooltip", + "obj": "tooltip", + # Roles within the std domain + "confval": "tooltip", + "hoverxref": "tooltip", + "ref": "tooltip", # Would be used by hoverxref_auto_ref if we set it to True + "term": "tooltip", } -# -- Options for HTML output ------------------------------------------------- -from sunpy_sphinx_theme.conf import * + +# -- Options for HTML output --------------------------------------------------- +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "sunpy" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ["_static"] + +# Render inheritance diagrams in SVG +graphviz_output_format = "svg" + +graphviz_dot_args = [ + '-Nfontsize=10', + '-Nfontname=Helvetica Neue, Helvetica, Arial, sans-serif', + '-Efontsize=10', + '-Efontname=Helvetica Neue, Helvetica, Arial, sans-serif', + '-Gfontsize=10', + '-Gfontname=Helvetica Neue, Helvetica, Arial, sans-serif' +] diff --git a/docs/dev_guide/index.rst b/docs/dev_guide/index.rst index 7dd2e8b..a6c7aa3 100644 --- a/docs/dev_guide/index.rst +++ b/docs/dev_guide/index.rst @@ -1,3 +1,6 @@ +.. _glue_solar_dev_docs_index: + +======================= Developer Documentation ======================= diff --git a/docs/dev_guide/loader-customization.rst b/docs/dev_guide/loader-customization.rst index 4e2842a..7effd8d 100644 --- a/docs/dev_guide/loader-customization.rst +++ b/docs/dev_guide/loader-customization.rst @@ -1,4 +1,4 @@ -.. _loader_customization_guide: +.. _glue_solar_dev_docs_loader_customization: =============================== Data Loader Customization Guide diff --git a/docs/index.rst b/docs/index.rst index bcbf412..cdd8505 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,6 @@ -.. _glue-solar: +.. _glue-solar-index: +======================== glue-solar documentation ======================== diff --git a/docs/nitpick-exceptions b/docs/nitpick-exceptions new file mode 100644 index 0000000..9d9486a --- /dev/null +++ b/docs/nitpick-exceptions @@ -0,0 +1,6 @@ +# Prevents sphinx nitpicky mode picking up on optional +# (see https://github.com/sphinx-doc/sphinx/issues/6861) +# Even if it was "fixed", still broken +py:class optional +# See https://github.com/numpy/numpy/issues/10039 +py:obj numpy.datetime64 diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000..df810cf --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Allow: /*/latest/ +Allow: /en/latest/ # Fallback for bots that don't understand wildcards +Allow: /*/stable/ +Allow: /en/stable/ # Fallback for bots that don't understand wildcards +Disallow: / diff --git a/docs/user_guide/guide-to-glue-1dprofile-viewer-for-iris-data.rst b/docs/user_guide/guide-to-glue-1dprofile-viewer-for-iris-data.rst index 6b28113..fc58330 100644 --- a/docs/user_guide/guide-to-glue-1dprofile-viewer-for-iris-data.rst +++ b/docs/user_guide/guide-to-glue-1dprofile-viewer-for-iris-data.rst @@ -1,4 +1,4 @@ -.. _guide_to_1dprofile_viewer_for_iris_data: +.. _glue_solar_user_guide_1dprofile_viewer_for_iris_data: ============================================================================= A guide to using ``glue``'s 1D profile viewer to probe IRIS Level 2 data sets @@ -37,7 +37,7 @@ Now we are ready to visualize the 2D slices of the imported data cubes. Using ``glue``'s 2D image viewer to get the indices and slices needed for the 1D spectrum plot ---------------------------------------------------------------------------------------------- -To plot 2D slices of the ND data cubes, we will need to drag the data ("C_II_1336" in this +To plot 2D slices of the AND data cubes, we will need to drag the data ("C_II_1336" in this example) concerned from the "Data Collection" area and drop it at the large plotting window to the right, as shown in the following image: diff --git a/docs/user_guide/index.rst b/docs/user_guide/index.rst index f8624ec..68966aa 100644 --- a/docs/user_guide/index.rst +++ b/docs/user_guide/index.rst @@ -1,5 +1,6 @@ -.. _users_guide: +.. _glue_solar_users_guide_index: +========== User Guide ========== diff --git a/docs/user_guide/loading-aia-and-hmi.rst b/docs/user_guide/loading-aia-and-hmi.rst index ae786df..849aded 100644 --- a/docs/user_guide/loading-aia-and-hmi.rst +++ b/docs/user_guide/loading-aia-and-hmi.rst @@ -1,4 +1,4 @@ -.. _loading_aia_and_hmi_files: +.. _glue_solar_users_guide_loading_aia_and_hmi_files: ========================================================= Loading and over-plotting AIA and HMI files as sunpy Maps diff --git a/docs/user_guide/loading-iris-level-2-raster-and-sji-data.rst b/docs/user_guide/loading-iris-level-2-raster-and-sji-data.rst index 59ce4e4..25b6e8e 100644 --- a/docs/user_guide/loading-iris-level-2-raster-and-sji-data.rst +++ b/docs/user_guide/loading-iris-level-2-raster-and-sji-data.rst @@ -1,4 +1,4 @@ -.. _loading_iris_level_2_raster_and_sji_files: +.. _glue_solar_users_guide_loading_iris_level_2_raster_and_sji_files: ======================================================= Loading IRIS Level 2 Raster and SJI Data Files Together diff --git a/glue_solar/__init__.py b/glue_solar/__init__.py index 368309c..0bcf857 100644 --- a/glue_solar/__init__.py +++ b/glue_solar/__init__.py @@ -1,16 +1,13 @@ from glue.config import colormaps -from glue.viewers.image.qt import ImageViewer from sunpy.visualization.colormaps import cmlist -from glue_solar.pixel_extraction.pixel_extraction import PixelExtractionTool # NOQA -from glue_solar.sources import iris, maps, sst # NOQA +from glue_solar.sources import iris, maps, sst from glue_solar.version import version as __version__ +__all__ = ["setup", "__version__", "iris", "maps", "sst"] def setup(): - # List of all plugins to enable to the default ImageViewer - ImageViewer.tools.append("solar:pixel_extraction") # Enables sunpy colormaps to be used in glueviz for _, ctable in sorted(cmlist.items()): colormaps.add(ctable.name, ctable) diff --git a/glue_solar/pixel_extraction/__init__.py b/glue_solar/pixel_extraction/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/glue_solar/pixel_extraction/pixel_extraction.py b/glue_solar/pixel_extraction/pixel_extraction.py deleted file mode 100644 index ed58620..0000000 --- a/glue_solar/pixel_extraction/pixel_extraction.py +++ /dev/null @@ -1,64 +0,0 @@ -from glue.config import viewer_tool -from glue.core.data_derived import IndexedData -from glue.viewers.matplotlib.toolbar_mode import ToolbarModeBase - -__all__ = ["PixelExtractionTool"] - - -@viewer_tool -class PixelExtractionTool(ToolbarModeBase): - """ - Create a "dervied dataset" corresponding to the selected pixel. - """ - - icon = "pencil" - tool_id = "solar:pixel_extraction" - action_text = "Pixel" - tool_tip = "Extract data for a single pixel based on mouse location" - status_tip = "CLICK to select a point, CLICK and DRAG to update the extracted dataset in real time" - _pressed = False - - def __init__(self, *args, **kwargs): - super(PixelExtractionTool, self).__init__(*args, **kwargs) - self._move_callback = self._extract_pixel - self._press_callback = self._on_press - self._release_callback = self._on_release - self._derived = None - self._line_x = self.viewer.axes.axvline(0, color="orange") - self._line_x.set_visible(False) - self._line_y = self.viewer.axes.axhline(0, color="orange") - self._line_y.set_visible(False) - - def _on_press(self, mode): - self._pressed = True - self._extract_pixel(mode) - - def _on_release(self, mode): - self._pressed = False - - def _extract_pixel(self, mode): - if not self._pressed: - return - x, y = self._event_xdata, self._event_ydata - if x is None or y is None: - return None - xi = int(round(x)) - yi = int(round(y)) - indices = [None] * self.viewer.state.reference_data.ndim - indices[self.viewer.state.x_att.axis] = xi - indices[self.viewer.state.y_att.axis] = yi - self._line_x.set_data([x, x], [0, 1]) - self._line_x.set_visible(True) - self._line_y.set_data([0, 1], [y, y]) - self._line_y.set_visible(True) - self.viewer.axes.figure.canvas.draw() - if self._derived is None: - self._derived = IndexedData(self.viewer.state.reference_data, indices) - self.viewer.session.data_collection.append(self._derived) - else: - try: - self._derived.indices = indices - except TypeError: - self.viewer.session.data_collection.remove(self._derived) - self._derived = IndexedData(self.viewer.state.reference_data, indices) - self.viewer.session.data_collection.append(self._derived) diff --git a/glue_solar/pixel_extraction/tests/__init__.py b/glue_solar/pixel_extraction/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/glue_solar/pixel_extraction/tests/test_pixel_extraction.py b/glue_solar/pixel_extraction/tests/test_pixel_extraction.py deleted file mode 100644 index 044247e..0000000 --- a/glue_solar/pixel_extraction/tests/test_pixel_extraction.py +++ /dev/null @@ -1,71 +0,0 @@ -import numpy as np -from glue.app.qt import GlueApplication -from glue.core.data import Data -from glue.utils.qt import process_events -from glue.viewers.image.qt import ImageViewer -from numpy.testing import assert_allclose - - -class TestPixelExtraction: - def setup_method(self, method): - self.data = Data(label="d1") - self.data["x"] = np.arange(24).reshape((3, 4, 2)).astype(float) - - self.app = GlueApplication() - self.session = self.app.session - self.hub = self.session.hub - - self.data_collection = self.session.data_collection - self.data_collection.append(self.data) - - self.viewer = self.app.new_data_viewer(ImageViewer) - - def teardown_method(self, method): - self.viewer.close(warn=False) - self.viewer = None - self.app.close() - self.app = None - - def test_navigate_sync_image(self): - self.viewer.add_data(self.data) - self.viewer.toolbar.active_tool = "solar:pixel_extraction" - - self.viewer.axes.figure.canvas.draw() - process_events() - - x, y = self.viewer.axes.transData.transform([[1, 2]])[0] - self.viewer.axes.figure.canvas.button_press_event(x, y, 1) - self.viewer.axes.figure.canvas.button_release_event(x, y, 1) - assert len(self.data_collection) == 2 - - derived1 = self.data_collection[1] - assert derived1.label == "d1[:,2,1]" - assert derived1.shape == (3,) - assert_allclose(derived1["x"], self.data["x"][:, 2, 1]) - - x, y = self.viewer.axes.transData.transform([[1, 1]])[0] - self.viewer.axes.figure.canvas.button_press_event(x, y, 1) - self.viewer.axes.figure.canvas.button_release_event(x, y, 1) - assert len(self.data_collection) == 2 - - derived2 = self.data_collection[1] - assert derived2 is derived1 - assert derived2.label == "d1[:,1,1]" - assert derived2.shape == (3,) - assert_allclose(derived2["x"], self.data["x"][:, 1, 1]) - - self.viewer.state.x_att = self.data.pixel_component_ids[0] - - self.viewer.axes.figure.canvas.draw() - process_events() - - x, y = self.viewer.axes.transData.transform([[1, 0]])[0] - self.viewer.axes.figure.canvas.button_press_event(x, y, 1) - self.viewer.axes.figure.canvas.button_release_event(x, y, 1) - assert len(self.data_collection) == 2 - - derived3 = self.data_collection[1] - assert derived3 is not derived1 - assert derived3.label == "d1[1,0,:]" - assert derived3.shape == (2,) - assert_allclose(derived3["x"], self.data["x"][1, 0, :]) diff --git a/glue_solar/sources/iris.py b/glue_solar/sources/iris.py index 62d0f69..cfbe5d0 100644 --- a/glue_solar/sources/iris.py +++ b/glue_solar/sources/iris.py @@ -9,8 +9,8 @@ from glue.core.data_factories import is_fits from glue.core.visual import VisualAttributes from irispy.io import read_files -from irispy.sji import IRISMapCube, IRISMapCubeSequence -from irispy.spectrograph import IRISCollection +from irispy.sji import SJICube +from irispy.spectrograph import SpectrogramCube from qtpy import QtWidgets from astropy.io import fits @@ -20,7 +20,7 @@ __all__ = ["import_iris", "read_iris_files"] -@qglue_parser(IRISCollection) +@qglue_parser(SpectrogramCube) def _parse_iris_raster(data): """ Parse IRIS Level 2 raster files so that it can be loaded by glue. @@ -41,7 +41,7 @@ def _parse_iris_raster(data): return w_data -@qglue_parser(IRISMapCube) +@qglue_parser(SJICube) def _parse_iris_sji(data, file_header): """ Parse IRIS Level 2 SJI files so that it can be loaded by glue. @@ -68,9 +68,9 @@ def read_iris_files(file_path): """ # TODO: Memmap in future. data = read_files(file_path, uncertainty=False, memmap=False) - if isinstance(data, IRISMapCubeSequence): + if isinstance(data, SJICube): return _parse_iris_sji(data, fits.getheader(file_path)) - elif isinstance(data, IRISCollection): + elif isinstance(data, SpectrogramCube): return _parse_iris_raster(data) else: raise ValueError(f"Unrecognised IRIS file type for {file_path}") diff --git a/glue_solar/sources/loaders/iris.py b/glue_solar/sources/loaders/iris.py index 4c3e665..379a394 100644 --- a/glue_solar/sources/loaders/iris.py +++ b/glue_solar/sources/loaders/iris.py @@ -5,8 +5,7 @@ from glue.core.coordinates import WCSCoordinates from glue.core.data import Data from glue.core.visual import VisualAttributes -from glue.utils.qt import get_qapp -from glue.utils.qt.helpers import load_ui +from glue_qt.utils import get_qapp, load_ui from irispy.io import read_files from qtpy import QtWidgets from qtpy.QtCore import Qt @@ -69,28 +68,31 @@ def populate_table_sji(self): self.sjis.resizeColumnToContents(1) def get_raster_windows(self): + if not self.raster_files: + return [] with fits.open(self.raster_files[0]) as hdulist: return list( - hdulist[0].header["TDESC{0}".format(i)] + hdulist[0].header[f"TDESC{i}"] for i in range(1, hdulist[0].header["NWIN"] + 1) ) def get_sji_windows(self): windows = {} + if not self.sji_files: + return windows for sji in self.sji_files: with fits.open(sji) as hdul: windows[hdul[0].header["TDESC1"]] = sji - return windows def load_sji(self, sji): with fits.open(sji) as hdul: - hdul.verify("fix") + hdul.verify("silentfix+ignore") label = hdul[0].header["TDESC1"] + hdul[0].header["OBSID"] data = Data(label=label) data.coords = WCSCoordinates(hdul[0].header) data.meta = hdul[0].header - preferred_cmap_name = "IRIS " + hdul[0].header["TDESC1"].replace("_", " ") + preferred_cmap_name = f"iris{hdul[0].header["TDESC1"].replace("_","").lower()}" data.style = VisualAttributes(preferred_cmap=preferred_cmap_name) data.add_component(Component(hdul[0].data), label) diff --git a/glue_solar/sources/loaders/maps.py b/glue_solar/sources/loaders/maps.py index aa304df..650d88e 100644 --- a/glue_solar/sources/loaders/maps.py +++ b/glue_solar/sources/loaders/maps.py @@ -4,8 +4,7 @@ from glue.core.component import Component from glue.core.data import Data from glue.core.visual import VisualAttributes -from glue.utils.qt import get_qapp -from glue.utils.qt.helpers import load_ui +from glue_qt.utils import get_qapp, load_ui from qtpy import QtWidgets from qtpy.QtCore import Qt diff --git a/pyproject.toml b/pyproject.toml index 349a6be..ee72b0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,62 +1,79 @@ [build-system] requires = [ - "setuptools>=56,!=61.0.0", - "setuptools_scm[toml]>=6.2", - "wheel", + "setuptools>=62.1", + "setuptools_scm[toml]>=6.2", + "wheel", ] -build-backend = 'setuptools.build_meta' - -[ tool.gilesbot ] - [ tool.gilesbot.pull_requests ] - enabled = true - - [ tool.gilesbot.towncrier_changelog ] - enabled = true - verify_pr_number = true - changelog_skip_label = "No Changelog Entry Needed" - help_url = "https://github.com/glueviz/glue-solar/blob/main/changelog/README.rst" - changelog_missing_long = "There isn't a changelog file in this pull request. Please add a changelog file to the `changelog/` directory following the instructions in the changelog [README](https://github.com/glueviz/glue-solar/blob/main/changelog/README.rst)." - type_incorrect_long = "The changelog file you added is not one of the allowed types. Please use one of the types described in the changelog [README](https://github.com/glueviz/glue-solar/blob/main/changelog/README.rst)" - number_incorrect_long = "The number in the changelog file you added does not match the number of this pull request. Please rename the file." - -[tool.towncrier] - package = "glue-solar" - filename = "CHANGELOG.rst" - directory = "changelog/" - issue_format = "`#{issue} `__" - title_format = "{version} ({project_date})" - - [[tool.towncrier.type]] - directory = "breaking" - name = "Breaking Changes" - showcontent = true - - [[tool.towncrier.type]] - directory = "deprecation" - name = "Deprecations" - showcontent = true - - [[tool.towncrier.type]] - directory = "removal" - name = "Removals" - showcontent = true - - [[tool.towncrier.type]] - directory = "feature" - name = "New Features" - showcontent = true - - [[tool.towncrier.type]] - directory = "bugfix" - name = "Bug Fixes" - showcontent = true - - [[tool.towncrier.type]] - directory = "doc" - name = "Documentation" - showcontent = true - - [[tool.towncrier.type]] - directory = "trivial" - name = "Internal Changes" - showcontent = true +build-backend = "setuptools.build_meta" + +[project] +name = "glue-solar" +description = "Solar Physics plugin for GlueViz" +requires-python = ">=3.10" +readme = { file = "README.rst", content-type = "text/x-rst" } +license = { file = "LICENSE.rst" } +authors = [ + { name = "The SunPy Community", email = "sunpy@googlegroups.com" }, +] +dependencies = [ + "glue-core[all]>=1.2.0", + "glue-qt[qt]>=0.3.0", + "sunpy[map,net,coordinates]>=6.0.0", + "irispy-lmsal[all]>=0.2.0rc1", +] +dynamic = ["version"] +keywords = ["solar physics", "solar", "science", "sun", "visualization", "coordinates", "glueviz"] +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Physics", +] + +[project.optional-dependencies] +tests = [ + "pytest-astropy", +] +docs = [ + "sphinx>=5.0.0", + "sphinx-automodapi>=0.14.1", + "sphinx-changelog>=1.5.0", + "sphinx-copybutton>=0.5.0", + "sphinx-design>=0.2.0", + "sphinxext-opengraph>=0.6.0", + "sunpy-sphinx-theme>=2.0.3", + "sphinx-hoverxref>=1.3.0", +] + +[project.urls] +Homepage = "https://glueviz.org" +Download = "https://pypi.org/project/glue-solar/" +"Source Code" = "https://github.com/glue-viz/glue-solar" +Documentation = "https://glue-solar.readthedocs.io/en/latest/" +Changelog = "https://glue-solar.readthedocs.io/en/latest/" +"Issue Tracker" = "https://github.com/glue-viz/glue-solar/issues" + +[tool.setuptools] +zip-safe = true +include-package-data = true +platforms = ["any"] +provides = ["glue_solar"] +license-files = ["LICENSE.rst"] + +[tool.setuptools.packages.find] +include = ["glue_solar*"] +exclude = ["glue_solar._dev*"] + + +[tool.setuptools_scm] +write_to = "glue_solar/_version.py" + +[project.entry-points."glue.plugins"] +glue_solar = "glue_solar:setup" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2e1d125 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,46 @@ +[pytest] +minversion = 7.0 +testpaths = + glue_solar + docs +norecursedirs = + .tox + build + docs/_build + docs/generated + *.egg-info + examples + glue_solar/_dev + .history + tools + glue_solar/extern + benchmarks +doctest_plus = enabled +doctest_optionflags = + NORMALIZE_WHITESPACE + FLOAT_CMP + ELLIPSIS +addopts = + --doctest-rst + -p no:unraisableexception + -p no:theadexception + --arraydiff + --doctest-ignore-import-errors + --doctest-continue-on-failure +remote_data_strict = true +junit_family = xunit1 +filterwarnings = + # Turn all warnings into errors so they do not pass silently. + error + # Do not fail on pytest config issues (i.e. missing plugins) but do show them + always::pytest.PytestConfigWarning + # A list of warnings to ignore follows. If you add to this list, you MUST + # add a comment or ideally a link to an issue that explains why the warning + # is being ignored + # These should have been fixed by numpy 2.0 but possible its due to the + # other packages not building agasint it yet? + # This should be at the top of the list as well. + ignore:.*may indicate binary incompatibility.* + ignore:.*Jupyter is migrating its paths to use standard platformdirs.* + ignore:.*could not determine irispy package.* + ignore:.*Setting colormap using "color" key is deprecated.* diff --git a/setup.cfg b/setup.cfg index 7ed3896..6147ff7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,71 +1,3 @@ -[metadata] -name = glue-solar -provides = glue_solar -description = Solar physics focused plugin for glueviz -long_description = file: README.rst -author = Nabil Freij -author_email = freij@baeri.org -license = BSD 3-Clause License -license_file = LICENSE.rst -url = https://github.com/glue-viz/glue-solar -edit_on_github = True -github_project = glueviz/glue-solar -platform = any -keywords = solar physics, solar, science, sun, wcs, coordinates, visualization, glue, glueviz -classifiers = - Development Status :: 2 - Pre-Alpha - Intended Audience :: Science/Research - License :: OSI Approved :: BSD License - Natural Language :: English - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Topic :: Scientific/Engineering :: Physics - -[options] -zip_safe = True -python_requires = >=3.8 -packages = find: -include_package_data = True -setup_requires = - setuptools_scm -install_requires = - glue-core[all,qt]>=1.2.2 - irispy-lmsal>=0.2.0rc1 - -[options.extras_require] -tests = - pytest -docs = - sphinx - sphinx-automodapi - sunpy-sphinx-theme - -[options.entry_points] -glue.plugins = - glue_solar=glue_solar:setup - -[tool:pytest] -testpaths = "glue_solar" "docs" -norecursedirs = ".tox" "build" "docs[\/]_build" "docs[\/]generated" "*.egg-info" "examples" ".history" -addopts = -p no:unraisableexception -p no:threadexception -filterwarnings = - error - # Do not fail on pytest config issues (i.e. missing plugins) but do show them - always::pytest.PytestConfigWarning - # - # A list of warnings to ignore follows. If you add to this list, you MUST - # add a comment or ideally a link to an issue that explains why the warning - # is being ignored - # - # - ignore:the imp module is deprecated in favour of importlib - ignore:numpy.ndarray size changed:RuntimeWarning - ignore:distutils Version classes are deprecated. Use packaging.version instead.:DeprecationWarning - ignore:numpy.ufunc size changed:RuntimeWarning [pycodestyle] max_line_length = 110 diff --git a/setup.py b/setup.py index 968f552..2d4f85f 100755 --- a/setup.py +++ b/setup.py @@ -1,25 +1,5 @@ -#!/usr/bin/env python -from setuptools import setup # isort:skip -import os -from itertools import chain +# We can remove this file when the default pip version +# that Python installs is pip >= 21.3 +from setuptools import setup -from setuptools.config import read_configuration - -################################################################################ -# Programmatically generate some extras combos. -################################################################################ -extras = read_configuration("setup.cfg")["options"]["extras_require"] - -# Dev is everything -extras["dev"] = list(chain(*extras.values())) - -# All is everything but tests and docs -exclude_keys = ("tests", "docs", "dev") -ex_extras = dict(filter(lambda i: i[0] not in exclude_keys, extras.items())) -# Concatenate all the values together for 'all' -extras["all"] = list(chain.from_iterable(ex_extras.values())) - -setup( - extras_require=extras, - use_scm_version={"write_to": os.path.join("glue_solar", "_version.py")}, -) +setup() diff --git a/tox.ini b/tox.ini index 41d078b..b72befb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,50 +1,88 @@ [tox] +min_version = 4.0 +requires = + tox-pypi-filter>=0.14 envlist = - py{38,39,310}{,-devdeps,-docs} + py{310,311,312} + build_docs + linkcheck codestyle -requires = - setuptools >= 30.3.0 - pip >= 19.3.1 -isolated_build = true [testenv] +allowlist_externals= + /bin/sh +# Run the tests in a temporary directory to make sure that we don't import anything from the source tree changedir = .tmp/{envname} description = run tests -passenv = +pass_env = + # A variable to tell tests we are on a CI system + CI + # Custom compiler locations (such as ccache) + CC + # Location of locales (needed by sphinx on some systems) + LOCALE_ARCHIVE + # If the user has set a LC override we should follow it + LC_ALL + # TBD DISPLAY + # TBD HOME setenv = - QT_DEBUG_PLUGINS = 0 - HIDE_PARFIVE_PROGESS = True - PYTEST_COMMAND = pytest -vvv -s -raR --pyargs glue_solar --cov-report=xml --cov=glue_solar --cov-config={toxinidir}/setup.cfg {toxinidir}/docs + devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple https://pypi.anaconda.org/liberfa/simple deps = - pytest-cov - # The devdeps factor is intended to be used to install the latest developer version. - # of key dependencies. - devdeps: git+https://github.com/glue-viz/glue.git -# The following indicates which extras_require from setup.cfg will be installed -# We use test and all so we don't install docs when we don't need it (as -# opposed to dev). extras = - all tests +commands_pre = + pip freeze --all --no-input commands = - {env:PYTEST_COMMAND} {posargs} + pytest \ + -vvv \ + -r fEs \ + --pyargs glue_solar \ + --cov-report=xml \ + --cov=glue_solar \ + --cov-config={toxinidir}/.coveragerc \ + {toxinidir}/docs \ + {posargs} -[testenv:docs] +[testenv:build_docs] changedir = docs description = Invoke sphinx-build to build the HTML docs -# Be verbose about the extras rather than using dev for clarity extras = all docs commands = - sphinx-build -j auto --color -W --keep-going -b html -d _build/.doctrees . _build/html {posargs} + pip freeze --all --no-input + sphinx-build \ + -j auto \ + --color \ + -W \ + --keep-going \ + -b html \ + -d _build/.doctrees \ + . \ + _build/html \ + {posargs} python -c 'import pathlib; print("Documentation available under file://\{0\}".format(pathlib.Path(r"{toxinidir}") / "docs" / "_build" / "index.html"))' +[testenv:linkcheck] +changedir = docs +description = Invoke sphinx-build to check the URLS within the docs +extras = + all + docs +commands = + pip freeze --all --no-input + sphinx-build \ + -qqq \ + --color \ + -b linkcheck \ + . \ + _build/html \ + {posargs} + [testenv:codestyle] -skipsdist = true skip_install = true description = Run all style and file checks with pre-commit deps =