From f760572935ef757ea6cd965d94618bf5774ba8a5 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 2 Dec 2022 14:10:20 -0500 Subject: [PATCH 01/82] feat: pyinstaller --- .github/workflows/standalone.yml | 75 +++++++++++++ CHANGES.rst | 4 + jdaviz/_astropy_init.py | 23 ++++ jdaviz/cli.py | 27 +++++ jdaviz/configs/cubeviz/cubeviz.ipynb | 41 +++++++ jdaviz/configs/default/default.ipynb | 50 +++++++++ jdaviz/configs/imviz/imviz.ipynb | 41 +++++++ jdaviz/configs/mosviz/mosviz.ipynb | 46 ++++++++ jdaviz/configs/specviz/specviz.ipynb | 41 +++++++ jdaviz/core/tools.py | 2 +- setup.cfg | 158 +++++++++++++++++++++++++++ standalone/jdaviz-cli-entrypoint.py | 23 ++++ standalone/jdaviz.spec | 89 +++++++++++++++ standalone/test.py | 12 ++ 14 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/standalone.yml create mode 100644 jdaviz/_astropy_init.py create mode 100644 jdaviz/configs/cubeviz/cubeviz.ipynb create mode 100644 jdaviz/configs/default/default.ipynb create mode 100644 jdaviz/configs/imviz/imviz.ipynb create mode 100644 jdaviz/configs/mosviz/mosviz.ipynb create mode 100644 jdaviz/configs/specviz/specviz.ipynb create mode 100644 setup.cfg create mode 100644 standalone/jdaviz-cli-entrypoint.py create mode 100644 standalone/jdaviz.spec create mode 100644 standalone/test.py diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml new file mode 100644 index 0000000000..842590bf9f --- /dev/null +++ b/.github/workflows/standalone.yml @@ -0,0 +1,75 @@ +name: Build standalone + +on: + push: + branches: + - main + - 'v*' + tags: + - 'v*' + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-standalone + cancel-in-progress: true + +defaults: + run: + shell: bash {0} + + +jobs: + build_binary: + runs-on: ${{ matrix.os }}-latest + strategy: + matrix: + os: [ubuntu, windows, macos] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install jdaviz + run: pip install . + + - name: Install pyinstaller + run: pip install pyinstaller + + - name: Create standalone binary + run: (cd standalone; pyinstaller ./jdaviz.spec) + + - name: Run jdaviz cmd in background + run: ./standalone/dist/jdaviz-cli/jdaviz-cli imviz& + + - name: Install playwright + run: (pip install playwright; playwright install chromium) + + - name: Install pytest + run: pip install pytest-playwright + + - name: Wait for Voila to get online + uses: ifaxity/wait-on-action@v1 + with: + resource: tcp:8866 + timeout: 60000 + + - name: Test standalone + run: (cd standalone; touch pytest.ini; JUPYTER_PLATFORM_DIRS=1 pytest test.py --video=on) + + - name: Upload Test artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-results-${{ matrix.os }} + path: standalone/test-results + + - name: Upload jdaviz standalone + if: always() + uses: actions/upload-artifact@v3 + with: + name: jdaviz-standlone-${{ matrix.os }} + path: standalone/dist/jdaviz-cli diff --git a/CHANGES.rst b/CHANGES.rst index 4a7750a4a5..617755f9e5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -360,6 +360,10 @@ New Features - Model fitting: API and UI to re-estimate model parameters based on current data/subset selection. [#1952] +- CLI launchers no longer require data to be specified [#1960] + +- Added direct launchers for each config (e.g. ``specviz``) [#1960] + Cubeviz ^^^^^^^ diff --git a/jdaviz/_astropy_init.py b/jdaviz/_astropy_init.py new file mode 100644 index 0000000000..95ea9ea7d6 --- /dev/null +++ b/jdaviz/_astropy_init.py @@ -0,0 +1,23 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +import os +import sys + +if not hasattr(sys.modules["__main__"], "__file__"): + # this happens under pyinstaller when running jdaviz cli + # which triggers an error in astropy, so we set it to the + # executable path of the cli executable + sys.modules["__main__"].__file__ = sys.executable + + +from astropy.tests.runner import TestRunner + +__all__ = ['__version__', 'test'] + +try: + from .version import version as __version__ +except ImportError: + __version__ = '' + +# Create the test function for self test +test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) +test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) diff --git a/jdaviz/cli.py b/jdaviz/cli.py index 2c673da2b2..1fae07245f 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -1,5 +1,6 @@ # Command-line interface for jdaviz +import inspect import os import pathlib import sys @@ -10,9 +11,11 @@ from jdaviz import __version__ from jdaviz.app import _verbosity_levels +from jdaviz import configs __all__ = ['main'] +CONFIGS_DIR = str(pathlib.Path(inspect.getfile(configs)).parent) JDAVIZ_DIR = pathlib.Path(__file__).parent.resolve() @@ -144,3 +147,27 @@ def _main(config=None): main(filepaths=args.filepaths, layout=layout, instrument=args.instrument, browser=args.browser, theme=args.theme, verbosity=args.verbosity, history_verbosity=args.history_verbosity, hotreload=args.hotreload) + + +def _specviz(): + _main(config='specviz') + + +def _specviz2d(): + _main(config='specviz2d') + + +def _imviz(): + _main(config='imviz') + + +def _cubeviz(): + _main(config='cubeviz') + + +def _mosviz(): + _main(config='mosviz') + + +if __name__ == "__main__": + _main() diff --git a/jdaviz/configs/cubeviz/cubeviz.ipynb b/jdaviz/configs/cubeviz/cubeviz.ipynb new file mode 100644 index 0000000000..886d36661c --- /dev/null +++ b/jdaviz/configs/cubeviz/cubeviz.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# PREFIX\n", + "from jdaviz import Cubeviz\n", + "\n", + "cubeviz = Cubeviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " cubeviz.load_data('DATA_FILENAME')\n", + "cubeviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6-final" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/default/default.ipynb b/jdaviz/configs/default/default.ipynb new file mode 100644 index 0000000000..b9d09d2ba8 --- /dev/null +++ b/jdaviz/configs/default/default.ipynb @@ -0,0 +1,50 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "from jdaviz.app import Application\n", + "app = Application(configuration='default')\n", + "app.verbosity = 'JDAVIZ_VERBOSITY'\n", + "app.history_verbosity = 'JDAVIZ_HISTORY_VERBOSITY'\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " app.load_data('DATA_FILENAME')\n", + "app" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/imviz/imviz.ipynb b/jdaviz/configs/imviz/imviz.ipynb new file mode 100644 index 0000000000..51c6fff42a --- /dev/null +++ b/jdaviz/configs/imviz/imviz.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# PREFIX\n", + "from jdaviz import Imviz\n", + "\n", + "imviz = Imviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " imviz.load_data('DATA_FILENAME')\n", + "imviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/mosviz/mosviz.ipynb b/jdaviz/configs/mosviz/mosviz.ipynb new file mode 100644 index 0000000000..e9350d166f --- /dev/null +++ b/jdaviz/configs/mosviz/mosviz.ipynb @@ -0,0 +1,46 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "# PREFIX\n", + "import pathlib\n", + "from jdaviz import Mosviz\n", + "\n", + "data_path = pathlib.Path('DATA_FILENAME')\n", + "\n", + "mosviz = Mosviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " mosviz.load_data(directory=data_path, instrument='INSTRUMENT')\n", + "mosviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/specviz/specviz.ipynb b/jdaviz/configs/specviz/specviz.ipynb new file mode 100644 index 0000000000..1f9f5fcd23 --- /dev/null +++ b/jdaviz/configs/specviz/specviz.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# PREFIX\n", + "from jdaviz import Specviz\n", + "\n", + "specviz = Specviz(verbosity='JDAVIZ_VERBOSITY', history_verbosity='JDAVIZ_HISTORY_VERBOSITY')\n", + "data_path = 'DATA_FILENAME'\n", + "if data_path:\n", + " specviz.load_data(data_path)\n", + "specviz.app" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6-final" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/core/tools.py b/jdaviz/core/tools.py index 39ba50cf3a..eaba870ddc 100644 --- a/jdaviz/core/tools.py +++ b/jdaviz/core/tools.py @@ -18,7 +18,7 @@ __all__ = [] -ICON_DIR = os.path.join(os.path.dirname(__file__), '..', 'data', 'icons') +ICON_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'data', 'icons')) # Override icons for built-in tools from glue-jupyter diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..479de7bc31 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,158 @@ +[metadata] +name = jdaviz +author = JDADF Developers +author_email = rosteen@stsci.edu +license = BSD 3-Clause +license_file = LICENSE.rst +url = https://jdaviz.readthedocs.io/en/latest/ +description = Astronomical data analysis development leveraging the Jupyter platform +long_description = file: README.rst +long_description_content_type = text/x-rst +edit_on_github = True +github_project = spacetelescope/jdaviz + +[options] +zip_safe = False +packages = find: +include_package_data = True +python_requires = >=3.8 +setup_requires = setuptools_scm +install_requires = + packaging + astropy>=4.3 + matplotlib + traitlets>=5.0.5 + bqplot>=0.12.36 + bqplot-image-gl>=1.4.11 + glue-core>=1.6.0 + glue-jupyter>=0.15.0 + echo>=0.5.0 + ipykernel>=6.19.4 + ipyvue>=1.6 + ipyvuetify>=1.7.0 + ipysplitpanes>=0.1.0 + ipygoldenlayout>=0.3.0 + ipywidgets>=8 + voila>=0.4 + pyyaml>=5.4.1 + specutils>=1.9 + specreduce>=1.3.0,<1.4.0 + photutils>=1.4 + glue-astronomy>=0.5.1 + asteval>=0.9.23 + idna + # vispy is an indirect dependency, but older vispy's don't play nice with jdaviz install + vispy>=0.6.5 + asdf>=2.14.3 + gwcs>=0.16.1 + regions>=0.6 + scikit-image + sidecar>=0.5.2 + ipypopout>=0.0.11 + astroquery + +[options.extras_require] +test = + pytest + pytest-astropy + pytest-tornasync +docs = + sphinx-rtd-theme + sphinx-astropy + +[options.package_data] +jdaviz = + data/* + data/*/* + *.vue + components/*.vue + configs/*/*/*/*.vue + configs/*/*.yaml + configs/*/*.ipynb +jdaviz.configs.imviz.tests = data/* + +[options.entry_points] +console_scripts = + jdaviz = jdaviz.cli:_main + specviz = jdaviz.cli:_specviz + specviz2d = jdaviz.cli:_specviz2d + imviz = jdaviz.cli:_imviz + cubeviz = jdaviz.cli:_cubeviz + mosviz = jdaviz.cli:_mosviz +gui_scripts = +jdaviz_plugins = + default = jdaviz.configs.default + cubeviz = jdaviz.configs.cubeviz + specviz = jdaviz.configs.specviz + mosviz = jdaviz.configs.mosviz + imviz = jdaviz.configs.imviz + +[tool:pytest] +testpaths = "jdaviz" "docs" +astropy_header = true +doctest_plus = enabled +text_file_format = rst +addopts = --doctest-rst --import-mode=append +filterwarnings = + error + ignore:numpy\.ufunc size changed:RuntimeWarning + ignore:numpy\.ndarray size changed:RuntimeWarning + ignore:Numpy has detected that you:DeprecationWarning + ignore:distutils Version classes are deprecated:DeprecationWarning + ignore:Passing unrecognized arguments to super:DeprecationWarning + ignore:.*With traitlets 4\.1, metadata should be set using the \.tag\(\) method:DeprecationWarning + ignore:Widget.* is deprecated:DeprecationWarning + ignore:.*np\.bool8.*is a deprecated alias for:DeprecationWarning + ignore:.*np\.uint0.*is a deprecated alias for:DeprecationWarning + ignore:.*np\.int0.*is a deprecated alias for:DeprecationWarning + ignore:zmq\.eventloop\.ioloop is deprecated in pyzmq:DeprecationWarning + ignore::DeprecationWarning:glue + ignore::DeprecationWarning:bqplot + ignore::DeprecationWarning:bqplot_image_gl + ignore::DeprecationWarning:bqscales + ignore::DeprecationWarning:traittypes + ignore::DeprecationWarning:voila + ignore:::specutils.spectra.spectrum1d + +[flake8] +max-line-length = 100 +# E123: closing bracket does not match indentation of opening bracket's line +# E126: continuation line over-indented for hanging indent +# E226: missing whitespace around arithmetic operator +# E402: Module level import not at top of file +# W503: line break before binary operator +# W504: line break after binary operator +ignore = E123,E126,E226,E402,W503,W504 + +[coverage:run] +omit = + jdaviz/_astropy_init* + jdaviz/conftest.py + jdaviz/*setup_package* + jdaviz/tests/* + jdaviz/*/tests/* + jdaviz/extern/* + jdaviz/version* + */jdaviz/_astropy_init* + */jdaviz/conftest.py + */jdaviz/*setup_package* + */jdaviz/tests/* + */jdaviz/*/tests/* + */jdaviz/extern/* + */jdaviz/version* + +[coverage:report] +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + # Don't complain about packages we have installed + except ImportError + # Don't complain if tests don't hit assertions + raise AssertionError + raise NotImplementedError + # Don't complain about script hooks + def main\(.*\): + # Ignore branches that don't pertain to this version of Python + pragma: py{ignore_python_version} + # Don't complain about IPython completion helper + def _ipython_key_completions_ diff --git a/standalone/jdaviz-cli-entrypoint.py b/standalone/jdaviz-cli-entrypoint.py new file mode 100644 index 0000000000..a4d2c0567e --- /dev/null +++ b/standalone/jdaviz-cli-entrypoint.py @@ -0,0 +1,23 @@ +import sys + + +def start_as_kernel(): + # similar to https://github.com/astrofrog/voila-qt-app/blob/master/voila_demo.py + import sys + + from ipykernel import kernelapp as app + app.launch_new_instance() + sys.argv = [app.__file__, sys.argv[3:]] + + +if __name__ == "__main__": + # When voila starts a kernel under pyinstaller, it will use sys.executable + # (which is this entry point again) + # if called like [sys.argv[0], "-m", "ipykernel_launcher", ...] + if len(sys.argv) >= 3 and sys.argv[1] == "-m" and sys.argv[2] == "ipykernel_launcher": + # it is important that we do not import jdaviz top level + # as that would cause it to import ipywidgets before the kernel is started + start_as_kernel() + else: + import jdaviz.cli + jdaviz.cli._main() diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec new file mode 100644 index 0000000000..eb5bde39cf --- /dev/null +++ b/standalone/jdaviz.spec @@ -0,0 +1,89 @@ +# -*- mode: python ; coding: utf-8 -*- +import sys +from pathlib import Path + +import bqplot +import debugpy +import glue +import glue_jupyter +import ipypopout +import photutils +import regions +from PyInstaller.utils.hooks import collect_all, collect_data_files, collect_submodules + +datas = [ + (Path(sys.prefix) / "share" / "jupyter", "./share/jupyter"), + (Path(sys.prefix) / "etc" / "jupyter", "./etc/jupyter"), + *collect_data_files("regions"), + *collect_data_files("photutils"), + *collect_data_files("debugpy"), + *collect_data_files("glue"), + *collect_data_files("glue_jupyter"), + *collect_data_files("bqplot"), + *collect_data_files("jdaviz"), + *collect_data_files("ipypopout"), +] +binaries = [] +# jdaviz is not imported condinally in jdaviz-cli-entrypoint.py, so a hidden import +hiddenimports = [] +hiddenimports += collect_submodules("jdaviz") +hiddenimports += collect_submodules("regions") +hiddenimports += collect_submodules("photutils") +hiddenimports += collect_submodules("jupyter_client") +tmp_ret = collect_all("astropy") +datas += tmp_ret[0] +binaries += tmp_ret[1] +hiddenimports += tmp_ret[2] +# tmp_ret = collect_all('.') +datas += tmp_ret[0] +binaries += tmp_ret[1] +hiddenimports += tmp_ret[2] + + +block_cipher = None + + +a = Analysis( + ["jdaviz-cli-entrypoint.py"], + pathex=[], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name="jdaviz-cli", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name="jdaviz-cli", +) diff --git a/standalone/test.py b/standalone/test.py new file mode 100644 index 0000000000..fb3276a7fa --- /dev/null +++ b/standalone/test.py @@ -0,0 +1,12 @@ +import re + +from playwright.sync_api import Page, expect + + +def test_voila_basics(page: Page): + page.goto("http://localhost:8866/") + + # basic voila is loaded + page.locator("body.theme-light").wait_for() + # when jdaviz is loaded (button at the top left) + page.locator("text=Import Data").wait_for() From b520aef748d1db7fe6ba8f186b149dec6983433c Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 16 May 2023 12:07:41 +0200 Subject: [PATCH 02/82] codesign osx --- .github/workflows/standalone.yml | 9 +++++++++ standalone/jdaviz.spec | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 842590bf9f..65dd6bb7b0 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -5,6 +5,7 @@ on: branches: - main - 'v*' + - 'pyinstaller_v2' tags: - 'v*' pull_request: @@ -25,6 +26,12 @@ jobs: matrix: os: [ubuntu, windows, macos] steps: + - name: Import Certificates (macOS) + uses: apple-actions/import-codesign-certs@v1 + if: ${{ matrix.os == 'macos' }} + with: + p12-file-base64: ${{ secrets.DEV_ID_APP_CERT }} + p12-password: ${{ secrets.DEV_ID_APP_PASSWORD }} - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -40,6 +47,8 @@ jobs: run: pip install pyinstaller - name: Create standalone binary + env: + DEVELOPER_ID_APPLICATION: ${{ secrets.DEVELOPER_ID_APPLICATION }} run: (cd standalone; pyinstaller ./jdaviz.spec) - name: Run jdaviz cmd in background diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index eb5bde39cf..842e5025ee 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -1,16 +1,20 @@ # -*- mode: python ; coding: utf-8 -*- import sys from pathlib import Path +import os import bqplot import debugpy import glue +import jdaviz import glue_jupyter import ipypopout import photutils import regions from PyInstaller.utils.hooks import collect_all, collect_data_files, collect_submodules +codesign_identity = os.environ.get("DEVELOPER_ID_APPLICATION") + datas = [ (Path(sys.prefix) / "share" / "jupyter", "./share/jupyter"), (Path(sys.prefix) / "etc" / "jupyter", "./etc/jupyter"), @@ -74,8 +78,9 @@ exe = EXE( disable_windowed_traceback=False, argv_emulation=False, target_arch=None, - codesign_identity=None, + codesign_identity=codesign_identity, entitlements_file=None, + ) coll = COLLECT( exe, @@ -87,3 +92,8 @@ coll = COLLECT( upx_exclude=[], name="jdaviz-cli", ) +app = BUNDLE(exe, coll, + name='jdaviz.app', + icon=None, + bundle_identifier='edu.stsci.jdaviz', + version=jdaviz.__version__) From 58730c308c7b8dac8e6edb2990448abd74d75530 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 16 May 2023 15:16:04 +0200 Subject: [PATCH 03/82] debug: try without codesign --- standalone/jdaviz.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index 842e5025ee..b47d6ad844 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -13,7 +13,7 @@ import photutils import regions from PyInstaller.utils.hooks import collect_all, collect_data_files, collect_submodules -codesign_identity = os.environ.get("DEVELOPER_ID_APPLICATION") +codesign_identity = None#os.environ.get("DEVELOPER_ID_APPLICATION") datas = [ (Path(sys.prefix) / "share" / "jupyter", "./share/jupyter"), From 47f966bb2f8a68f0d0170f0de66ac7660850afa7 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 6 Jun 2023 11:44:25 +0200 Subject: [PATCH 04/82] does not need arguments --- standalone/jdaviz-cli-entrypoint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/standalone/jdaviz-cli-entrypoint.py b/standalone/jdaviz-cli-entrypoint.py index a4d2c0567e..ade6986813 100644 --- a/standalone/jdaviz-cli-entrypoint.py +++ b/standalone/jdaviz-cli-entrypoint.py @@ -20,4 +20,5 @@ def start_as_kernel(): start_as_kernel() else: import jdaviz.cli - jdaviz.cli._main() + # should change this to _main, but now it doesn't need arguments + jdaviz.cli._imviz() From 2f9041a5a8feee5f58cb1116e65c1d17d20b5113 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 6 Jun 2023 11:45:18 +0200 Subject: [PATCH 05/82] use hooks --- standalone/hooks/hook-glue.py | 4 ++++ standalone/hooks/hook-glue_jupyter.py | 4 ++++ standalone/hooks/hook-jdaviz.py | 4 ++++ standalone/jdaviz.spec | 4 +--- 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 standalone/hooks/hook-glue.py create mode 100644 standalone/hooks/hook-glue_jupyter.py create mode 100644 standalone/hooks/hook-jdaviz.py diff --git a/standalone/hooks/hook-glue.py b/standalone/hooks/hook-glue.py new file mode 100644 index 0000000000..ab90a39ded --- /dev/null +++ b/standalone/hooks/hook-glue.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata + +datas = collect_data_files('glue', include_py_files=True) +datas += copy_metadata('glue-core') diff --git a/standalone/hooks/hook-glue_jupyter.py b/standalone/hooks/hook-glue_jupyter.py new file mode 100644 index 0000000000..89df6f2f0d --- /dev/null +++ b/standalone/hooks/hook-glue_jupyter.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata + +datas = collect_data_files('glue_jupyter', include_py_files=True) +datas += copy_metadata('glue_jupyter') diff --git a/standalone/hooks/hook-jdaviz.py b/standalone/hooks/hook-jdaviz.py new file mode 100644 index 0000000000..461acff37e --- /dev/null +++ b/standalone/hooks/hook-jdaviz.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files, copy_metadata + +datas = collect_data_files('jdaviz', include_py_files=True) +datas += copy_metadata('jdaviz') diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index b47d6ad844..a419a8e253 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -24,13 +24,11 @@ datas = [ *collect_data_files("glue"), *collect_data_files("glue_jupyter"), *collect_data_files("bqplot"), - *collect_data_files("jdaviz"), *collect_data_files("ipypopout"), ] binaries = [] # jdaviz is not imported condinally in jdaviz-cli-entrypoint.py, so a hidden import hiddenimports = [] -hiddenimports += collect_submodules("jdaviz") hiddenimports += collect_submodules("regions") hiddenimports += collect_submodules("photutils") hiddenimports += collect_submodules("jupyter_client") @@ -53,7 +51,7 @@ a = Analysis( binaries=binaries, datas=datas, hiddenimports=hiddenimports, - hookspath=[], + hookspath=["hooks"], hooksconfig={}, runtime_hooks=[], excludes=[], From 5569a4cca4b48393ac42c5d6ba5782db2f3dabf0 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 6 Jun 2023 12:43:26 +0200 Subject: [PATCH 06/82] also download the app --- .github/workflows/standalone.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 65dd6bb7b0..fb0ba0997e 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -81,4 +81,6 @@ jobs: uses: actions/upload-artifact@v3 with: name: jdaviz-standlone-${{ matrix.os }} - path: standalone/dist/jdaviz-cli + path: | + standalone/dist/jdaviz-cli + standalone/dist/jdaviz.app From ba8ee5a0e319c7b527aba458ba089cfe4f893694 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 6 Jun 2023 15:37:53 +0200 Subject: [PATCH 07/82] use branch of pyinstaller --- .github/workflows/standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index fb0ba0997e..3e50ee5603 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -44,7 +44,7 @@ jobs: run: pip install . - name: Install pyinstaller - run: pip install pyinstaller + run: pip install https://github.com/maartenbreddels/pyinstaller/archive/fix_relocate_osx_libraries.zip - name: Create standalone binary env: From ef4b254d9da080b26d9135138e1e2cdf6dada6b2 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 10:30:35 +0200 Subject: [PATCH 08/82] better hooks --- .github/workflows/standalone.yml | 2 +- standalone/hooks/hook-debugpy.py | 8 ++++++++ standalone/hooks/hook-skimage.py | 12 ++++++++++++ standalone/jdaviz.spec | 3 ++- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 standalone/hooks/hook-debugpy.py create mode 100644 standalone/hooks/hook-skimage.py diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 3e50ee5603..fb0ba0997e 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -44,7 +44,7 @@ jobs: run: pip install . - name: Install pyinstaller - run: pip install https://github.com/maartenbreddels/pyinstaller/archive/fix_relocate_osx_libraries.zip + run: pip install pyinstaller - name: Create standalone binary env: diff --git a/standalone/hooks/hook-debugpy.py b/standalone/hooks/hook-debugpy.py new file mode 100644 index 0000000000..5b7d0813ff --- /dev/null +++ b/standalone/hooks/hook-debugpy.py @@ -0,0 +1,8 @@ +from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs + +datas = collect_data_files("debugpy") +# we are picking up debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_linux_amd64.dylib +datas = filter(lambda x: not x[0].endswith('.dylib'), datas) +# binaries = collect_dynamic_libs('omp') + +# breakpoint() \ No newline at end of file diff --git a/standalone/hooks/hook-skimage.py b/standalone/hooks/hook-skimage.py new file mode 100644 index 0000000000..3f383b4319 --- /dev/null +++ b/standalone/hooks/hook-skimage.py @@ -0,0 +1,12 @@ +from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs + +datas = collect_data_files("skimage", includes=["*.pyi"]) +# osx does not like the .dylib directory with signing +# [('.../site-packages/skimage/.dylibs/libomp.dylib', 'skimage/.dylibs')] +binaries = collect_dynamic_libs('skimage') +if binaries and binaries[0][0].endswith('.dylib'): + assert len(binaries) == 1 + assert binaries[0][0].endswith('.dylibs/libomp.dylib') + binaries = [ + (binaries[0][0], 'skimage'), + ] diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index a419a8e253..533f22fabe 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -20,7 +20,7 @@ datas = [ (Path(sys.prefix) / "etc" / "jupyter", "./etc/jupyter"), *collect_data_files("regions"), *collect_data_files("photutils"), - *collect_data_files("debugpy"), + # *collect_data_files("debugpy"), *collect_data_files("glue"), *collect_data_files("glue_jupyter"), *collect_data_files("bqplot"), @@ -32,6 +32,7 @@ hiddenimports = [] hiddenimports += collect_submodules("regions") hiddenimports += collect_submodules("photutils") hiddenimports += collect_submodules("jupyter_client") +hiddenimports += collect_submodules("debugpy") tmp_ret = collect_all("astropy") datas += tmp_ret[0] binaries += tmp_ret[1] From 02e7025c83fcf474e201c461f1f208d98f016fc0 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 12:29:42 +0200 Subject: [PATCH 09/82] pin to 5.11 Otherwise we need to do at least: + rm -rf standalone/dist/jdaviz.app/Contents/MacOS/jedi/third_party/typeshed/stdlib/ + rm -rf standalone/dist/jdaviz.app/Contents/MacOS/**/*.dist-info --- .github/workflows/standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index fb0ba0997e..f103e78e75 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -44,7 +44,7 @@ jobs: run: pip install . - name: Install pyinstaller - run: pip install pyinstaller + run: pip install pyinstaller==5.11 - name: Create standalone binary env: From b0c526d6af4b914cf34e4c5f35df66eb0aeecf13 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 14:08:03 +0200 Subject: [PATCH 10/82] fix: maintain symlinks by zipping, upload-artifact does not support it --- .github/workflows/standalone.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index f103e78e75..9050df9235 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -76,6 +76,11 @@ jobs: name: test-results-${{ matrix.os }} path: standalone/test-results + - name: Zip OSX app to maintain symlinks + # if we do not call always() GHA will && with success() + if: always() && ${{ matrix.os == 'macos' }} + run: (cd standalone/dist; zip -r --symlinks jdaviz.zip jdaviz.app ) + - name: Upload jdaviz standalone if: always() uses: actions/upload-artifact@v3 @@ -83,4 +88,4 @@ jobs: name: jdaviz-standlone-${{ matrix.os }} path: | standalone/dist/jdaviz-cli - standalone/dist/jdaviz.app + standalone/dist/jdaviz.zip From afebeee6ddfe95596aaa4e726447883838a31ea8 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 14:28:14 +0200 Subject: [PATCH 11/82] GHA logic? --- .github/workflows/standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 9050df9235..bb2ab21f40 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -78,7 +78,7 @@ jobs: - name: Zip OSX app to maintain symlinks # if we do not call always() GHA will && with success() - if: always() && ${{ matrix.os == 'macos' }} + if: ${{ always() && (matrix.os == 'macos') }} run: (cd standalone/dist; zip -r --symlinks jdaviz.zip jdaviz.app ) - name: Upload jdaviz standalone From 309860a23c0772a42ff108f5bc76d326204b9acd Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 15:15:37 +0200 Subject: [PATCH 12/82] code sign on gha --- standalone/jdaviz.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index 533f22fabe..08f3d2bced 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -13,7 +13,7 @@ import photutils import regions from PyInstaller.utils.hooks import collect_all, collect_data_files, collect_submodules -codesign_identity = None#os.environ.get("DEVELOPER_ID_APPLICATION") +codesign_identity = os.environ.get("DEVELOPER_ID_APPLICATION") datas = [ (Path(sys.prefix) / "share" / "jupyter", "./share/jupyter"), From 9577b2a3be3df2fd2f20883eb0543c061ebf6799 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 15:56:08 +0200 Subject: [PATCH 13/82] run notary tool on gha --- .github/workflows/standalone.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index bb2ab21f40..6b0106bca3 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -51,6 +51,18 @@ jobs: DEVELOPER_ID_APPLICATION: ${{ secrets.DEVELOPER_ID_APPLICATION }} run: (cd standalone; pyinstaller ./jdaviz.spec) + - name: Remove invalid files for OSX + # hopefully we can improve this in the future + # by using good hooks + if: ${{ matrix.os == 'macos' }} + run: | + rm -rf dist/jdaviz.app/Contents/Resources/skimage/.dylibs + + - name: Notary step + if: ${{ matrix.os == 'macos' }} + run: | + xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }} + - name: Run jdaviz cmd in background run: ./standalone/dist/jdaviz-cli/jdaviz-cli imviz& From c80aad500703fd5893acb3c2a9ffe4308b0814ec Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 16:19:12 +0200 Subject: [PATCH 14/82] fix: redo codesign after modifications --- .github/workflows/standalone.yml | 9 ++++++++- standalone/entitlements.plist | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 standalone/entitlements.plist diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 6b0106bca3..d4963c150e 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -26,6 +26,7 @@ jobs: matrix: os: [ubuntu, windows, macos] steps: + # osx signing based on https://melatonin.dev/blog/how-to-code-sign-and-notarize-macos-audio-plugins-in-ci/ - name: Import Certificates (macOS) uses: apple-actions/import-codesign-certs@v1 if: ${{ matrix.os == 'macos' }} @@ -58,7 +59,13 @@ jobs: run: | rm -rf dist/jdaviz.app/Contents/Resources/skimage/.dylibs - - name: Notary step + - name: Codesign (OSX) + if: ${{ matrix.os == 'macos' }} + run: | + cd standalone/dist + codesign --deep --force --options=runtime --entitlements entitlements.plist --sign ${{ secrets.DEVELOPER_ID_APPLICATION }} --timestamp jdaviz.app + + - name: Notary step (OSX) if: ${{ matrix.os == 'macos' }} run: | xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }} diff --git a/standalone/entitlements.plist b/standalone/entitlements.plist new file mode 100644 index 0000000000..2ac1f14b34 --- /dev/null +++ b/standalone/entitlements.plist @@ -0,0 +1,13 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file From 36872663d25f4da86c4d5ed71ca2960771227e8c Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 16:20:36 +0200 Subject: [PATCH 15/82] fix: reorder zipping and notary step --- .github/workflows/standalone.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index d4963c150e..ae5be76156 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -65,6 +65,11 @@ jobs: cd standalone/dist codesign --deep --force --options=runtime --entitlements entitlements.plist --sign ${{ secrets.DEVELOPER_ID_APPLICATION }} --timestamp jdaviz.app + - name: Zip OSX app to maintain symlinks and to sign + # if we do not call always() GHA will && with success() + if: ${{ always() && (matrix.os == 'macos') }} + run: (cd standalone/dist; zip -r --symlinks jdaviz.zip jdaviz.app ) + - name: Notary step (OSX) if: ${{ matrix.os == 'macos' }} run: | @@ -95,11 +100,6 @@ jobs: name: test-results-${{ matrix.os }} path: standalone/test-results - - name: Zip OSX app to maintain symlinks - # if we do not call always() GHA will && with success() - if: ${{ always() && (matrix.os == 'macos') }} - run: (cd standalone/dist; zip -r --symlinks jdaviz.zip jdaviz.app ) - - name: Upload jdaviz standalone if: always() uses: actions/upload-artifact@v3 From 6c5da95b703aa37b8ee53a71fe06b4c4d9747f12 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 16:30:48 +0200 Subject: [PATCH 16/82] remove invalid symlink --- .github/workflows/standalone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index ae5be76156..b119eca43d 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -57,6 +57,7 @@ jobs: # by using good hooks if: ${{ matrix.os == 'macos' }} run: | + rm -rf dist/jdaviz.app/Contents/Contents/MacOS/skimage/.dylibs rm -rf dist/jdaviz.app/Contents/Resources/skimage/.dylibs - name: Codesign (OSX) From a7c7e34dd11d8cc0dd80a3e48906f8cfe1667daf Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 17:07:42 +0200 Subject: [PATCH 17/82] make sure the program executes --- .github/workflows/standalone.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index b119eca43d..8105c19ec1 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -76,6 +76,11 @@ jobs: run: | xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }} + - name: Validate app (OSX) + if: ${{ matrix.os == 'macos' }} + run: | + spctl -a -vvv -t execute standalone/dist/jdaviz.app + - name: Run jdaviz cmd in background run: ./standalone/dist/jdaviz-cli/jdaviz-cli imviz& From 76657d1ef08b57b74f690c0c0375e221c2350702 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 17:07:54 +0200 Subject: [PATCH 18/82] fix path of entitlements file --- .github/workflows/standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 8105c19ec1..ab3fed92d8 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -64,7 +64,7 @@ jobs: if: ${{ matrix.os == 'macos' }} run: | cd standalone/dist - codesign --deep --force --options=runtime --entitlements entitlements.plist --sign ${{ secrets.DEVELOPER_ID_APPLICATION }} --timestamp jdaviz.app + codesign --deep --force --options=runtime --entitlements ../entitlements.plist --sign ${{ secrets.DEVELOPER_ID_APPLICATION }} --timestamp jdaviz.app - name: Zip OSX app to maintain symlinks and to sign # if we do not call always() GHA will && with success() From 8c0e2afa7c7a34f7d607f4214c09867442a873b6 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 17:54:19 +0200 Subject: [PATCH 19/82] add comment for hint with the osx fix --- .github/workflows/standalone.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index ab3fed92d8..276fec44f8 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -55,9 +55,11 @@ jobs: - name: Remove invalid files for OSX # hopefully we can improve this in the future # by using good hooks + # i think the issue is that we have a . in the name, there are many + # google hits on pyqt having the same issue if: ${{ matrix.os == 'macos' }} run: | - rm -rf dist/jdaviz.app/Contents/Contents/MacOS/skimage/.dylibs + rm -rf dist/jdaviz.app/Contents/MacOS/skimage/.dylibs rm -rf dist/jdaviz.app/Contents/Resources/skimage/.dylibs - name: Codesign (OSX) From 42cfcf292b97d8ab702acff05e88fdd0e17e0484 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 17:55:22 +0200 Subject: [PATCH 20/82] gpt assisted way of running the log tool on failure --- .github/workflows/standalone.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 276fec44f8..ab93bd826d 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -76,7 +76,15 @@ jobs: - name: Notary step (OSX) if: ${{ matrix.os == 'macos' }} run: | - xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }} + output=$(xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true + echo "$output" + if [[ $output == *"status: Accepted"* ]]; then + uuid=$(echo "$output" | awk '/id:/ {print $2}') + echo "UUID: $uuid" + else + uuid=$(echo "$output" | awk '/id:/ {print $2}') + xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true $uuid + fi - name: Validate app (OSX) if: ${{ matrix.os == 'macos' }} From e7cfa4956e4a51936a558b9baa53eaa874eb047c Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 18:29:51 +0200 Subject: [PATCH 21/82] fix syntax error --- .github/workflows/standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index ab93bd826d..4ace8ab54c 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -83,7 +83,7 @@ jobs: echo "UUID: $uuid" else uuid=$(echo "$output" | awk '/id:/ {print $2}') - xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true $uuid + xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }} $uuid || true fi - name: Validate app (OSX) From f70642efe323a041eb04175d396a23adfa11aab5 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 18:30:01 +0200 Subject: [PATCH 22/82] fix path --- .github/workflows/standalone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 4ace8ab54c..5796608f41 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -59,8 +59,8 @@ jobs: # google hits on pyqt having the same issue if: ${{ matrix.os == 'macos' }} run: | - rm -rf dist/jdaviz.app/Contents/MacOS/skimage/.dylibs - rm -rf dist/jdaviz.app/Contents/Resources/skimage/.dylibs + rm -rf standalone/dist/jdaviz.app/Contents/MacOS/skimage/.dylibs + rm -rf standalone/dist/jdaviz.app/Contents/Resources/skimage/.dylibs - name: Codesign (OSX) if: ${{ matrix.os == 'macos' }} From c0834763ad6eaae06d03a4020c81e827b8d8e210 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 20:02:52 +0200 Subject: [PATCH 23/82] comments for the future --- .github/workflows/standalone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 5796608f41..8961715e0b 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -57,6 +57,8 @@ jobs: # by using good hooks # i think the issue is that we have a . in the name, there are many # google hits on pyqt having the same issue + # and we might be able to remove it after https://github.com/pyinstaller/pyinstaller/pull/7619 + # is released (pyinstaller 5.13 probably) if: ${{ matrix.os == 'macos' }} run: | rm -rf standalone/dist/jdaviz.app/Contents/MacOS/skimage/.dylibs From 7c42fceeed0fa5e0f32f639f2cb09d6c74089315 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 20:03:25 +0200 Subject: [PATCH 24/82] fix uuid parsing and re-zip the app after notary step --- .github/workflows/standalone.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 8961715e0b..b536f58368 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -80,13 +80,16 @@ jobs: run: | output=$(xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true echo "$output" + uuid=$(echo "$output" | awk -F '[ :]+' '/id:/ {print $2; exit}') + echo "UUID: $uuid" if [[ $output == *"status: Accepted"* ]]; then - uuid=$(echo "$output" | awk '/id:/ {print $2}') - echo "UUID: $uuid" + echo "Great, notarization succeeded!" else - uuid=$(echo "$output" | awk '/id:/ {print $2}') + echo "Log output for failed notarization: $uuid" xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }} $uuid || true fi + # re-zip it + (cd standalone/dist; rm jdaviz.zip; zip -r --symlinks jdaviz.zip jdaviz.app ) - name: Validate app (OSX) if: ${{ matrix.os == 'macos' }} From dbd81426ca27edbaaf3eec2e006801c811fd70ec Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 20:08:52 +0200 Subject: [PATCH 25/82] fix: use ditto instead of zip from https://developer.apple.com/forums/thread/116831 zip might cause issues with utf8 or metadata. --- .github/workflows/standalone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index b536f58368..f39bbc5172 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -73,7 +73,7 @@ jobs: - name: Zip OSX app to maintain symlinks and to sign # if we do not call always() GHA will && with success() if: ${{ always() && (matrix.os == 'macos') }} - run: (cd standalone/dist; zip -r --symlinks jdaviz.zip jdaviz.app ) + run: (cd standalone/dist; ditto -c -k --sequesterRsrc --keepParent jdaviz.app jdaviz.zip ) - name: Notary step (OSX) if: ${{ matrix.os == 'macos' }} @@ -89,7 +89,7 @@ jobs: xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }} $uuid || true fi # re-zip it - (cd standalone/dist; rm jdaviz.zip; zip -r --symlinks jdaviz.zip jdaviz.app ) + (cd standalone/dist; rm jdaviz.zip; ditto -c -k --sequesterRsrc --keepParent jdaviz.app jdaviz.zip ) - name: Validate app (OSX) if: ${{ matrix.os == 'macos' }} From 4aa88bf3f565c7805b7cc49b1b957722e8dfaf7d Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 20:59:01 +0200 Subject: [PATCH 26/82] comments for the future --- .github/workflows/standalone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index f39bbc5172..dbfccee9f4 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -73,6 +73,8 @@ jobs: - name: Zip OSX app to maintain symlinks and to sign # if we do not call always() GHA will && with success() if: ${{ always() && (matrix.os == 'macos') }} + # it seems ditto (not zip) should be used in combination with notarization + # see https://developer.apple.com/forums/thread/116831 run: (cd standalone/dist; ditto -c -k --sequesterRsrc --keepParent jdaviz.app jdaviz.zip ) - name: Notary step (OSX) From 42d3eb1e06af818f2ef2c314ae6d8b1ac1dbf30f Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 20:59:13 +0200 Subject: [PATCH 27/82] fix uuid for notary step --- .github/workflows/standalone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index dbfccee9f4..e8ee950dea 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -82,7 +82,7 @@ jobs: run: | output=$(xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true echo "$output" - uuid=$(echo "$output" | awk -F '[ :]+' '/id:/ {print $2; exit}') + uuid=$(echo "$output" | awk -F '[ :]+' '/id:/ {print $3; exit}') echo "UUID: $uuid" if [[ $output == *"status: Accepted"* ]]; then echo "Great, notarization succeeded!" From 00b188614b16c7c7e985e36a427e79abf7c77843 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 13 Jun 2023 20:59:42 +0200 Subject: [PATCH 28/82] upload different artifact for osx --- .github/workflows/standalone.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index e8ee950dea..0e90da44ad 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -123,11 +123,17 @@ jobs: name: test-results-${{ matrix.os }} path: standalone/test-results - - name: Upload jdaviz standalone - if: always() + - name: Upload jdaviz standalone (non-OSX) + if: ${{ always() && (matrix.os != 'macos') }} uses: actions/upload-artifact@v3 with: name: jdaviz-standlone-${{ matrix.os }} path: | standalone/dist/jdaviz-cli - standalone/dist/jdaviz.zip + + - name: Upload jdaviz standalone (OSX) + if: ${{ always() && (matrix.os == 'macos') }} + uses: actions/upload-artifact@v3 + with: + name: jdaviz-standlone-${{ matrix.os }} + path: standalone/dist/jdaviz.zip From 3adfbb4794457c68fb4e11e4b91ca180f173f8c3 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 20 Jun 2023 11:31:10 +0200 Subject: [PATCH 29/82] cleanup spec file and hooks, no more __pycache__ files should be included --- standalone/hooks/hook-bqplot.py | 3 ++ standalone/hooks/hook-glue.py | 2 +- standalone/hooks/hook-glue_jupyter.py | 2 +- standalone/hooks/hook-ipypopout.py | 3 ++ standalone/hooks/hook-jdaviz.py | 2 +- standalone/hooks/hook-jupyter_client.py | 3 ++ standalone/hooks/hook-photutils.py | 5 +++ standalone/hooks/hook-regions.py | 5 +++ standalone/jdaviz.spec | 59 ++++++++----------------- 9 files changed, 40 insertions(+), 44 deletions(-) create mode 100644 standalone/hooks/hook-bqplot.py create mode 100644 standalone/hooks/hook-ipypopout.py create mode 100644 standalone/hooks/hook-jupyter_client.py create mode 100644 standalone/hooks/hook-photutils.py create mode 100644 standalone/hooks/hook-regions.py diff --git a/standalone/hooks/hook-bqplot.py b/standalone/hooks/hook-bqplot.py new file mode 100644 index 0000000000..613900b9eb --- /dev/null +++ b/standalone/hooks/hook-bqplot.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files('bqplot') diff --git a/standalone/hooks/hook-glue.py b/standalone/hooks/hook-glue.py index ab90a39ded..4941c0920b 100644 --- a/standalone/hooks/hook-glue.py +++ b/standalone/hooks/hook-glue.py @@ -1,4 +1,4 @@ from PyInstaller.utils.hooks import collect_data_files, copy_metadata -datas = collect_data_files('glue', include_py_files=True) +datas = collect_data_files('glue') datas += copy_metadata('glue-core') diff --git a/standalone/hooks/hook-glue_jupyter.py b/standalone/hooks/hook-glue_jupyter.py index 89df6f2f0d..98baed00c2 100644 --- a/standalone/hooks/hook-glue_jupyter.py +++ b/standalone/hooks/hook-glue_jupyter.py @@ -1,4 +1,4 @@ from PyInstaller.utils.hooks import collect_data_files, copy_metadata -datas = collect_data_files('glue_jupyter', include_py_files=True) +datas = collect_data_files('glue_jupyter') datas += copy_metadata('glue_jupyter') diff --git a/standalone/hooks/hook-ipypopout.py b/standalone/hooks/hook-ipypopout.py new file mode 100644 index 0000000000..55422b9aa5 --- /dev/null +++ b/standalone/hooks/hook-ipypopout.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files('ipypopout') diff --git a/standalone/hooks/hook-jdaviz.py b/standalone/hooks/hook-jdaviz.py index 461acff37e..c7abff2b5e 100644 --- a/standalone/hooks/hook-jdaviz.py +++ b/standalone/hooks/hook-jdaviz.py @@ -1,4 +1,4 @@ from PyInstaller.utils.hooks import collect_data_files, copy_metadata -datas = collect_data_files('jdaviz', include_py_files=True) +datas = collect_data_files('jdaviz') datas += copy_metadata('jdaviz') diff --git a/standalone/hooks/hook-jupyter_client.py b/standalone/hooks/hook-jupyter_client.py new file mode 100644 index 0000000000..6ecd059447 --- /dev/null +++ b/standalone/hooks/hook-jupyter_client.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules("jupyter_client") diff --git a/standalone/hooks/hook-photutils.py b/standalone/hooks/hook-photutils.py new file mode 100644 index 0000000000..31bc609d12 --- /dev/null +++ b/standalone/hooks/hook-photutils.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules("photutils") +# for CITATION.rst +datas = collect_data_files('photutils') diff --git a/standalone/hooks/hook-regions.py b/standalone/hooks/hook-regions.py new file mode 100644 index 0000000000..9373dbae9a --- /dev/null +++ b/standalone/hooks/hook-regions.py @@ -0,0 +1,5 @@ +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +hiddenimports = collect_submodules("regions") +# for CITATION.rst +datas = collect_data_files('regions') diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index 08f3d2bced..be688f8411 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -3,45 +3,19 @@ import sys from pathlib import Path import os -import bqplot -import debugpy -import glue -import jdaviz -import glue_jupyter -import ipypopout -import photutils -import regions -from PyInstaller.utils.hooks import collect_all, collect_data_files, collect_submodules +from PyInstaller.building.build_main import Analysis +from PyInstaller.building.api import COLLECT, EXE, PYZ +from PyInstaller.building.osx import BUNDLE +import jdaviz codesign_identity = os.environ.get("DEVELOPER_ID_APPLICATION") +# this copies over the nbextensions enabling json and the js assets +# for all the widgets datas = [ (Path(sys.prefix) / "share" / "jupyter", "./share/jupyter"), (Path(sys.prefix) / "etc" / "jupyter", "./etc/jupyter"), - *collect_data_files("regions"), - *collect_data_files("photutils"), - # *collect_data_files("debugpy"), - *collect_data_files("glue"), - *collect_data_files("glue_jupyter"), - *collect_data_files("bqplot"), - *collect_data_files("ipypopout"), ] -binaries = [] -# jdaviz is not imported condinally in jdaviz-cli-entrypoint.py, so a hidden import -hiddenimports = [] -hiddenimports += collect_submodules("regions") -hiddenimports += collect_submodules("photutils") -hiddenimports += collect_submodules("jupyter_client") -hiddenimports += collect_submodules("debugpy") -tmp_ret = collect_all("astropy") -datas += tmp_ret[0] -binaries += tmp_ret[1] -hiddenimports += tmp_ret[2] -# tmp_ret = collect_all('.') -datas += tmp_ret[0] -binaries += tmp_ret[1] -hiddenimports += tmp_ret[2] - block_cipher = None @@ -49,9 +23,9 @@ block_cipher = None a = Analysis( ["jdaviz-cli-entrypoint.py"], pathex=[], - binaries=binaries, + binaries=[], datas=datas, - hiddenimports=hiddenimports, + hiddenimports=[], hookspath=["hooks"], hooksconfig={}, runtime_hooks=[], @@ -78,8 +52,7 @@ exe = EXE( argv_emulation=False, target_arch=None, codesign_identity=codesign_identity, - entitlements_file=None, - + entitlements_file="entitlements.plist", ) coll = COLLECT( exe, @@ -91,8 +64,12 @@ coll = COLLECT( upx_exclude=[], name="jdaviz-cli", ) -app = BUNDLE(exe, coll, - name='jdaviz.app', - icon=None, - bundle_identifier='edu.stsci.jdaviz', - version=jdaviz.__version__) +app = BUNDLE( + exe, + coll, + name="jdaviz.app", + icon=None, + entitlements_file="entitlements.plist", + bundle_identifier="edu.stsci.jdaviz", + version=jdaviz.__version__, +) From 18f750d19ca2543426f0449c44cbe4d5f3a31632 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 20 Jun 2023 11:57:30 +0200 Subject: [PATCH 30/82] ci: do not cancel on failure --- .github/workflows/standalone.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 0e90da44ad..8b369e1e92 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -10,10 +10,6 @@ on: - 'v*' pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-standalone - cancel-in-progress: true - defaults: run: shell: bash {0} From ab8f88134e79f734c6455985be160062a1e0e09b Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 20 Jun 2023 13:04:54 +0200 Subject: [PATCH 31/82] fix: mistune 3.0 needs this --- standalone/hooks/hook-mistune.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 standalone/hooks/hook-mistune.py diff --git a/standalone/hooks/hook-mistune.py b/standalone/hooks/hook-mistune.py new file mode 100644 index 0000000000..ac80b6f9e8 --- /dev/null +++ b/standalone/hooks/hook-mistune.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = collect_submodules("mistune") From fc5fb855ba2d18beaf932fc244db6d710f617dc9 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 20 Jun 2023 13:28:26 +0200 Subject: [PATCH 32/82] make dmg instead of zip for osx --- .github/workflows/standalone.yml | 28 ++++++++++++++++++---------- standalone/jdaviz.spec | 5 ++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 8b369e1e92..1abeb02f1a 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -66,28 +66,31 @@ jobs: cd standalone/dist codesign --deep --force --options=runtime --entitlements ../entitlements.plist --sign ${{ secrets.DEVELOPER_ID_APPLICATION }} --timestamp jdaviz.app - - name: Zip OSX app to maintain symlinks and to sign + - name: Create dmg (OSX) # if we do not call always() GHA will && with success() if: ${{ always() && (matrix.os == 'macos') }} # it seems ditto (not zip) should be used in combination with notarization # see https://developer.apple.com/forums/thread/116831 - run: (cd standalone/dist; ditto -c -k --sequesterRsrc --keepParent jdaviz.app jdaviz.zip ) + # but dmg also works + # see https://github.com/glue-viz/glue-standalone-apps/blob/main/.github/workflows/build_stable.yml + run: | + rm -rf standalone/dist/jdaviz + hdiutil create -volname "Jdaviz" -srcfolder standalone/dist -ov -format UDZO standalone/dist/jdaviz.dmg - - name: Notary step (OSX) + - name: Notary step + stapling (OSX) if: ${{ matrix.os == 'macos' }} run: | - output=$(xcrun notarytool submit standalone/dist/jdaviz.zip --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true + output=$(xcrun notarytool submit standalone/dist/jdaviz.dmg --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --wait --password ${{ secrets.NOTARIZATION_PASSWORD }}) || true echo "$output" uuid=$(echo "$output" | awk -F '[ :]+' '/id:/ {print $3; exit}') echo "UUID: $uuid" if [[ $output == *"status: Accepted"* ]]; then - echo "Great, notarization succeeded!" + echo "Great, notarization succeeded, staple it!" + xcrun stapler staple standalone/dist/jdaviz.dmg else echo "Log output for failed notarization: $uuid" xcrun notarytool log --apple-id ${{ secrets.NOTARIZATION_USERNAME }} --team-id ${{ secrets.TEAM_ID }} --password ${{ secrets.NOTARIZATION_PASSWORD }} $uuid || true fi - # re-zip it - (cd standalone/dist; rm jdaviz.zip; ditto -c -k --sequesterRsrc --keepParent jdaviz.app jdaviz.zip ) - name: Validate app (OSX) if: ${{ matrix.os == 'macos' }} @@ -95,7 +98,12 @@ jobs: spctl -a -vvv -t execute standalone/dist/jdaviz.app - name: Run jdaviz cmd in background - run: ./standalone/dist/jdaviz-cli/jdaviz-cli imviz& + if: ${{ matrix.os == 'macos' }} + run: ./standalone/dist/jdaviz.app/Contents/MacOS/jdaviz-cli imviz& + + - name: Run jdaviz cmd in background + if: ${{ matrix.os != 'macos' }} + run: ./standalone/dist/jdaviz/jdaviz-cli imviz& - name: Install playwright run: (pip install playwright; playwright install chromium) @@ -125,11 +133,11 @@ jobs: with: name: jdaviz-standlone-${{ matrix.os }} path: | - standalone/dist/jdaviz-cli + standalone/dist/jdaviz - name: Upload jdaviz standalone (OSX) if: ${{ always() && (matrix.os == 'macos') }} uses: actions/upload-artifact@v3 with: name: jdaviz-standlone-${{ matrix.os }} - path: standalone/dist/jdaviz.zip + path: standalone/dist/jdaviz.dmg diff --git a/standalone/jdaviz.spec b/standalone/jdaviz.spec index be688f8411..45768f2db4 100644 --- a/standalone/jdaviz.spec +++ b/standalone/jdaviz.spec @@ -42,6 +42,8 @@ exe = EXE( a.scripts, [], exclude_binaries=True, + # executable name: dist/jdaviz/jdaviz-cli + # note: cannot be called jdaviz, because there is a directory called jdaviz name="jdaviz-cli", debug=False, bootloader_ignore_signals=False, @@ -62,7 +64,8 @@ coll = COLLECT( strip=False, upx=True, upx_exclude=[], - name="jdaviz-cli", + # directory name: dist/jdaviz + name="jdaviz", ) app = BUNDLE( exe, From df0ace82ca0430e9931b0a528a6b241d198d49a1 Mon Sep 17 00:00:00 2001 From: Pey Lian Lim <2090236+pllim@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:48:40 -0400 Subject: [PATCH 33/82] BUG: Fix mouseover behavior in Cubeviz spectrum viewer when spatial subset is present. Co-authored-by: Duy Nguyen --- CHANGES.rst | 2 ++ jdaviz/configs/imviz/plugins/coords_info/coords_info.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 98c9b63545..cd4d24ad70 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -91,6 +91,8 @@ Bug Fixes Cubeviz ^^^^^^^ +- Fixed mouseover not behaving properly in spectrum viewer when spatial subset is present. [#2258] + Imviz ^^^^^ diff --git a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py index 5cc0ecdbeb..65c51ab88c 100644 --- a/jdaviz/configs/imviz/plugins/coords_info/coords_info.py +++ b/jdaviz/configs/imviz/plugins/coords_info/coords_info.py @@ -464,7 +464,7 @@ def _copy_axes_to_spectral(): sp = self.app._get_object_cache[cache_key] else: sp = self._specviz_helper.get_data(data_label=data_label, - spectral_subset=subset_label) + spatial_subset=subset_label) self.app._get_object_cache[cache_key] = sp # Calculations have to happen in the frame of viewer display units. From 54ae4c813948dcfce6b57f32190f62786a86578d Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Tue, 20 Jun 2023 13:48:28 -0400 Subject: [PATCH 34/82] Remove change log from #2258 because the bug only affects unreleased code [ci skip] [rtd skip] --- CHANGES.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index cd4d24ad70..98c9b63545 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -91,8 +91,6 @@ Bug Fixes Cubeviz ^^^^^^^ -- Fixed mouseover not behaving properly in spectrum viewer when spatial subset is present. [#2258] - Imviz ^^^^^ From 8350d839855197dc0b92cc7530083b105966aaed Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Wed, 7 Jun 2023 09:51:19 -0400 Subject: [PATCH 35/82] Deprecate get_subsets_from_viewer --- docs/cubeviz/export_data.rst | 2 +- jdaviz/app.py | 4 ++++ .../cubeviz/plugins/tests/test_parsers.py | 4 ++-- .../cubeviz/plugins/tests/test_tools.py | 12 +++++----- jdaviz/tests/test_subsets.py | 22 +++++++++---------- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/cubeviz/export_data.rst b/docs/cubeviz/export_data.rst index 248ad932f4..ede78dd192 100644 --- a/docs/cubeviz/export_data.rst +++ b/docs/cubeviz/export_data.rst @@ -41,7 +41,7 @@ To get all subsets from the spectrum viewer: .. code-block:: python - subset1_spec1d = cubeviz.app.get_subsets_from_viewer("spectrum-viewer") + subset1_spec1d = cubeviz.app.get_subsets() To access the spatial regions themselves: diff --git a/jdaviz/app.py b/jdaviz/app.py index 3b8afb2c34..0289466bc8 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -14,6 +14,7 @@ from astropy.io import fits from astropy.coordinates import Angle from astropy.time import Time +from astropy.utils.exceptions import AstropyDeprecationWarning from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion from echo import CallbackProperty, DictCallbackProperty, ListCallbackProperty @@ -802,6 +803,9 @@ def get_subsets_from_viewer(self, viewer_reference, data_label=None, subset_type representing the subset name and values as astropy regions objects. """ + warnings.warn(AstropyDeprecationWarning("get_subsets_from_viewer() is deprecated in v3.6 " + "and will be removed in a future release. Use " + "get_subsets() instead.")) viewer = self.get_viewer(viewer_reference) data = self.get_data_from_viewer(viewer_reference, data_label, diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py index 48b9ba81f9..f82765bdac 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py @@ -73,8 +73,8 @@ def test_spectrum1d_with_fake_fixed_units(spectrum1d, cubeviz_helper): cubeviz_helper.app.add_data_to_viewer('spectrum-viewer', 'test') cubeviz_helper.app.get_viewer('spectrum-viewer').apply_roi(XRangeROI(6600, 7400)) - subsets = cubeviz_helper.app.get_subsets_from_viewer("spectrum-viewer") - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets(spatial_only=True) + reg = subsets.get('Subset 1')[0]['region'] assert len(subsets) == 1 assert_allclose(reg.lower.value, 6600.) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_tools.py b/jdaviz/configs/cubeviz/plugins/tests/test_tools.py index 5ce4867451..b97d181aba 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_tools.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_tools.py @@ -25,8 +25,8 @@ def test_spectrum_at_spaxel(cubeviz_helper, spectrum1d_cube): assert len(spectrum_viewer.data()) == 2 # Check that a new subset was created - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert len(subsets) == 1 assert isinstance(reg, RectanglePixelRegion) @@ -64,8 +64,8 @@ def test_spectrum_at_spaxel_altkey_true(cubeviz_helper, spectrum1d_cube): assert len(spectrum_viewer.data()) == 2 # Check that subset was created - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert len(subsets) == 1 assert isinstance(reg, RectanglePixelRegion) @@ -77,8 +77,8 @@ def test_spectrum_at_spaxel_altkey_true(cubeviz_helper, spectrum1d_cube): assert len(flux_viewer.figure.marks) == 4 assert len(spectrum_viewer.data()) == 3 - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg2 = subsets.get('Subset 2') + subsets = cubeviz_helper.app.get_subsets() + reg2 = subsets.get('Subset 2')[0]['region'] assert len(subsets) == 2 assert isinstance(reg2, RectanglePixelRegion) diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index 7dd56bca67..4e8faf2e8c 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -22,8 +22,8 @@ def test_region_from_subset_2d(cubeviz_helper): cubeviz_helper.app.get_viewer('flux-viewer').apply_roi(EllipticalROI(1, 3.5, 1.2, 3.3)) - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert len(subsets) == 1 assert isinstance(reg, EllipsePixelRegion) @@ -99,8 +99,8 @@ def test_region_from_subset_3d(cubeviz_helper): assert subset_plugin._get_value_from_subset_definition(0, "Ymax", key) == 3.3 assert subset_plugin._get_value_from_subset_definition(0, "Angle", key) == 45 - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert_allclose(reg.center.x, 2.75) assert_allclose(reg.center.y, 1.65) @@ -110,8 +110,8 @@ def test_region_from_subset_3d(cubeviz_helper): # Move the rectangle subset_plugin.set_center((3, 2), update=True) - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer') - reg = subsets.get('Subset 1') + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1')[0]['region'] assert_allclose(reg.center.x, 3) assert_allclose(reg.center.y, 2) assert_allclose(reg.width, 1.5) @@ -140,7 +140,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): cubeviz_helper.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(5, 15.5)) - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert len(subsets) == 1 @@ -165,7 +165,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): assert subset_plugin._get_value_from_subset_definition(0, "Lower bound", key) == 10 assert subset_plugin._get_value_from_subset_definition(0, "Upper bound", key) == 15.5 - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert_quantity_allclose(reg.lower, 10.0 * u.Hz) @@ -173,7 +173,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): # Move the Subset. subset_plugin.set_center(10, update=True) - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert_quantity_allclose(reg.lower, 7.25 * u.Hz) assert_quantity_allclose(reg.upper, 12.75 * u.Hz) @@ -200,7 +200,7 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs): assert len([m for m in spectrum_viewer.figure.marks if isinstance(m, ShadowSpatialSpectral)]) == 1 # noqa - subsets = cubeviz_helper.app.get_subsets_from_viewer('spectrum-viewer', subset_type='spectral') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 1') assert len(subsets) == 1 @@ -209,7 +209,7 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs): assert_quantity_allclose(reg.lower, 5.0 * u.Hz) assert_quantity_allclose(reg.upper, 15.5 * u.Hz) - subsets = cubeviz_helper.app.get_subsets_from_viewer('flux-viewer', subset_type='spatial') + subsets = cubeviz_helper.app.get_subsets(spectral_only=True) reg = subsets.get('Subset 2') assert len(subsets) == 1 From 48726953884b2ae85e208c10912a7da4b6ae9369 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Wed, 7 Jun 2023 10:01:22 -0400 Subject: [PATCH 36/82] Missing region index --- jdaviz/tests/test_subsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index 4e8faf2e8c..faaa5f6d1a 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -210,7 +210,7 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs): assert_quantity_allclose(reg.upper, 15.5 * u.Hz) subsets = cubeviz_helper.app.get_subsets(spectral_only=True) - reg = subsets.get('Subset 2') + reg = subsets.get('Subset 2')[0]['region'] assert len(subsets) == 1 assert isinstance(reg, RectanglePixelRegion) From 83ccc6c4b79b8f7f292af2a93a13ec995929cc8c Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Wed, 7 Jun 2023 15:52:26 -0400 Subject: [PATCH 37/82] Fix subset args --- jdaviz/configs/cubeviz/plugins/tests/test_parsers.py | 4 ++-- jdaviz/tests/test_subsets.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py index f82765bdac..5fb34a91d4 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_parsers.py @@ -73,8 +73,8 @@ def test_spectrum1d_with_fake_fixed_units(spectrum1d, cubeviz_helper): cubeviz_helper.app.add_data_to_viewer('spectrum-viewer', 'test') cubeviz_helper.app.get_viewer('spectrum-viewer').apply_roi(XRangeROI(6600, 7400)) - subsets = cubeviz_helper.app.get_subsets(spatial_only=True) - reg = subsets.get('Subset 1')[0]['region'] + subsets = cubeviz_helper.app.get_subsets() + reg = subsets.get('Subset 1') assert len(subsets) == 1 assert_allclose(reg.lower.value, 6600.) diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index faaa5f6d1a..8d4066bdf7 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -209,7 +209,7 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs): assert_quantity_allclose(reg.lower, 5.0 * u.Hz) assert_quantity_allclose(reg.upper, 15.5 * u.Hz) - subsets = cubeviz_helper.app.get_subsets(spectral_only=True) + subsets = cubeviz_helper.app.get_subsets(spatial_only=True) reg = subsets.get('Subset 2')[0]['region'] assert len(subsets) == 1 From 4c4ef73011f5a6a9109bedd258cdbf9aa0c5280f Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 10:00:45 -0400 Subject: [PATCH 38/82] Remove get_data_from_viewer from imviz viewer tests --- jdaviz/configs/imviz/tests/test_helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jdaviz/configs/imviz/tests/test_helper.py b/jdaviz/configs/imviz/tests/test_helper.py index f7c8a33041..f2e4410cb7 100644 --- a/jdaviz/configs/imviz/tests/test_helper.py +++ b/jdaviz/configs/imviz/tests/test_helper.py @@ -25,12 +25,12 @@ def test_create_new_viewer(imviz_helper, image_2d_wcs): assert len(imviz_helper.app.get_viewer_ids()) == 2 # there should be no data in the new viewer - assert len(imviz_helper.app.get_data_from_viewer(viewer_name)) == 0 + assert len(imviz_helper.app.get_viewer(viewer_name).data()) == 0 # then add data, and check that data were added to the new viewer imviz_helper.app.add_data_to_viewer(viewer_name, data_label) - assert len(imviz_helper.app.get_data_from_viewer(viewer_name)) == 1 + assert len(imviz_helper.app.get_viewer(viewer_name).data()) == 1 # remove data from the new viewer, check that it was removed imviz_helper.app.remove_data_from_viewer(viewer_name, data_label) - assert len(imviz_helper.app.get_data_from_viewer(viewer_name)) == 0 + assert len(imviz_helper.app.get_viewer(viewer_name).data()) == 0 From 30c5ec8c476a8196648eb479e0743f259cea591f Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 13:31:52 -0400 Subject: [PATCH 39/82] Remove get_data_from_viewer from mosviz data loading test and sub hardcoded viewer ref names --- .../configs/mosviz/tests/test_data_loading.py | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index 9a5ab2ca5d..b40264f8aa 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -20,12 +20,14 @@ def test_load_spectrum1d(mosviz_helper, spectrum1d): assert dc_1.label == label assert dc_1.meta['uncertainty_type'] == 'std' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert isinstance(data[label], Spectrum1D) + assert len(data) == 1 + assert isinstance(data[0], Spectrum1D) with pytest.raises(AttributeError): mosviz_helper.load_1d_spectra([1, 2, 3]) @@ -42,12 +44,14 @@ def test_load_image(mosviz_helper, mos_image): assert PRIHDR_KEY not in dc_1.meta assert dc_1.meta['RADESYS'] == 'ICRS' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('image-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_image_viewer_reference_name + ).data() - dataval = data[f"{label} 0"] + assert len(data) == 1 + dataval = data[0] assert isinstance(dataval, CCDData) assert dataval.shape == (55, 55) @@ -63,12 +67,14 @@ def test_load_spectrum_collection(mosviz_helper, spectrum_collection): assert dc_1.label == labels[0] assert dc_1.meta['uncertainty_type'] == 'std' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.select_row(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert isinstance(data[labels[0]], Spectrum1D) + assert len(data) == 1 + assert isinstance(data[0], Spectrum1D) def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d): @@ -83,12 +89,14 @@ def test_load_list_of_spectrum1d(mosviz_helper, spectrum1d): assert dc_1.label == labels[0] assert dc_1.meta['uncertainty_type'] == 'std' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert isinstance(data[labels[0]], Spectrum1D) + assert len(data) == 1 + assert isinstance(data[0], Spectrum1D) def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d): @@ -102,12 +110,14 @@ def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d): assert dc_1.label == label assert dc_1.meta['INSTRUME'] == 'nirspec' - table = mosviz_helper.app.get_viewer('table-viewer') + table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_data_from_viewer('spectrum-2d-viewer') + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + ).data() - assert data[label].shape == (1024, 15) + assert len(data) == 0 + assert data[0].shape == (1024, 15) @pytest.mark.parametrize('label', [None, "Test Label"]) @@ -118,7 +128,8 @@ def test_load_multi_image_spec(mosviz_helper, mos_image, spectrum1d, mos_spectru mosviz_helper.load_data(spectra1d, spectra2d, images=images, images_label=label) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 10 qtable = mosviz_helper.to_table() @@ -134,7 +145,8 @@ def test_load_multi_image_and_spec1d_only(mosviz_helper, mos_image, spectrum1d): mosviz_helper.load_data(spectra_1d=spectra1d, images=images) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 7 @@ -144,7 +156,8 @@ def test_load_multi_image_and_spec2d_only(mosviz_helper, mos_image, mos_spectrum mosviz_helper.load_data(spectra_2d=spectra2d, images=images) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 7 @@ -170,7 +183,8 @@ def test_load_single_image_multi_spec(mosviz_helper, mos_image, spectrum1d, mos_ mosviz_helper.load_data(spectra1d, spectra2d, images=mos_image, images_label=label) - assert mosviz_helper.app.get_viewer("table-viewer").figure_widget.highlighted == 0 + assert mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name + ).figure_widget.highlighted == 0 assert len(mosviz_helper.app.data_collection) == 8 qtable = mosviz_helper.to_table() From 3638a5061984189bb114869a7440c52b6de83d9d Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 15:01:07 -0400 Subject: [PATCH 40/82] Mosviz test update image truth class --- jdaviz/configs/mosviz/tests/test_data_loading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index b40264f8aa..ecca78fdff 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -2,7 +2,7 @@ from zipfile import ZipFile -from astropy.nddata import CCDData +from glue.core import Data import numpy as np import pytest from specutils import Spectrum1D @@ -52,7 +52,7 @@ def test_load_image(mosviz_helper, mos_image): assert len(data) == 1 dataval = data[0] - assert isinstance(dataval, CCDData) + assert isinstance(dataval, Data) assert dataval.shape == (55, 55) From 2b1eff9ea717fc4d81ee70222bb2aa3590a6a926 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 15:12:40 -0400 Subject: [PATCH 41/82] Fix incorrect viewer ref --- jdaviz/configs/mosviz/tests/test_data_loading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index ecca78fdff..83b12651d3 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -113,10 +113,10 @@ def test_load_mos_spectrum2d(mosviz_helper, mos_spectrum2d): table = mosviz_helper.app.get_viewer(mosviz_helper._default_table_viewer_reference_name) table.widget_table.vue_on_row_clicked(0) - data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_viewer_reference_name + data = mosviz_helper.app.get_viewer(mosviz_helper._default_spectrum_2d_viewer_reference_name ).data() - assert len(data) == 0 + assert len(data) == 1 assert data[0].shape == (1024, 15) From 4d6658dd6e6347bc6011080c7a0a79b42e06fe4f Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 16:51:30 -0400 Subject: [PATCH 42/82] Set Mos2Dviewer data statistic to None by default --- jdaviz/configs/mosviz/plugins/viewers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jdaviz/configs/mosviz/plugins/viewers.py b/jdaviz/configs/mosviz/plugins/viewers.py index ba65587373..c4cd8e1760 100644 --- a/jdaviz/configs/mosviz/plugins/viewers.py +++ b/jdaviz/configs/mosviz/plugins/viewers.py @@ -207,8 +207,9 @@ def _handle_x_axis_orientation(self, *args): x_scales.min = max(limits) if self.inverted_x_axis else min(limits) x_scales.max = min(limits) if self.inverted_x_axis else max(limits) - def data(self, cls=None): - return [layer_state.layer.get_object(cls=cls or self.default_class) + def data(self, cls=None, statistic=None): + return [layer_state.layer.get_object(statistic=statistic, + cls=cls or self.default_class) for layer_state in self.state.layers if hasattr(layer_state, 'layer') and isinstance(layer_state.layer, BaseData)] From f43ee63eb43670b17c8b67332bb0fdef88512ddb Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 17:00:18 -0400 Subject: [PATCH 43/82] Non-existent data check --- jdaviz/tests/test_app.py | 284 +++++++++++++++++++-------------------- 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py index f0138f082e..db0a72f522 100644 --- a/jdaviz/tests/test_app.py +++ b/jdaviz/tests/test_app.py @@ -1,142 +1,142 @@ -import pytest - -from jdaviz import Application, Specviz -from jdaviz.configs.default.plugins.gaussian_smooth.gaussian_smooth import GaussianSmooth - - -# This applies to all viz but testing with Imviz should be enough. -def test_viewer_calling_app(imviz_helper): - viewer = imviz_helper.default_viewer - assert viewer.session.jdaviz_app is imviz_helper.app - - -def test_get_tray_item_from_name(): - app = Application(configuration='default') - plg = app.get_tray_item_from_name('g-gaussian-smooth') - assert isinstance(plg, GaussianSmooth) - - with pytest.raises(KeyError, match='not found in app'): - app.get_tray_item_from_name('imviz-compass') - - -def test_nonstandard_specviz_viewer_name(spectrum1d): - config = {'settings': {'configuration': 'nonstandard', - 'data': {'parser': 'specviz-spectrum1d-parser'}, - 'visible': {'menu_bar': False, - 'toolbar': True, - 'tray': True, - 'tab_headers': False}, - 'context': {'notebook': {'max_height': '750px'}}}, - 'toolbar': ['g-data-tools', 'g-subset-tools'], - 'tray': ['g-metadata-viewer', - 'g-plot-options', - 'g-subset-plugin', - 'g-gaussian-smooth', - 'g-model-fitting', - 'g-unit-conversion', - 'g-line-list', - 'specviz-line-analysis', - 'g-export-plot'], - 'viewer_area': [{'container': 'col', - 'children': [{'container': 'row', - 'viewers': [{'name': 'H', - 'plot': 'specviz-profile-viewer', - 'reference': 'h'}, - {'name': 'K', - 'plot': 'specviz-profile-viewer', - 'reference': 'k'}]}]}]} - - class Customviz(Specviz): - _default_configuration = config - _default_spectrum_viewer_reference_name = 'h' - - viz = Customviz() - assert viz.app.get_viewer_reference_names() == ['h', 'k'] - - viz.load_spectrum(spectrum1d, data_label='example label') - assert not len(viz.app.get_data_from_viewer("h", "non-existent label")) - - -def test_duplicate_data_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test") - specviz_helper.load_spectrum(spectrum1d, data_label="test") - dc = specviz_helper.app.data_collection - assert dc[0].label == "test" - assert dc[1].label == "test (1)" - specviz_helper.load_spectrum(spectrum1d, data_label="test_1") - specviz_helper.load_spectrum(spectrum1d, data_label="test") - assert dc[2].label == "test_1" - assert dc[3].label == "test (2)" - - -def test_duplicate_data_labels_with_brackets(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") - dc = specviz_helper.app.data_collection - assert len(dc) == 2 - assert dc[0].label == "test[test]" - assert dc[1].label == "test[test] (1)" - - -def test_return_data_label_is_none(specviz_helper): - data_label = specviz_helper.app.return_data_label(None) - assert data_label == "Unknown" - - -def test_return_data_label_is_image(specviz_helper): - data_label = specviz_helper.app.return_data_label("data/path/test.jpg") - assert data_label == "test[jpg]" - - -def test_hdulist_with_filename(cubeviz_helper, image_cube_hdu_obj): - image_cube_hdu_obj.file_name = "test" - data_label = cubeviz_helper.app.return_data_label(image_cube_hdu_obj) - assert data_label == "test[HDU object]" - - -def test_file_path_not_image(imviz_helper, tmp_path): - path = tmp_path / "myimage.fits" - path.touch() - data_label = imviz_helper.app.return_data_label(str(path)) - assert data_label == "myimage" - - -def test_unique_name_variations(specviz_helper, spectrum1d): - data_label = specviz_helper.app.return_unique_name(None) - assert data_label == "Unknown" - - specviz_helper.load_spectrum(spectrum1d, data_label="test[flux]") - data_label = specviz_helper.app.return_data_label("test[flux]", ext="flux") - assert data_label == "test[flux][flux]" - - data_label = specviz_helper.app.return_data_label("test", ext="flux") - assert data_label == "test[flux] (1)" - - -def test_substring_in_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="M31") - specviz_helper.load_spectrum(spectrum1d, data_label="M32") - data_label = specviz_helper.app.return_data_label("M") - assert data_label == "M" - - -@pytest.mark.parametrize('data_label', ('111111', 'aaaaa', '///(#$@)', - 'two spaces repeating', - 'word42word42word two spaces')) -def test_edge_cases(specviz_helper, spectrum1d, data_label): - dc = specviz_helper.app.data_collection - - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - assert dc[1].label == f"{data_label} (1)" - - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - assert dc[2].label == f"{data_label} (2)" - - -def test_case_that_used_to_break_return_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break (1)") - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break") - dc = specviz_helper.app.data_collection - assert dc[0].label == "this used to break (1)" - assert dc[1].label == "this used to break (2)" +import pytest + +from jdaviz import Application, Specviz +from jdaviz.configs.default.plugins.gaussian_smooth.gaussian_smooth import GaussianSmooth + + +# This applies to all viz but testing with Imviz should be enough. +def test_viewer_calling_app(imviz_helper): + viewer = imviz_helper.default_viewer + assert viewer.session.jdaviz_app is imviz_helper.app + + +def test_get_tray_item_from_name(): + app = Application(configuration='default') + plg = app.get_tray_item_from_name('g-gaussian-smooth') + assert isinstance(plg, GaussianSmooth) + + with pytest.raises(KeyError, match='not found in app'): + app.get_tray_item_from_name('imviz-compass') + + +def test_nonstandard_specviz_viewer_name(spectrum1d): + config = {'settings': {'configuration': 'nonstandard', + 'data': {'parser': 'specviz-spectrum1d-parser'}, + 'visible': {'menu_bar': False, + 'toolbar': True, + 'tray': True, + 'tab_headers': False}, + 'context': {'notebook': {'max_height': '750px'}}}, + 'toolbar': ['g-data-tools', 'g-subset-tools'], + 'tray': ['g-metadata-viewer', + 'g-plot-options', + 'g-subset-plugin', + 'g-gaussian-smooth', + 'g-model-fitting', + 'g-unit-conversion', + 'g-line-list', + 'specviz-line-analysis', + 'g-export-plot'], + 'viewer_area': [{'container': 'col', + 'children': [{'container': 'row', + 'viewers': [{'name': 'H', + 'plot': 'specviz-profile-viewer', + 'reference': 'h'}, + {'name': 'K', + 'plot': 'specviz-profile-viewer', + 'reference': 'k'}]}]}]} + + class Customviz(Specviz): + _default_configuration = config + _default_spectrum_viewer_reference_name = 'h' + + viz = Customviz() + assert viz.app.get_viewer_reference_names() == ['h', 'k'] + + viz.load_spectrum(spectrum1d, data_label='example label') + assert not len(viz.app.get_data("non-existent label")) + + +def test_duplicate_data_labels(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_spectrum(spectrum1d, data_label="test") + dc = specviz_helper.app.data_collection + assert dc[0].label == "test" + assert dc[1].label == "test (1)" + specviz_helper.load_spectrum(spectrum1d, data_label="test_1") + specviz_helper.load_spectrum(spectrum1d, data_label="test") + assert dc[2].label == "test_1" + assert dc[3].label == "test (2)" + + +def test_duplicate_data_labels_with_brackets(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") + specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") + dc = specviz_helper.app.data_collection + assert len(dc) == 2 + assert dc[0].label == "test[test]" + assert dc[1].label == "test[test] (1)" + + +def test_return_data_label_is_none(specviz_helper): + data_label = specviz_helper.app.return_data_label(None) + assert data_label == "Unknown" + + +def test_return_data_label_is_image(specviz_helper): + data_label = specviz_helper.app.return_data_label("data/path/test.jpg") + assert data_label == "test[jpg]" + + +def test_hdulist_with_filename(cubeviz_helper, image_cube_hdu_obj): + image_cube_hdu_obj.file_name = "test" + data_label = cubeviz_helper.app.return_data_label(image_cube_hdu_obj) + assert data_label == "test[HDU object]" + + +def test_file_path_not_image(imviz_helper, tmp_path): + path = tmp_path / "myimage.fits" + path.touch() + data_label = imviz_helper.app.return_data_label(str(path)) + assert data_label == "myimage" + + +def test_unique_name_variations(specviz_helper, spectrum1d): + data_label = specviz_helper.app.return_unique_name(None) + assert data_label == "Unknown" + + specviz_helper.load_spectrum(spectrum1d, data_label="test[flux]") + data_label = specviz_helper.app.return_data_label("test[flux]", ext="flux") + assert data_label == "test[flux][flux]" + + data_label = specviz_helper.app.return_data_label("test", ext="flux") + assert data_label == "test[flux] (1)" + + +def test_substring_in_label(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="M31") + specviz_helper.load_spectrum(spectrum1d, data_label="M32") + data_label = specviz_helper.app.return_data_label("M") + assert data_label == "M" + + +@pytest.mark.parametrize('data_label', ('111111', 'aaaaa', '///(#$@)', + 'two spaces repeating', + 'word42word42word two spaces')) +def test_edge_cases(specviz_helper, spectrum1d, data_label): + dc = specviz_helper.app.data_collection + + specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + assert dc[1].label == f"{data_label} (1)" + + specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + assert dc[2].label == f"{data_label} (2)" + + +def test_case_that_used_to_break_return_label(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="this used to break (1)") + specviz_helper.load_spectrum(spectrum1d, data_label="this used to break") + dc = specviz_helper.app.data_collection + assert dc[0].label == "this used to break (1)" + assert dc[1].label == "this used to break (2)" From a402b029c637aa042b5ff31792ae7be8b44dfcbc Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 8 Jun 2023 17:03:42 -0400 Subject: [PATCH 44/82] Properly deprecate getters --- jdaviz/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jdaviz/app.py b/jdaviz/app.py index 0289466bc8..3052cb935b 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -14,6 +14,7 @@ from astropy.io import fits from astropy.coordinates import Angle from astropy.time import Time +from astropy.utils.decorators import deprecated from astropy.utils.exceptions import AstropyDeprecationWarning from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion @@ -663,6 +664,7 @@ def get_viewer_by_id(self, vid): """ return self._viewer_store.get(vid) + @deprecated(since="3.6", alternative="get_data") def get_data_from_viewer(self, viewer_reference, data_label=None, cls='default', include_subsets=True): """ @@ -770,6 +772,7 @@ def get_data_from_viewer(self, viewer_reference, data_label=None, # If a data label was provided, return only the corresponding data, otherwise return all: return data.get(data_label, data) + @deprecated(since="3.6", alternative="get_subsets") def get_subsets_from_viewer(self, viewer_reference, data_label=None, subset_type=None): """ Returns the subsets of a specified viewer converted to astropy regions @@ -803,9 +806,6 @@ def get_subsets_from_viewer(self, viewer_reference, data_label=None, subset_type representing the subset name and values as astropy regions objects. """ - warnings.warn(AstropyDeprecationWarning("get_subsets_from_viewer() is deprecated in v3.6 " - "and will be removed in a future release. Use " - "get_subsets() instead.")) viewer = self.get_viewer(viewer_reference) data = self.get_data_from_viewer(viewer_reference, data_label, From e6b9eb41d1e5c22968c03fe7b9e551f8ecfec7d5 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 9 Jun 2023 09:38:21 -0400 Subject: [PATCH 45/82] Properly check for valueError on non-existent label --- jdaviz/app.py | 1 - jdaviz/tests/test_app.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jdaviz/app.py b/jdaviz/app.py index 3052cb935b..fd6debc2a1 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -15,7 +15,6 @@ from astropy.coordinates import Angle from astropy.time import Time from astropy.utils.decorators import deprecated -from astropy.utils.exceptions import AstropyDeprecationWarning from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion from echo import CallbackProperty, DictCallbackProperty, ListCallbackProperty diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py index db0a72f522..69dd0d1eaa 100644 --- a/jdaviz/tests/test_app.py +++ b/jdaviz/tests/test_app.py @@ -54,7 +54,8 @@ class Customviz(Specviz): assert viz.app.get_viewer_reference_names() == ['h', 'k'] viz.load_spectrum(spectrum1d, data_label='example label') - assert not len(viz.app.get_data("non-existent label")) + with pytest.raises(ValueError): + viz.get_data("non-existent label") def test_duplicate_data_labels(specviz_helper, spectrum1d): From 723fe1a2baddfbfede17e46f2d7331baa253c1a5 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 9 Jun 2023 10:12:44 -0400 Subject: [PATCH 46/82] Update Specviz get_data_from_viewer test --- jdaviz/configs/specviz/tests/test_helper.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py index 0f47db0d02..bade4d68aa 100644 --- a/jdaviz/configs/specviz/tests/test_helper.py +++ b/jdaviz/configs/specviz/tests/test_helper.py @@ -32,10 +32,9 @@ def test_load_spectrum1d(self): assert dc_0.label == self.label assert dc_0.meta['uncertainty_type'] == 'std' - data = self.spec_app.app.get_data_from_viewer('spectrum-viewer') + data = self.spec_app.get_data() - assert isinstance(list(data.values())[0], Spectrum1D) - assert list(data.keys())[0] == self.label + assert isinstance(data, Spectrum1D) def test_load_spectrum_list_no_labels(self): # now load three more spectra from a SpectrumList, without labels From 4d6e2a4d70728681e484b14efa1db14789511bbd Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 9 Jun 2023 10:58:19 -0400 Subject: [PATCH 47/82] Rename "subset_to_apply" to "spectral_subset --- docs/cubeviz/export_data.rst | 2 +- jdaviz/configs/specviz/helper.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/cubeviz/export_data.rst b/docs/cubeviz/export_data.rst index ede78dd192..f125aab68a 100644 --- a/docs/cubeviz/export_data.rst +++ b/docs/cubeviz/export_data.rst @@ -22,7 +22,7 @@ can be used to extract the *spectrum* of a spatial subset named "Subset 1": .. code-block:: python - subset1_spec1d = cubeviz.specviz.get_spectra(subset_to_apply="Subset 1") + subset1_spec1d = cubeviz.specviz.get_spectra(spectral_subset="Subset 1") An example without accessing Specviz: diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index e0969e7cdd..a2297a0597 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -1,6 +1,7 @@ import warnings from astropy import units as u +from astropy.utils.decorators import deprecated_renamed_argument from regions.core.core import Region from glue.core.subset_group import GroupedSubset from specutils import SpectralRegion, Spectrum1D @@ -68,7 +69,8 @@ def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, show_in_viewer=show_in_viewer, concat_by_file=concat_by_file) - def get_spectra(self, data_label=None, subset_to_apply=None, apply_slider_redshift="Warn"): + @deprecated_renamed_argument(old_name="subset_to_apply", new_name="spectral_subset", since="3.6") + def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshift="Warn"): """Returns the current data loaded into the main viewer """ @@ -81,16 +83,16 @@ def get_spectra(self, data_label=None, subset_to_apply=None, apply_slider_redshi if data_label is not None: spectrum = get_data_method(data_label=data_label, - spectral_subset=subset_to_apply, + spectral_subset=spectral_subset, cls=Spectrum1D) spectra[data_label] = spectrum else: for layer_state in viewer.state.layers: lyr = layer_state.layer - if subset_to_apply is not None: - if lyr.label == subset_to_apply: + if spectral_subset is not None: + if lyr.label == spectral_subset: spectrum = get_data_method(data_label=lyr.data.label, - spectral_subset=subset_to_apply, + spectral_subset=spectral_subset, cls=Spectrum1D, **function_kwargs) spectra[lyr.data.label] = spectrum @@ -291,7 +293,7 @@ def get_data(self, data_label=None, spectral_subset=None, cls=None, use_display_units=False, **kwargs): """ Returns data with name equal to data_label of type cls with subsets applied from - subset_to_apply. + spectral_subset. Parameters ---------- From ef6a38b80d693fe76675e6baefd0563de7320cd5 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 9 Jun 2023 12:04:22 -0400 Subject: [PATCH 48/82] Codestyle --- jdaviz/configs/specviz/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index a2297a0597..3a7c843bcd 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -69,7 +69,7 @@ def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, show_in_viewer=show_in_viewer, concat_by_file=concat_by_file) - @deprecated_renamed_argument(old_name="subset_to_apply", new_name="spectral_subset", since="3.6") + @deprecated_renamed_argument("subset_to_apply", "spectral_subset", "3.6") def get_spectra(self, data_label=None, spectral_subset=None, apply_slider_redshift="Warn"): """Returns the current data loaded into the main viewer From 0817e83d30bce0b8beddd61b462e79a4d5ca2728 Mon Sep 17 00:00:00 2001 From: Duy Tuong Nguyen Date: Fri, 9 Jun 2023 16:41:36 -0400 Subject: [PATCH 49/82] Fix docs wording Co-authored-by: Jesse Averbukh --- docs/cubeviz/export_data.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cubeviz/export_data.rst b/docs/cubeviz/export_data.rst index f125aab68a..11c439e7ad 100644 --- a/docs/cubeviz/export_data.rst +++ b/docs/cubeviz/export_data.rst @@ -41,7 +41,7 @@ To get all subsets from the spectrum viewer: .. code-block:: python - subset1_spec1d = cubeviz.app.get_subsets() + subset1_spec1d = cubeviz.specviz.app.get_subsets() To access the spatial regions themselves: From 867210656cd4f7f11eda1a4ee30fac0346238161 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:42:07 -0400 Subject: [PATCH 50/82] Catch missed code, fix bug --- CHANGES.rst | 9 +++++++++ jdaviz/app.py | 14 +++++++++----- .../cubeviz/plugins/tests/test_data_retrieval.py | 13 ++++++++----- jdaviz/core/helpers.py | 14 ++++++++------ jdaviz/core/template_mixin.py | 2 +- notebooks/CubevizExample.ipynb | 4 ++-- notebooks/ImvizDitheredExample.ipynb | 2 +- notebooks/ImvizExample.ipynb | 2 +- notebooks/concepts/cubeviz_data_interactions.ipynb | 6 +++--- notebooks/concepts/cubeviz_ndarray_gif.ipynb | 5 ++--- .../default_programmatic_viewers_from_blank.ipynb | 2 +- notebooks/concepts/imviz_roman_asdf.ipynb | 2 +- notebooks/concepts/mosviz_concept.ipynb | 2 +- 13 files changed, 47 insertions(+), 30 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 98c9b63545..55d25bf0d6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -39,6 +39,13 @@ Specviz2d API Changes ----------- +- ``viz.app.get_data_from_viewer()`` is deprecated; use ``viz.get_data()``. [#2242] + +- ``viz.app.get_subsets_from_viewer()`` is deprecated; use ``viz.app.get_subsets()``. [#2242] + +- ``viz.get_data()`` now takes optional ``**kwargs``; e.g., you could pass in + ``function="sum"`` to collapse a cube in Cubeviz. [#2242] + Cubeviz ^^^^^^^ @@ -61,6 +68,8 @@ Bug Fixes - Fixed wrong elliptical region translation in ``app.get_subsets()``. [#2244] +- Fixed ``cls`` input being ignored in ``viz.get_data()``. [#2242] + Cubeviz ^^^^^^^ diff --git a/jdaviz/app.py b/jdaviz/app.py index fd6debc2a1..f8d16356be 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -663,7 +663,7 @@ def get_viewer_by_id(self, vid): """ return self._viewer_store.get(vid) - @deprecated(since="3.6", alternative="get_data") + @deprecated(since="3.6", alternative="viz_helper.get_data") def get_data_from_viewer(self, viewer_reference, data_label=None, cls='default', include_subsets=True): """ @@ -806,9 +806,11 @@ def get_subsets_from_viewer(self, viewer_reference, data_label=None, subset_type objects. """ viewer = self.get_viewer(viewer_reference) - data = self.get_data_from_viewer(viewer_reference, - data_label, - cls=None) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + data = self.get_data_from_viewer(viewer_reference, + data_label, + cls=None) regions = {} if data_label is not None: @@ -848,7 +850,9 @@ def get_subsets_from_viewer(self, viewer_reference, data_label=None, subset_type regions[key] = self.get_subsets(key) continue - temp_data = self.get_data_from_viewer(viewer_reference, value.label) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + temp_data = self.get_data_from_viewer(viewer_reference, value.label) if isinstance(temp_data, Spectrum1D): regions[key] = self.get_subsets(key) continue diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_data_retrieval.py b/jdaviz/configs/cubeviz/plugins/tests/test_data_retrieval.py index 7509b7cc44..b5f1dfc6f5 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_data_retrieval.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_data_retrieval.py @@ -1,16 +1,17 @@ +import warnings + import pytest import numpy as np from astropy.utils.data import download_file -@pytest.mark.filterwarnings('ignore') @pytest.mark.remote_data def test_data_retrieval(cubeviz_helper): """The purpose of this test is to check that both methods: - app.get_viewer('spectrum-viewer').data() - - app.get_data_from_viewer("spectrum-viewer") + - cubeviz_helper.get_data() return the same spectrum values. """ @@ -20,14 +21,16 @@ def test_data_retrieval(cubeviz_helper): spectrum_viewer_reference_name = "spectrum-viewer" fn = download_file(URL, cache=True) - cubeviz_helper.load_data(fn) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + cubeviz_helper.load_data(fn) # two ways of retrieving data from the viewer. # They should return the same spectral values a1 = cubeviz_helper.app.get_viewer(spectrum_viewer_reference_name).data() - a2 = cubeviz_helper.app.get_data_from_viewer(spectrum_viewer_reference_name) + a2 = cubeviz_helper.get_data("contents[FLUX]", function="sum") test_value_1 = a1[0].data - test_value_2 = list(a2.values())[0].data + test_value_2 = a2.flux.value assert np.allclose(test_value_1, test_value_2, atol=1e-5) diff --git a/jdaviz/core/helpers.py b/jdaviz/core/helpers.py index 986547f28b..7a021d6421 100644 --- a/jdaviz/core/helpers.py +++ b/jdaviz/core/helpers.py @@ -554,7 +554,7 @@ def _handle_display_units(data, use_display_units): return _handle_display_units(data, use_display_units) - def get_data(self, data_label=None, cls=None, use_display_units=False): + def get_data(self, data_label=None, cls=None, use_display_units=False, **kwargs): """ Returns data with name equal to data_label of type cls. @@ -564,18 +564,20 @@ def get_data(self, data_label=None, cls=None, use_display_units=False): Provide a label to retrieve a specific data set from data_collection. cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional The type that data will be returned as. - use_display_units: bool, optional + use_display_units : bool, optional Whether to convert to the display units defined in the plugin. + kwargs : dict + For Cubeviz, you could also pass in ``function`` (str) to collapse + the cube into 1D spectrum using provided function. Returns ------- data : cls - Data is returned as type cls. + Data is returned as type ``cls``. """ - return self._get_data(data_label=data_label, spatial_subset=None, - spectral_subset=None, function=None, - cls=None, use_display_units=use_display_units) + return self._get_data(data_label=data_label, + cls=cls, use_display_units=use_display_units, **kwargs) class ImageConfigHelper(ConfigHelper): diff --git a/jdaviz/core/template_mixin.py b/jdaviz/core/template_mixin.py index 5bc71b4e57..1b8fece455 100644 --- a/jdaviz/core/template_mixin.py +++ b/jdaviz/core/template_mixin.py @@ -1067,7 +1067,7 @@ def _update_has_subregions(self): def selected_obj(self): if self.selected in self.manual_options or self.selected not in self.labels: return None - # NOTE: we use reference names here instead of IDs since get_subsets_from_viewer requires + # NOTE: we use reference names here instead of IDs since get_subsets requires # that. For imviz, this will mean we won't be able to loop through each of the viewers, # but the original viewer should have access to all the subsets. for viewer_ref in self.viewer_refs: diff --git a/notebooks/CubevizExample.ipynb b/notebooks/CubevizExample.ipynb index ac78ed88e7..af16b3998f 100644 --- a/notebooks/CubevizExample.ipynb +++ b/notebooks/CubevizExample.ipynb @@ -155,7 +155,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = cubeviz.app.get_data_from_viewer('flux-viewer', 'contents[FLUX]')" + "data = cubeviz.get_data('jw02732-o004_t004_miri_ch1-shortmediumlong_s3d.fits[SCI]')" ] }, { @@ -187,7 +187,7 @@ "metadata": {}, "outputs": [], "source": [ - "spectra_dict = cubeviz.specviz.get_spectra()\n", + "spectra_dict = cubeviz.specviz.get_spectra(apply_slider_redshift=True)\n", "spectra_dict" ] }, diff --git a/notebooks/ImvizDitheredExample.ipynb b/notebooks/ImvizDitheredExample.ipynb index 8af6aac55c..52c75d05cf 100644 --- a/notebooks/ImvizDitheredExample.ipynb +++ b/notebooks/ImvizDitheredExample.ipynb @@ -301,7 +301,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = imviz.app.get_data_from_viewer('imviz-0', 'acs_47tuc_1[SCI,1]')" + "data = imviz.get_data('acs_47tuc_1[SCI,1]')" ] }, { diff --git a/notebooks/ImvizExample.ipynb b/notebooks/ImvizExample.ipynb index b39014d4c5..1841efb1bd 100644 --- a/notebooks/ImvizExample.ipynb +++ b/notebooks/ImvizExample.ipynb @@ -348,7 +348,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = imviz.app.get_data_from_viewer('imviz-0', 'jw02727-o002_t062_nircam_clear-f090w_i2d[DATA]')" + "data = imviz.get_data('jw02727-o002_t062_nircam_clear-f090w_i2d[DATA]')" ] }, { diff --git a/notebooks/concepts/cubeviz_data_interactions.ipynb b/notebooks/concepts/cubeviz_data_interactions.ipynb index 862ba92047..fd6b6d0a2f 100644 --- a/notebooks/concepts/cubeviz_data_interactions.ipynb +++ b/notebooks/concepts/cubeviz_data_interactions.ipynb @@ -149,7 +149,7 @@ "source": [ "from specutils import Spectrum1D\n", "\n", - "spec_data = app.get_data_from_viewer('spectrum-viewer')\n", + "spec_data = app.get_viewer('spectrum-viewer').data()\n", "\n", "# The returned data from `get_data` is in list format, as it's \n", "# possible for there to be several data plotted in the viewer\n", @@ -170,7 +170,7 @@ "metadata": {}, "outputs": [], "source": [ - "spec = app.get_data_from_viewer('spectrum-viewer', '6de4c8ee5659e87a302e3de595074ba5[FLUX]')\n", + "spec = app._jdaviz_helper.get_data('6de4c8ee5659e87a302e3de595074ba5[FLUX]', function='sum')\n", "spec" ] }, @@ -267,7 +267,7 @@ "import matplotlib.pyplot as plt\n", "\n", "# The returned data object is a `CCData` class to represent the 2D nature of the image data.\n", - "image_data = app.get_data_from_viewer('flux-viewer')\n", + "image_data = app.get_viewer('flux-viewer').data()[0]\n", "\n", "f, ax = plt.subplots()\n", "\n", diff --git a/notebooks/concepts/cubeviz_ndarray_gif.ipynb b/notebooks/concepts/cubeviz_ndarray_gif.ipynb index e49026182c..f8dfc86423 100644 --- a/notebooks/concepts/cubeviz_ndarray_gif.ipynb +++ b/notebooks/concepts/cubeviz_ndarray_gif.ipynb @@ -102,8 +102,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = cubeviz.app.get_data_from_viewer('flux-viewer', 'ordered')\n", - "orig_cube = data.get_object(statistic=None)\n", + "orig_cube = cubeviz.get_data('ordered')\n", "orig_cube.shape # Input was (8, 5, 10) # x, y, z" ] }, @@ -124,7 +123,7 @@ "metadata": {}, "outputs": [], "source": [ - "data_2 = cubeviz.app.get_data_from_viewer('flux-viewer', 'roundtrip_test')\n", + "data_2 = cubeviz.get_data('roundtrip_test')\n", "data_2.shape" ] }, diff --git a/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb b/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb index 08b20d5f7d..3b885fa201 100644 --- a/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb +++ b/notebooks/concepts/default_programmatic_viewers_from_blank.ipynb @@ -280,7 +280,7 @@ "outputs": [], "source": [ "# can I access the data back out? \n", - "app.get_data_from_viewer('image-viewer')" + "app.get_viewer('image-viewer').data()" ] }, { diff --git a/notebooks/concepts/imviz_roman_asdf.ipynb b/notebooks/concepts/imviz_roman_asdf.ipynb index 818cd22d0d..7d03ba9886 100644 --- a/notebooks/concepts/imviz_roman_asdf.ipynb +++ b/notebooks/concepts/imviz_roman_asdf.ipynb @@ -202,7 +202,7 @@ "metadata": {}, "outputs": [], "source": [ - "data = imviz.app.get_data_from_viewer('imviz-0', imviz.app.data_collection[0].label)" + "data = imviz.get_data(imviz.app.data_collection[0].label)" ] }, { diff --git a/notebooks/concepts/mosviz_concept.ipynb b/notebooks/concepts/mosviz_concept.ipynb index c5372c5d78..d5426ce912 100644 --- a/notebooks/concepts/mosviz_concept.ipynb +++ b/notebooks/concepts/mosviz_concept.ipynb @@ -637,7 +637,7 @@ "source": [ "# Assuming the user has done something with a glue plugin that adds another spectrum to the spectrum view\n", "\n", - "currspec = mosviz.app.get_data_from_viewer('spectrum-viewer', 'spectrum-added-by-plugin')\n", + "currspec = mosviz.get_data('spectrum-added-by-plugin')\n", "\n", "... do something with currspec ..." ] From 1688ec16c5c3660dbb90bea2e36c0b6619d105e7 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:25:13 -0400 Subject: [PATCH 51/82] Retain Mosviz get_data behavior and fix change log --- CHANGES.rst | 5 ++++- jdaviz/configs/mosviz/plugins/viewers.py | 2 +- jdaviz/configs/mosviz/tests/test_data_loading.py | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 55d25bf0d6..a8b8611b0d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -55,10 +55,13 @@ Imviz Mosviz ^^^^^^ +- Added new ``statistic`` keyword to ``mosviz.get_viewer("spectrum-2d-viewer").data()`` + to allow user to collapse 2D spectrum to 1D. [#2242] + Specviz ^^^^^^^ -* Re-enabled unit conversion support. [#2127] +- Re-enabled unit conversion support. [#2127] Specviz2d ^^^^^^^^^ diff --git a/jdaviz/configs/mosviz/plugins/viewers.py b/jdaviz/configs/mosviz/plugins/viewers.py index c4cd8e1760..c5ad804655 100644 --- a/jdaviz/configs/mosviz/plugins/viewers.py +++ b/jdaviz/configs/mosviz/plugins/viewers.py @@ -43,7 +43,7 @@ def __init__(self, *args, **kwargs): self.figure.fig_margin = {'left': 0, 'bottom': 0, 'top': 0, 'right': 0} def data(self, cls=None): - return [layer_state.layer # .get_object(cls=cls or self.default_class) + return [layer_state.layer.get_object(cls=cls or self.default_class) for layer_state in self.state.layers if hasattr(layer_state, 'layer') and isinstance(layer_state.layer, BaseData)] diff --git a/jdaviz/configs/mosviz/tests/test_data_loading.py b/jdaviz/configs/mosviz/tests/test_data_loading.py index 83b12651d3..67d73ffe1c 100644 --- a/jdaviz/configs/mosviz/tests/test_data_loading.py +++ b/jdaviz/configs/mosviz/tests/test_data_loading.py @@ -2,9 +2,9 @@ from zipfile import ZipFile -from glue.core import Data import numpy as np import pytest +from astropy.nddata import CCDData from specutils import Spectrum1D from jdaviz.utils import PRIHDR_KEY @@ -48,11 +48,11 @@ def test_load_image(mosviz_helper, mos_image): table.widget_table.vue_on_row_clicked(0) data = mosviz_helper.app.get_viewer(mosviz_helper._default_image_viewer_reference_name - ).data() + ).data(cls=CCDData) assert len(data) == 1 dataval = data[0] - assert isinstance(dataval, Data) + assert isinstance(dataval, CCDData) assert dataval.shape == (55, 55) From 06f2ed022721bee6604200d283dd66792dd15d17 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:23:29 -0400 Subject: [PATCH 52/82] Undo bad diff --- jdaviz/tests/test_app.py | 286 +++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 143 deletions(-) diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py index 69dd0d1eaa..c72d4fee27 100644 --- a/jdaviz/tests/test_app.py +++ b/jdaviz/tests/test_app.py @@ -1,143 +1,143 @@ -import pytest - -from jdaviz import Application, Specviz -from jdaviz.configs.default.plugins.gaussian_smooth.gaussian_smooth import GaussianSmooth - - -# This applies to all viz but testing with Imviz should be enough. -def test_viewer_calling_app(imviz_helper): - viewer = imviz_helper.default_viewer - assert viewer.session.jdaviz_app is imviz_helper.app - - -def test_get_tray_item_from_name(): - app = Application(configuration='default') - plg = app.get_tray_item_from_name('g-gaussian-smooth') - assert isinstance(plg, GaussianSmooth) - - with pytest.raises(KeyError, match='not found in app'): - app.get_tray_item_from_name('imviz-compass') - - -def test_nonstandard_specviz_viewer_name(spectrum1d): - config = {'settings': {'configuration': 'nonstandard', - 'data': {'parser': 'specviz-spectrum1d-parser'}, - 'visible': {'menu_bar': False, - 'toolbar': True, - 'tray': True, - 'tab_headers': False}, - 'context': {'notebook': {'max_height': '750px'}}}, - 'toolbar': ['g-data-tools', 'g-subset-tools'], - 'tray': ['g-metadata-viewer', - 'g-plot-options', - 'g-subset-plugin', - 'g-gaussian-smooth', - 'g-model-fitting', - 'g-unit-conversion', - 'g-line-list', - 'specviz-line-analysis', - 'g-export-plot'], - 'viewer_area': [{'container': 'col', - 'children': [{'container': 'row', - 'viewers': [{'name': 'H', - 'plot': 'specviz-profile-viewer', - 'reference': 'h'}, - {'name': 'K', - 'plot': 'specviz-profile-viewer', - 'reference': 'k'}]}]}]} - - class Customviz(Specviz): - _default_configuration = config - _default_spectrum_viewer_reference_name = 'h' - - viz = Customviz() - assert viz.app.get_viewer_reference_names() == ['h', 'k'] - - viz.load_spectrum(spectrum1d, data_label='example label') - with pytest.raises(ValueError): - viz.get_data("non-existent label") - - -def test_duplicate_data_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test") - specviz_helper.load_spectrum(spectrum1d, data_label="test") - dc = specviz_helper.app.data_collection - assert dc[0].label == "test" - assert dc[1].label == "test (1)" - specviz_helper.load_spectrum(spectrum1d, data_label="test_1") - specviz_helper.load_spectrum(spectrum1d, data_label="test") - assert dc[2].label == "test_1" - assert dc[3].label == "test (2)" - - -def test_duplicate_data_labels_with_brackets(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") - dc = specviz_helper.app.data_collection - assert len(dc) == 2 - assert dc[0].label == "test[test]" - assert dc[1].label == "test[test] (1)" - - -def test_return_data_label_is_none(specviz_helper): - data_label = specviz_helper.app.return_data_label(None) - assert data_label == "Unknown" - - -def test_return_data_label_is_image(specviz_helper): - data_label = specviz_helper.app.return_data_label("data/path/test.jpg") - assert data_label == "test[jpg]" - - -def test_hdulist_with_filename(cubeviz_helper, image_cube_hdu_obj): - image_cube_hdu_obj.file_name = "test" - data_label = cubeviz_helper.app.return_data_label(image_cube_hdu_obj) - assert data_label == "test[HDU object]" - - -def test_file_path_not_image(imviz_helper, tmp_path): - path = tmp_path / "myimage.fits" - path.touch() - data_label = imviz_helper.app.return_data_label(str(path)) - assert data_label == "myimage" - - -def test_unique_name_variations(specviz_helper, spectrum1d): - data_label = specviz_helper.app.return_unique_name(None) - assert data_label == "Unknown" - - specviz_helper.load_spectrum(spectrum1d, data_label="test[flux]") - data_label = specviz_helper.app.return_data_label("test[flux]", ext="flux") - assert data_label == "test[flux][flux]" - - data_label = specviz_helper.app.return_data_label("test", ext="flux") - assert data_label == "test[flux] (1)" - - -def test_substring_in_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="M31") - specviz_helper.load_spectrum(spectrum1d, data_label="M32") - data_label = specviz_helper.app.return_data_label("M") - assert data_label == "M" - - -@pytest.mark.parametrize('data_label', ('111111', 'aaaaa', '///(#$@)', - 'two spaces repeating', - 'word42word42word two spaces')) -def test_edge_cases(specviz_helper, spectrum1d, data_label): - dc = specviz_helper.app.data_collection - - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - assert dc[1].label == f"{data_label} (1)" - - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - assert dc[2].label == f"{data_label} (2)" - - -def test_case_that_used_to_break_return_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break (1)") - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break") - dc = specviz_helper.app.data_collection - assert dc[0].label == "this used to break (1)" - assert dc[1].label == "this used to break (2)" +import pytest + +from jdaviz import Application, Specviz +from jdaviz.configs.default.plugins.gaussian_smooth.gaussian_smooth import GaussianSmooth + + +# This applies to all viz but testing with Imviz should be enough. +def test_viewer_calling_app(imviz_helper): + viewer = imviz_helper.default_viewer + assert viewer.session.jdaviz_app is imviz_helper.app + + +def test_get_tray_item_from_name(): + app = Application(configuration='default') + plg = app.get_tray_item_from_name('g-gaussian-smooth') + assert isinstance(plg, GaussianSmooth) + + with pytest.raises(KeyError, match='not found in app'): + app.get_tray_item_from_name('imviz-compass') + + +def test_nonstandard_specviz_viewer_name(spectrum1d): + config = {'settings': {'configuration': 'nonstandard', + 'data': {'parser': 'specviz-spectrum1d-parser'}, + 'visible': {'menu_bar': False, + 'toolbar': True, + 'tray': True, + 'tab_headers': False}, + 'context': {'notebook': {'max_height': '750px'}}}, + 'toolbar': ['g-data-tools', 'g-subset-tools'], + 'tray': ['g-metadata-viewer', + 'g-plot-options', + 'g-subset-plugin', + 'g-gaussian-smooth', + 'g-model-fitting', + 'g-unit-conversion', + 'g-line-list', + 'specviz-line-analysis', + 'g-export-plot'], + 'viewer_area': [{'container': 'col', + 'children': [{'container': 'row', + 'viewers': [{'name': 'H', + 'plot': 'specviz-profile-viewer', + 'reference': 'h'}, + {'name': 'K', + 'plot': 'specviz-profile-viewer', + 'reference': 'k'}]}]}]} + + class Customviz(Specviz): + _default_configuration = config + _default_spectrum_viewer_reference_name = 'h' + + viz = Customviz() + assert viz.app.get_viewer_reference_names() == ['h', 'k'] + + viz.load_spectrum(spectrum1d, data_label='example label') + with pytest.raises(ValueError): + viz.get_data("non-existent label") + + +def test_duplicate_data_labels(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_spectrum(spectrum1d, data_label="test") + dc = specviz_helper.app.data_collection + assert dc[0].label == "test" + assert dc[1].label == "test (1)" + specviz_helper.load_spectrum(spectrum1d, data_label="test_1") + specviz_helper.load_spectrum(spectrum1d, data_label="test") + assert dc[2].label == "test_1" + assert dc[3].label == "test (2)" + + +def test_duplicate_data_labels_with_brackets(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") + specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") + dc = specviz_helper.app.data_collection + assert len(dc) == 2 + assert dc[0].label == "test[test]" + assert dc[1].label == "test[test] (1)" + + +def test_return_data_label_is_none(specviz_helper): + data_label = specviz_helper.app.return_data_label(None) + assert data_label == "Unknown" + + +def test_return_data_label_is_image(specviz_helper): + data_label = specviz_helper.app.return_data_label("data/path/test.jpg") + assert data_label == "test[jpg]" + + +def test_hdulist_with_filename(cubeviz_helper, image_cube_hdu_obj): + image_cube_hdu_obj.file_name = "test" + data_label = cubeviz_helper.app.return_data_label(image_cube_hdu_obj) + assert data_label == "test[HDU object]" + + +def test_file_path_not_image(imviz_helper, tmp_path): + path = tmp_path / "myimage.fits" + path.touch() + data_label = imviz_helper.app.return_data_label(str(path)) + assert data_label == "myimage" + + +def test_unique_name_variations(specviz_helper, spectrum1d): + data_label = specviz_helper.app.return_unique_name(None) + assert data_label == "Unknown" + + specviz_helper.load_spectrum(spectrum1d, data_label="test[flux]") + data_label = specviz_helper.app.return_data_label("test[flux]", ext="flux") + assert data_label == "test[flux][flux]" + + data_label = specviz_helper.app.return_data_label("test", ext="flux") + assert data_label == "test[flux] (1)" + + +def test_substring_in_label(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="M31") + specviz_helper.load_spectrum(spectrum1d, data_label="M32") + data_label = specviz_helper.app.return_data_label("M") + assert data_label == "M" + + +@pytest.mark.parametrize('data_label', ('111111', 'aaaaa', '///(#$@)', + 'two spaces repeating', + 'word42word42word two spaces')) +def test_edge_cases(specviz_helper, spectrum1d, data_label): + dc = specviz_helper.app.data_collection + + specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + assert dc[1].label == f"{data_label} (1)" + + specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + assert dc[2].label == f"{data_label} (2)" + + +def test_case_that_used_to_break_return_label(specviz_helper, spectrum1d): + specviz_helper.load_spectrum(spectrum1d, data_label="this used to break (1)") + specviz_helper.load_spectrum(spectrum1d, data_label="this used to break") + dc = specviz_helper.app.data_collection + assert dc[0].label == "this used to break (1)" + assert dc[1].label == "this used to break (2)" From a160c5fca21d3de3dc97dc026ffbb6e99e9b03b0 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:47:07 -0400 Subject: [PATCH 53/82] MNT: Add .mailmap so git shortlog -es gives sane listing. --- .mailmap | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..3e8f9e57a9 --- /dev/null +++ b/.mailmap @@ -0,0 +1,25 @@ +Brett Graham +Brett M. Morris +Camilla Pacifici +Craig Jones +Duy Nguyen +Duy Nguyen +Duy Nguyen Duy Tuong Nguyen +Duy Nguyen Duy Tuong Nguyen +Duy Nguyen duytnguyendtn +Ivo Busko Ivo +Ivo Busko busko +Ivo Busko busko@stsci.edu +Jennifer Kotler +Jennifer Kotler +Jesse Averbukh +Josh Soref <2119212+jsoref@users.noreply.github.com> +Kyle Conroy Kyle +Mario Buikhuizen +Nicholas Earl +Nicholas Earl nmearl +Ori Fox Ori +P. L. Lim <2090236+pllim@users.noreply.github.com> +Patrick Ogle PatrickOgle +Ricky O'Steen <39831871+rosteen@users.noreply.github.com> +Ricky O'Steen <39831871+rosteen@users.noreply.github.com> rosteen <39831871+rosteen@users.noreply.github.com> From d5fbd700850fb509e67b95afec6188b4a008a6e8 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Fri, 23 Jun 2023 07:21:28 -0700 Subject: [PATCH 54/82] DOC: Add warning about surface brightness in Simple Aperture Photometry plugin (#2261) * DOC: Add warning about surface brightness in Simple Aperture Photometry plugin. I am beginning to think the Simple in plugin name no longer applies. * DOC: Improve verbiage. Co-authored-by: Camilla Pacifici --------- Co-authored-by: Camilla Pacifici --- docs/imviz/plugins.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index 4ae367b37e..a69819d66f 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -202,6 +202,15 @@ an interactively selected region. A typical workflow is as follows: in display data unit. Otherwise, it is only informational. If this field is not applicable for you, leave it at 0. **This field resets every time Data selection changes if auto-population not possible.** + + .. warning:: + + If your data is in surface brightness units and pixels on the image + have varying sky area, you should first convert your data from + surface brightness to flux units before using this plugin. + This is because, for performance reasons, the plugin multiplies + by the area after the aperture sum is calculated. + 7. If you also want photometry result in the unit of counts, you can enter a conversion factor in the :guilabel:`Counts conversion factor` field. The value must be in the unit of display data unit per counts. This is used to convert linear From bc6cdee595ae972c72b79bb5f51bd76533dbd788 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Thu, 29 Jun 2023 08:11:48 -0700 Subject: [PATCH 55/82] FEAT: Annulus draw tool for Imviz (#2240) * FEAT: Annulus draw tool for Imviz. TODO: Need to fix icon. TST: Add tests. [ci skip] [rtd skip] * Pull in #2204 into this PR and bump upstream pins * Changed function name upstream in glue-astronomy during review process * Proper annulus icon from J. Kotler Co-authored-by: Jennifer Kotler * Avoid error traceback with bad annulus radii * Disable recentering for annulus * Fix test failure because not sure why spectral region is using Imviz centering method but okay. --------- Co-authored-by: Jennifer Kotler Co-authored-by: Ricky O'Steen --- CHANGES.rst | 3 +- jdaviz/app.py | 28 +------- .../plugins/subset_plugin/subset_plugin.py | 67 +++++++++---------- jdaviz/configs/imviz/plugins/viewers.py | 2 +- jdaviz/core/tools.py | 6 +- jdaviz/data/icons/select_annulus.svg | 12 ++++ jdaviz/tests/test_subsets.py | 27 +++++--- pyproject.toml | 4 +- 8 files changed, 74 insertions(+), 75 deletions(-) create mode 100644 jdaviz/data/icons/select_annulus.svg diff --git a/CHANGES.rst b/CHANGES.rst index a8b8611b0d..3b6639f105 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,7 +20,8 @@ Imviz - Added the ability to load DS9 region files (``.reg``) using the ``IMPORT DATA`` button. However, this only works after loading at least one image into Imviz. [#2201] -- Added support for new ``CircularAnnulusROI`` subset from glue. [#2201] +- Added support for new ``CircularAnnulusROI`` subset from glue, including + a new draw tool. [#2201, #2240] Mosviz ^^^^^^ diff --git a/jdaviz/app.py b/jdaviz/app.py index f8d16356be..1bdd8df36f 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -12,11 +12,8 @@ from astropy import units as u from astropy.nddata import CCDData, NDData from astropy.io import fits -from astropy.coordinates import Angle from astropy.time import Time from astropy.utils.decorators import deprecated -from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion - from echo import CallbackProperty, DictCallbackProperty, ListCallbackProperty from ipygoldenlayout import GoldenLayout from ipysplitpanes import SplitPanes @@ -39,9 +36,9 @@ from glue.core.state_objects import State from glue.core.subset import (Subset, RangeSubsetState, RoiSubsetState, CompositeSubsetState, InvertState) -from glue.core.roi import CircularROI, EllipticalROI, RectangularROI from glue.core.units import unit_converter from glue_astronomy.spectral_coordinates import SpectralCoordinates +from glue_astronomy.translators.regions import roi_subset_state_to_region from glue_jupyter.app import JupyterApplication from glue_jupyter.common.toolbar_vuetify import read_icon from glue_jupyter.state_traitlets_helpers import GlueState @@ -1055,27 +1052,8 @@ def _get_range_subset_bounds(self, subset_state, return spec_region def _get_roi_subset_definition(self, subset_state): - _around_decimals = 6 - roi = subset_state.roi - roi_as_region = None - if isinstance(roi, CircularROI): - x, y = roi.get_center() - r = roi.radius - roi_as_region = CirclePixelRegion(PixCoord(x, y), r) - - elif isinstance(roi, RectangularROI): - theta = np.around(np.degrees(roi.theta), decimals=_around_decimals) - roi_as_region = RectanglePixelRegion(PixCoord(roi.center()[0], roi.center()[1]), - roi.width(), roi.height(), Angle(theta, "deg")) - - elif isinstance(roi, EllipticalROI): - xc = roi.xc - yc = roi.yc - rx = roi.radius_x - ry = roi.radius_y - theta = np.around(np.degrees(roi.theta), decimals=_around_decimals) - roi_as_region = EllipsePixelRegion(PixCoord(xc, yc), rx * 2, ry * 2, Angle(theta, "deg")) # noqa: E501 - + # TODO: Imviz: Return sky region if link type is WCS. + roi_as_region = roi_subset_state_to_region(subset_state) return [{"name": subset_state.roi.__class__.__name__, "glue_state": subset_state.__class__.__name__, "region": roi_as_region, diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py index 5bd4fb22b3..12d881ee44 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py +++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py @@ -7,8 +7,8 @@ from glue.core.message import EditSubsetMessage, SubsetUpdateMessage from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode, ReplaceMode, XorMode) -from glue.core.roi import CircularROI, EllipticalROI, RectangularROI -from glue.core.subset import RoiSubsetState, RangeSubsetState, CompositeSubsetState +from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI +from glue.core.subset import RoiSubsetState, RangeSubsetState from glue.icons import icon_path from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu from glue_jupyter.common.toolbar_vuetify import read_icon @@ -149,7 +149,11 @@ def _unpack_get_subsets_for_ui(self): _around_decimals = 6 # Avoid 30 degrees from coming back as 29.999999999999996 if not subset_information: return - if len(subset_information) == 1: + if ((len(subset_information) == 1) and + (isinstance(subset_information[0]["subset_state"], RangeSubsetState) or + (isinstance(subset_information[0]["subset_state"], RoiSubsetState) and + isinstance(subset_information[0]["subset_state"].roi, + (CircularROI, RectangularROI, EllipticalROI))))): self.is_centerable = True else: self.is_centerable = False @@ -161,7 +165,7 @@ def _unpack_get_subsets_for_ui(self): glue_state = spec["glue_state"] if isinstance(subset_state, RoiSubsetState): if isinstance(subset_state.roi, CircularROI): - x, y = subset_state.roi.get_center() + x, y = subset_state.roi.center() r = subset_state.roi.radius subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x}, {"name": "Y Center", "att": "yc", "value": y, "orig": y}, @@ -178,8 +182,7 @@ def _unpack_get_subsets_for_ui(self): {"name": "Angle", "att": "theta", "value": theta, "orig": theta}) elif isinstance(subset_state.roi, EllipticalROI): - xc = subset_state.roi.xc - yc = subset_state.roi.yc + xc, yc = subset_state.roi.center() rx = subset_state.roi.radius_x ry = subset_state.roi.radius_y theta = np.around(np.degrees(subset_state.roi.theta), decimals=_around_decimals) @@ -190,6 +193,17 @@ def _unpack_get_subsets_for_ui(self): {"name": "Y Radius", "att": "radius_y", "value": ry, "orig": ry}, {"name": "Angle", "att": "theta", "value": theta, "orig": theta}] + elif isinstance(subset_state.roi, CircularAnnulusROI): + x, y = subset_state.roi.center() + inner_r = subset_state.roi.inner_radius + outer_r = subset_state.roi.outer_radius + subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x}, + {"name": "Y Center", "att": "yc", "value": y, "orig": y}, + {"name": "Inner radius", "att": "inner_radius", + "value": inner_r, "orig": inner_r}, + {"name": "Outer radius", "att": "outer_radius", + "value": outer_r, "orig": outer_r}] + subset_type = subset_state.roi.__class__.__name__ elif isinstance(subset_state, RangeSubsetState): @@ -303,6 +317,7 @@ def _check_input(self): reason = "" for index, sub in enumerate(self.subset_definitions): lo = hi = xmin = xmax = ymin = ymax = None + inner_radius = outer_radius = None for d_att in sub: if d_att["att"] == "lo": lo = d_att["value"] @@ -320,6 +335,10 @@ def _check_input(self): ymin = d_att["value"] elif d_att["att"] == "ymax": ymax = d_att["value"] + elif d_att["att"] == "outer_radius": + outer_radius = d_att["value"] + elif d_att["att"] == "inner_radius": + inner_radius = d_att["value"] if lo and hi and hi <= lo: status = False @@ -329,6 +348,10 @@ def _check_input(self): status = False reason = "Failed to update Subset: width and length must be positive scalars" break + elif inner_radius and outer_radius and inner_radius >= outer_radius: + status = False + reason = "Failed to update Subset: inner radius must be less than outer radius" + break return status, reason @@ -375,39 +398,13 @@ def get_center(self): depending on the Subset type, if applicable. If Subset is not centerable, this returns `None`. - Raises - ------ - NotImplementedError - Subset type is not supported. - """ # Composite region cannot be centered. if not self.is_centerable: # no-op return subset_state = self.subset_select.selected_subset_state - - if isinstance(subset_state, RoiSubsetState): - sbst_obj = subset_state.roi - if isinstance(sbst_obj, (CircularROI, EllipticalROI)): - cen = sbst_obj.get_center() - elif isinstance(sbst_obj, RectangularROI): - cen = sbst_obj.center() - else: # pragma: no cover - raise NotImplementedError( - f'Getting center of {sbst_obj.__class__} is not supported') - - elif isinstance(subset_state, RangeSubsetState): - cen = (subset_state.hi - subset_state.lo) * 0.5 + subset_state.lo - - elif isinstance(subset_state, CompositeSubsetState): - cen = None - - else: # pragma: no cover - raise NotImplementedError( - f'Getting center of {subset_state.__class__} is not supported') - - return cen + return subset_state.center() def set_center(self, new_cen, update=False): """Set the desired center for the selected Subset, if applicable. @@ -439,7 +436,7 @@ def set_center(self, new_cen, update=False): if isinstance(subset_state, RoiSubsetState): x, y = new_cen sbst_obj = subset_state.roi - if isinstance(sbst_obj, (CircularROI, EllipticalROI)): + if isinstance(sbst_obj, (CircularROI, CircularAnnulusROI, EllipticalROI)): self._set_value_in_subset_definition(0, "X Center", "value", x) self._set_value_in_subset_definition(0, "Y Center", "value", y) elif isinstance(sbst_obj, RectangularROI): @@ -454,7 +451,7 @@ def set_center(self, new_cen, update=False): raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported') elif isinstance(subset_state, RangeSubsetState): - dx = new_cen - ((subset_state.hi - subset_state.lo) * 0.5 + subset_state.lo) + dx = new_cen - subset_state.center() self._set_value_in_subset_definition(0, "Lower bound", "value", subset_state.lo + dx) self._set_value_in_subset_definition(0, "Upper bound", "value", subset_state.hi + dx) diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py index 46467b92a4..958d076fd1 100644 --- a/jdaviz/configs/imviz/plugins/viewers.py +++ b/jdaviz/configs/imviz/plugins/viewers.py @@ -24,7 +24,7 @@ class ImvizImageView(JdavizViewerMixin, BqplotImageView, AstrowidgetsImageViewer ['jdaviz:homezoom', 'jdaviz:prevzoom'], ['jdaviz:boxzoommatch', 'jdaviz:boxzoom'], ['jdaviz:panzoommatch', 'jdaviz:imagepanzoom'], - ['bqplot:circle', 'bqplot:rectangle', 'bqplot:ellipse', + ['bqplot:circle', 'bqplot:rectangle', 'bqplot:ellipse', 'bqplot:circannulus', 'jdaviz:singlepixelregion'], ['jdaviz:blinkonce', 'jdaviz:contrastbias'], ['jdaviz:sidebar_plot', 'jdaviz:sidebar_export', 'jdaviz:sidebar_compass'] diff --git a/jdaviz/core/tools.py b/jdaviz/core/tools.py index f86618d77e..f6dea95d86 100644 --- a/jdaviz/core/tools.py +++ b/jdaviz/core/tools.py @@ -9,8 +9,9 @@ HomeTool, BqplotPanZoomMode, BqplotPanZoomXMode, BqplotPanZoomYMode, BqplotRectangleMode, BqplotCircleMode, - BqplotEllipseMode, BqplotXRangeMode, - BqplotYRangeMode, BqplotSelectionTool, + BqplotEllipseMode, BqplotCircularAnnulusMode, + BqplotXRangeMode, BqplotYRangeMode, + BqplotSelectionTool, INTERACT_COLOR) from bqplot.interacts import BrushSelector, BrushIntervalSelector @@ -25,6 +26,7 @@ BqplotRectangleMode.icon = os.path.join(ICON_DIR, 'select_xy.svg') BqplotCircleMode.icon = os.path.join(ICON_DIR, 'select_circle.svg') BqplotEllipseMode.icon = os.path.join(ICON_DIR, 'select_ellipse.svg') +BqplotCircularAnnulusMode.icon = os.path.join(ICON_DIR, 'select_annulus.svg') BqplotXRangeMode.icon = os.path.join(ICON_DIR, 'select_x.svg') BqplotYRangeMode.icon = os.path.join(ICON_DIR, 'select_y.svg') diff --git a/jdaviz/data/icons/select_annulus.svg b/jdaviz/data/icons/select_annulus.svg new file mode 100644 index 0000000000..86863e1456 --- /dev/null +++ b/jdaviz/data/icons/select_annulus.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index 8d4066bdf7..3d06573f56 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -3,9 +3,10 @@ from astropy import units as u from astropy.tests.helper import assert_quantity_allclose from glue.core import Data -from glue.core.roi import CircularROI, EllipticalROI, RectangularROI, XRangeROI +from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI, XRangeROI from glue.core.edit_subset_mode import AndMode, AndNotMode, OrMode, XorMode -from regions import PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion +from regions import (PixCoord, CirclePixelRegion, RectanglePixelRegion, EllipsePixelRegion, + CircleAnnulusPixelRegion) from numpy.testing import assert_allclose from specutils import SpectralRegion, Spectrum1D @@ -316,7 +317,7 @@ def test_composite_region_from_subset_3d(cubeviz_helper): 'subset_state': reg[-1]['subset_state']} cubeviz_helper.app.session.edit_subset_mode.mode = OrMode - viewer.apply_roi(EllipticalROI(30, 30, 3, 6)) + viewer.apply_roi(EllipticalROI(xc=30, yc=30, radius_x=3, radius_y=6)) reg = cubeviz_helper.app.get_subsets("Subset 1") ellipse1 = EllipsePixelRegion(center=PixCoord(x=30, y=30), width=6, height=12, angle=0.0 * u.deg) @@ -368,7 +369,7 @@ def test_composite_region_with_consecutive_and_not_states(cubeviz_helper): 'subset_state': reg[-1]['subset_state']} cubeviz_helper.app.session.edit_subset_mode.mode = AndNotMode - viewer.apply_roi(EllipticalROI(30, 30, 3, 6)) + viewer.apply_roi(EllipticalROI(xc=30, yc=30, radius_x=3, radius_y=6)) reg = cubeviz_helper.app.get_subsets("Subset 1") ellipse1 = EllipsePixelRegion(center=PixCoord(x=30, y=30), width=6, height=12, angle=0.0 * u.deg) @@ -413,7 +414,7 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): arr = np.ones((10, 10)) data_label = 'image-data' - viewer = imviz_helper.app.get_viewer('imviz-0') + viewer = imviz_helper.default_viewer imviz_helper.load_data(arr, data_label=data_label, show_in_viewer=True) viewer.apply_roi(CircularROI(xc=5, yc=5, radius=2)) reg = imviz_helper.app.get_subsets("Subset 1") @@ -422,7 +423,7 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): 'subset_state': reg[-1]['subset_state']} imviz_helper.app.session.edit_subset_mode.mode = AndNotMode - viewer.apply_roi(RectangularROI(2, 4, 2, 4)) + viewer.apply_roi(RectangularROI(xmin=2, xmax=4, ymin=2, ymax=4)) reg = imviz_helper.app.get_subsets("Subset 1") rectangle1 = RectanglePixelRegion(center=PixCoord(x=3, y=3), width=2, height=2, angle=0.0 * u.deg) @@ -430,17 +431,25 @@ def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): 'subset_state': reg[-1]['subset_state']} imviz_helper.app.session.edit_subset_mode.mode = AndNotMode - viewer.apply_roi(EllipticalROI(3, 3, 3, 6)) + viewer.apply_roi(EllipticalROI(xc=3, yc=3, radius_x=3, radius_y=6)) reg = imviz_helper.app.get_subsets("Subset 1") ellipse1 = EllipsePixelRegion(center=PixCoord(x=3, y=3), width=6, height=12, angle=0.0 * u.deg) assert reg[-1] == {'name': 'EllipticalROI', 'glue_state': 'AndNotState', 'region': ellipse1, 'subset_state': reg[-1]['subset_state']} + imviz_helper.app.session.edit_subset_mode.mode = OrMode + viewer.apply_roi(CircularAnnulusROI(xc=5, yc=5, inner_radius=2.5, outer_radius=5)) + reg = imviz_helper.app.get_subsets("Subset 1") + ann1 = CircleAnnulusPixelRegion(center=PixCoord(x=5, y=5), inner_radius=2.5, outer_radius=5) + assert reg[-1] == {'name': 'CircularAnnulusROI', 'glue_state': 'OrState', 'region': ann1, + 'subset_state': reg[-1]['subset_state']} + subset_plugin = imviz_helper.app.get_tray_item_from_name('g-subset-plugin') assert subset_plugin.subset_selected == "Subset 1" - assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI'] - assert subset_plugin.glue_state_types == ['AndState', 'AndNotState', 'AndNotState'] + assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI', + 'CircularAnnulusROI'] + assert subset_plugin.glue_state_types == ['AndState', 'AndNotState', 'AndNotState', 'OrState'] def test_with_invalid_subset_name(cubeviz_helper): diff --git a/pyproject.toml b/pyproject.toml index 44e46a75a0..e06a9f7943 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "bqplot>=0.12.37", "bqplot-image-gl>=1.4.11", "glue-core>=1.11", - "glue-jupyter>=0.16.3", + "glue-jupyter>=0.17", "echo>=0.5.0", "ipykernel>=6.19.4", "ipyvue>=1.6", @@ -26,7 +26,7 @@ dependencies = [ "specutils>=1.9", "specreduce>=1.3.0,<1.4.0", "photutils>=1.4", - "glue-astronomy>=0.9", + "glue-astronomy>=0.10", "asteval>=0.9.23", "idna", "vispy>=0.6.5", From 55a19f4fe455a02e09e910a77c7bfee4c3e6480f Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Thu, 29 Jun 2023 08:20:31 -0700 Subject: [PATCH 56/82] MNT: Temporarily pin voila<0.5 (#2269) * MNT: Temp pin voila<0.5 because voila-template is incompatible, see #2268 * TST: Disable voila dev in test matrix --- pyproject.toml | 2 +- tox.ini | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e06a9f7943..3d1023b832 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "ipysplitpanes>=0.1.0", "ipygoldenlayout>=0.3.0", "ipywidgets>=8.0.6", - "voila>=0.4", + "voila>=0.4,<0.5", "pyyaml>=5.4.1", "specutils>=1.9", "specreduce>=1.3.0,<1.4.0", diff --git a/tox.ini b/tox.ini index c101fc241b..d212680cbd 100644 --- a/tox.ini +++ b/tox.ini @@ -53,7 +53,8 @@ deps = devdeps: git+https://github.com/spacetelescope/stdatamodels.git devdeps: git+https://github.com/bqplot/bqplot.git@0.12.x devdeps: git+https://github.com/glue-viz/glue.git - devdeps: git+https://github.com/voila-dashboards/voila.git + # FIXME: https://github.com/spacetelescope/jdaviz/pull/2268 + #devdeps: git+https://github.com/voila-dashboards/voila.git devdeps: git+https://github.com/glue-viz/bqplot-image-gl.git devdeps: git+https://github.com/glue-viz/glue-jupyter.git devdeps: git+https://github.com/glue-viz/glue-astronomy.git From a9f13e9cad6de60e5a96611651bf8207e8dd76a7 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:37:46 -0700 Subject: [PATCH 57/82] TST: Ignore DeprecationWarning from asteval (#2274) * TST: Ignore DeprecationWarning from asteval. For example https://github.com/newville/asteval/issues/120 * Also ignore FutureWarning from asteval --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3d1023b832..08a722738e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,6 +137,8 @@ filterwarnings = [ "ignore::DeprecationWarning:bqscales", "ignore::DeprecationWarning:traittypes", "ignore::DeprecationWarning:voila", + "ignore::DeprecationWarning:asteval", + "ignore::FutureWarning:asteval", "ignore:::specutils.spectra.spectrum1d", ] From 40188835f30f6f2a7bb17a23f05eb553bdfe70eb Mon Sep 17 00:00:00 2001 From: Jesse Averbukh Date: Fri, 30 Jun 2023 17:12:13 -0400 Subject: [PATCH 58/82] Add doc for windows latency issue --- docs/known_bugs.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/known_bugs.rst b/docs/known_bugs.rst index 086f959a35..37112b2901 100644 --- a/docs/known_bugs.rst +++ b/docs/known_bugs.rst @@ -130,6 +130,14 @@ the side of the cell output to collapse or expand the scrollable window. This has the unintended consequence of changing the contrast of the image displayed in the Cubeviz cube viewer. +On Windows OS, latency increases exponentially with number of subsets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This seems to be an issue with Windows OS specifically, although we are still +investigating exactly what causes it. +See `Issue #2263 `_ for +updates on this topic. + .. _known_issues_imviz: Imviz From 2c03ef14320bb1c0205518e8ce849b3ff3c38885 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 29 Jun 2023 10:34:11 -0400 Subject: [PATCH 59/82] Deprecate load_spectrum --- jdaviz/configs/specviz/helper.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index 3a7c843bcd..11c4a875a7 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -1,7 +1,7 @@ import warnings from astropy import units as u -from astropy.utils.decorators import deprecated_renamed_argument +from astropy.utils.decorators import deprecated_renamed_argument, deprecated from regions.core.core import Region from glue.core.subset_group import GroupedSubset from specutils import SpectralRegion, Spectrum1D @@ -41,11 +41,35 @@ def __init__(self, *args, **kwargs): self.app.hub.subscribe(self, RedshiftMessage, handler=self._redshift_listener) + @deprecated(since="3.6", alternative="specviz.load_data") def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, concat_by_file=False): """ Loads a data file or `~specutils.Spectrum1D` object into Specviz. + Parameters + ---------- + data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` + Spectrum1D, SpectrumList, or path to compatible data file. + data_label : str + The Glue data label found in the ``DataCollection``. + format : str + Loader format specification used to indicate data format in + `~specutils.Spectrum1D.read` io method. + show_in_viewer : bool + Show data in viewer(s). + concat_by_file : bool + If True and there is more than one available extension, concatenate + the extensions within each spectrum file passed to the parser and + add a concatenated spectrum to the data collection. + """ + self.load_data(data, data_label, format, show_in_viewer, concat_by_file) + + def load_data(self, data, data_label=None, format=None, show_in_viewer=True, + concat_by_file=False): + """ + Loads a data file or `~specutils.Spectrum1D` object into Specviz. + Parameters ---------- data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` From eb623653a112ae15b3a47e48689682dd887a60a6 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 29 Jun 2023 12:23:55 -0400 Subject: [PATCH 60/82] Update tests and docs from load_spectrum --- docs/cubeviz/import_data.rst | 4 +-- docs/specviz/export_data.rst | 2 +- docs/specviz/import_data.rst | 20 +++++------ .../line_lists/tests/test_line_lists.py | 8 ++--- .../model_fitting/tests/test_plugin.py | 18 +++++----- .../subset_plugin/tests/test_subset_plugin.py | 2 +- jdaviz/configs/specviz/helper.py | 2 +- .../line_analysis/tests/test_line_analysis.py | 26 +++++++------- .../line_analysis/tests/test_lineflux.py | 4 +-- .../tests/test_unit_conversion.py | 8 ++--- jdaviz/configs/specviz/tests/test_helper.py | 34 +++++++++---------- jdaviz/core/data_formats.py | 7 ++-- jdaviz/core/tests/test_data_menu.py | 4 +-- jdaviz/core/tests/test_helpers.py | 4 +-- jdaviz/core/tests/test_template_mixin.py | 2 +- jdaviz/tests/test_app.py | 30 ++++++++-------- jdaviz/tests/test_subsets.py | 10 +++--- 17 files changed, 91 insertions(+), 94 deletions(-) diff --git a/docs/cubeviz/import_data.rst b/docs/cubeviz/import_data.rst index 993ad8969f..b421dd3199 100644 --- a/docs/cubeviz/import_data.rst +++ b/docs/cubeviz/import_data.rst @@ -74,7 +74,7 @@ Importing data via the API Alternatively, users who work in a coding environment like a Jupyter notebook can access the Cubeviz helper class API. Using this API, users can -load data into the application through code with the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_spectrum` +load data into the application through code with the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` method, which takes as input a :class:`~specutils.Spectrum1D` object. FITS Files @@ -162,7 +162,7 @@ object, you can load it into Cubeviz as follows: # Create your spectrum1 spec3d = Spectrum1D(data, wcs=my_wcs) cubeviz = Cubeviz() - cubeviz.load_spectrum(spec3d, data_label='My Cube') + cubeviz.load_data(spec3d, data_label='My Cube') cubeviz.show() There is no plan to natively load such objects until ``datamodels`` diff --git a/docs/specviz/export_data.rst b/docs/specviz/export_data.rst index e6d322acb6..5f7111bad0 100644 --- a/docs/specviz/export_data.rst +++ b/docs/specviz/export_data.rst @@ -45,7 +45,7 @@ spectrum containing only your subset by running: spec = specviz.get_data(spectral_subset='Subset 1') subset_spec = Spectrum1D(flux=spec.flux[~spec.mask], spectral_axis=spec.spectral_axis[~spec.mask]) - specviz.load_spectrum(subset_spec) + specviz.load_data(subset_spec) .. seealso:: diff --git a/docs/specviz/import_data.rst b/docs/specviz/import_data.rst index e30aa19f02..e1e10303b2 100644 --- a/docs/specviz/import_data.rst +++ b/docs/specviz/import_data.rst @@ -51,7 +51,7 @@ Importing data via the API Alternatively, users who work in a coding environment like a Jupyter notebook can access the Specviz helper class API. Using this API, users can load data into the application through code with the -:py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_spectrum` +:py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` method, which takes as input a :class:`~specutils.Spectrum1D` object. FITS Files @@ -64,15 +64,15 @@ The example below loads a FITS file into Specviz: from specutils import Spectrum1D spec1d = Spectrum1D.read("/path/to/data/file") specviz = Specviz() - specviz.load_spectrum(spec1d, data_label="my_spec") + specviz.load_data(spec1d, data_label="my_spec") specviz.show() You can also pass the path to a file that `~specutils.Spectrum1D` understands directly to the -:py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_spectrum` method: +:py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` method: .. code-block:: python - specviz.load_spectrum("path/to/data/file") + specviz.load_data("path/to/data/file") Creating Your Own Array ----------------------- @@ -90,7 +90,7 @@ You can create your own array to load into Specviz: wavelength = np.arange(5100, 5300) * u.AA spec1d = Spectrum1D(spectral_axis=wavelength, flux=flux) specviz = Specviz() - specviz.load_spectrum(spec1d, data_label="my_spec") + specviz.load_data(spec1d, data_label="my_spec") specviz.show() JWST datamodels @@ -111,7 +111,7 @@ object, you can load it into Specviz as follows: spec1d = Spectrum1D(flux=flux, spectral_axis=wave) specviz = Specviz() - specviz.load_spectrum(spec1d, data_label="MultiSpecModel") + specviz.load_data(spec1d, data_label="MultiSpecModel") specviz.show() There is no plan to natively load such objects until ``datamodels`` @@ -122,7 +122,7 @@ is separated from the ``jwst`` pipeline package. Importing a SpectrumList ------------------------ -The :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_spectrum` also accepts +The :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` also accepts a `~specutils.SpectrumList` object, in which case it will both load the individual `~specutils.Spectrum1D` objects in the list and additionally attempt to stitch together the spectra into a single data object so that @@ -132,7 +132,7 @@ they can be manipulated and analyzed in the application as a single entity: from specutils import SpectrumList spec_list = SpectrumList([spec1d_1, spec1d_2]) - specviz.load_spectrum(spec_list) + specviz.load_data(spec_list) specviz.show() In the screenshot below, the combined spectrum is plotted in gray, and one of @@ -145,13 +145,13 @@ end of the red region in the screenshot below: .. image:: img/spectrumlist_combined.png This functionality is also available in limited instances by providing a directory path -to the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_spectrum` method. Note +to the :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` method. Note that the ``read`` method of :class:`~specutils.SpectrumList` is only set up to handle directory input in limited cases, for example JWST MIRI MRS data, and will throw an error in other cases. In cases that it does work, only files in the directory level specified will be read, with no recursion into deeper folders. -The :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_spectrum` method also takes +The :py:meth:`~jdaviz.configs.specviz.helper.Specviz.load_data` method also takes an optional keyword argument ``concat_by_file``. When set to ``True``, the spectra loaded in the :class:`~specutils.SpectrumList` will be concatenated together into one combined spectrum per loaded file, which may be useful for MIRI observations, for example. diff --git a/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py b/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py index 2af3cae334..e85959f49f 100644 --- a/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py +++ b/jdaviz/configs/default/plugins/line_lists/tests/test_line_lists.py @@ -12,7 +12,7 @@ def test_line_lists(specviz_helper): spec = Spectrum1D(flux=np.random.rand(100)*u.Jy, spectral_axis=np.arange(6000, 7000, 10)*u.AA) - specviz_helper.load_spectrum(spec) + specviz_helper.load_data(spec) lt = QTable() lt['linename'] = ['O III', 'Halpha'] @@ -51,7 +51,7 @@ def test_redshift(specviz_helper, spectrum1d): assert plg._obj.disabled_msg label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) assert not plg._obj.disabled_msg @@ -102,7 +102,7 @@ def test_redshift(specviz_helper, spectrum1d): def test_load_available_preset_lists(specviz_helper, spectrum1d): """ Loads all available line lists and checks the medium requirement """ label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) # Check to make sure we got our line lists available_linelists = get_available_linelists() @@ -125,7 +125,7 @@ def test_load_available_preset_lists(specviz_helper, spectrum1d): def test_line_identify(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) lt = QTable() lt['linename'] = ['O III', 'Halpha'] diff --git a/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py b/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py index 42232520bc..4c9d89e072 100644 --- a/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py +++ b/jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py @@ -20,7 +20,7 @@ def test_default_model_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] # By default, the spectral region should be the entire spectrum assert modelfit_plugin._obj.spectral_subset_selected == "Entire Spectrum" @@ -46,7 +46,7 @@ def test_default_model_labels(specviz_helper, spectrum1d): def test_custom_model_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] for i, model in enumerate(MODELS): @@ -68,7 +68,7 @@ def test_register_model_with_uncertainty_weighting(specviz_helper, spectrum1d): spectrum1d.uncertainty = StdDevUncertainty(spectrum1d.flux * 0.1) with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] # Test registering a simple linear fit @@ -105,7 +105,7 @@ def test_register_model_uncertainty_is_none(specviz_helper, spectrum1d): spectrum1d.uncertainty = None with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] # Test registering a simple linear fit @@ -157,7 +157,7 @@ def test_register_cube_model(cubeviz_helper, spectrum1d_cube): def test_user_api(specviz_helper, spectrum1d): with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) p = specviz_helper.plugins['Model Fitting'] # even though the default label is set to C, adding Linear1D should default to its automatic @@ -195,7 +195,7 @@ def test_user_api(specviz_helper, spectrum1d): def test_fit_gaussian_with_fixed_mean(specviz_helper, spectrum1d): with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) modelfit_plugin = specviz_helper.plugins['Model Fitting'] modelfit_plugin.create_model_component('Gaussian1D', 'G') @@ -217,7 +217,7 @@ def test_fit_gaussian_with_fixed_mean(specviz_helper, spectrum1d): def test_reestimate_parameters(specviz_helper, spectrum1d): with warnings.catch_warnings(): warnings.simplefilter('ignore') - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) mf = specviz_helper.plugins['Model Fitting'] mf.create_model_component('Gaussian1D', 'G') @@ -327,12 +327,12 @@ def test_subset_masks(cubeviz_helper, spectrum1d_cube_larger): def test_invalid_subset(specviz_helper, spectrum1d): # 6000-8000 - specviz_helper.load_spectrum(spectrum1d, data_label="right_spectrum") + specviz_helper.load_data(spectrum1d, data_label="right_spectrum") # 5000-7000 sp2 = Spectrum1D(spectral_axis=spectrum1d.spectral_axis - 1000*spectrum1d.spectral_axis.unit, flux=spectrum1d.flux * 1.25) - specviz_helper.load_spectrum(sp2, data_label="left_spectrum") + specviz_helper.load_data(sp2, data_label="left_spectrum") # apply subset that overlaps on left_spectrum, but not right_spectrum # NOTE: using a subset that overlaps the right_spectrum (reference) results in errors when diff --git a/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py index b867e62489..3d0b4e094a 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py +++ b/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py @@ -6,7 +6,7 @@ @pytest.mark.filterwarnings('ignore') def test_plugin(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) p = specviz_helper.plugins['Subset Tools'] # regression test for https://github.com/spacetelescope/jdaviz/issues/1693 diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index 11c4a875a7..54d5b0cda7 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -66,7 +66,7 @@ def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, self.load_data(data, data_label, format, show_in_viewer, concat_by_file) def load_data(self, data, data_label=None, format=None, show_in_viewer=True, - concat_by_file=False): + concat_by_file=False): """ Loads a data file or `~specutils.Spectrum1D` object into Specviz. diff --git a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py index 34812a2453..682d6040fc 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py @@ -14,7 +14,7 @@ def test_plugin(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -79,7 +79,7 @@ def test_spatial_subset(cubeviz_helper, image_cube_hdu_obj): def test_user_api(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) sv = specviz_helper.app.get_viewer('spectrum-viewer') sv.apply_roi(XRangeROI(6500, 7400)) @@ -109,7 +109,7 @@ def test_user_api(specviz_helper, spectrum1d): def test_line_identify(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) lt = QTable() lt['linename'] = ['O III', 'Halpha'] @@ -180,7 +180,7 @@ def test_coerce_unit(): def test_continuum_surrounding_spectral_subset(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -207,7 +207,7 @@ def test_continuum_surrounding_spectral_subset(specviz_helper, spectrum1d): def test_continuum_spectral_same_value(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -234,7 +234,7 @@ def test_continuum_spectral_same_value(specviz_helper, spectrum1d): def test_continuum_surrounding_invalid_width(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -259,7 +259,7 @@ def test_continuum_surrounding_invalid_width(specviz_helper, spectrum1d): def test_continuum_subset_spectral_entire(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -286,7 +286,7 @@ def test_continuum_subset_spectral_entire(specviz_helper, spectrum1d): def test_continuum_subset_spectral_subset2(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -319,7 +319,7 @@ def test_continuum_subset_spectral_subset2(specviz_helper, spectrum1d): def test_continuum_surrounding_no_right(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -347,7 +347,7 @@ def test_continuum_surrounding_no_right(specviz_helper, spectrum1d): def test_continuum_surrounding_no_left(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -375,7 +375,7 @@ def test_continuum_surrounding_no_left(specviz_helper, spectrum1d): def test_subset_changed(specviz_helper, spectrum1d): label = "Test 1D Spectrum" - specviz_helper.load_spectrum(spectrum1d, data_label=label) + specviz_helper.load_data(spectrum1d, data_label=label) plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') plugin.open_in_tray() @@ -406,12 +406,12 @@ def test_subset_changed(specviz_helper, spectrum1d): def test_invalid_subset(specviz_helper, spectrum1d): # 6000-8000 - specviz_helper.load_spectrum(spectrum1d, data_label="right_spectrum") + specviz_helper.load_data(spectrum1d, data_label="right_spectrum") # 5000-7000 sp2 = Spectrum1D(spectral_axis=spectrum1d.spectral_axis - 1000*spectrum1d.spectral_axis.unit, flux=spectrum1d.flux * 1.25) - specviz_helper.load_spectrum(sp2, data_label="left_spectrum") + specviz_helper.load_data(sp2, data_label="left_spectrum") # apply subset that overlaps on left_spectrum, but not right_spectrum # NOTE: using a subset that overlaps the right_spectrum (reference) results in errors when diff --git a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py index 7ec76cd0aa..3e1b07b9ce 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py @@ -99,7 +99,7 @@ def test_unit_gaussian(specviz_helper, test_case): Test an Area 1 Gaussian and ensure the result returns in W/m2 Test provided by Patrick Ogle ''' - specviz_helper.load_spectrum(test_case) + specviz_helper.load_data(test_case) lineflux_result = _calculate_line_flux(specviz_helper) assert_quantity_allclose(float(lineflux_result['result']) * u.Unit(lineflux_result['unit']), @@ -117,7 +117,7 @@ def test_unit_gaussian_mixed_units_per_steradian(specviz_helper): flx_wave = _gauss_with_unity_area(lam_a.value, mn, sig)*1E3*u.erg/u.s/u.cm**2/u.Angstrom/u.sr fl_wave = Spectrum1D(spectral_axis=lam_a, flux=flx_wave) - specviz_helper.load_spectrum(fl_wave) + specviz_helper.load_data(fl_wave) lineflux_result = _calculate_line_flux(specviz_helper) assert_quantity_allclose(float(lineflux_result['result']) * u.Unit(lineflux_result['unit']), 1*u.Unit('W/(m2sr)')) diff --git a/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py index 245c19c124..d69e93b35f 100644 --- a/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py +++ b/jdaviz/configs/specviz/plugins/unit_conversion/tests/test_unit_conversion.py @@ -10,7 +10,7 @@ ("micron", "fail", "micron", "Jy")]) def test_value_error_exception(specviz_helper, spectrum1d, new_spectral_axis, new_flux, expected_spectral_axis, expected_flux): - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] @@ -34,7 +34,7 @@ def test_value_error_exception(specviz_helper, spectrum1d, new_spectral_axis, ne def test_conv_wave_only(specviz_helper, spectrum1d, uncert): if uncert is False: spectrum1d.uncertainty = None - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] @@ -50,7 +50,7 @@ def test_conv_wave_only(specviz_helper, spectrum1d, uncert): def test_conv_flux_only(specviz_helper, spectrum1d, uncert): if uncert is False: spectrum1d.uncertainty = None - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] @@ -66,7 +66,7 @@ def test_conv_flux_only(specviz_helper, spectrum1d, uncert): def test_conv_wave_flux(specviz_helper, spectrum1d, uncert): if uncert is False: spectrum1d.uncertainty = None - specviz_helper.load_spectrum(spectrum1d, data_label="Test 1D Spectrum") + specviz_helper.load_data(spectrum1d, data_label="Test 1D Spectrum") viewer = specviz_helper.app.get_viewer("spectrum-viewer") plg = specviz_helper.plugins["Unit Conversion"] diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py index bade4d68aa..68ae8870c6 100644 --- a/jdaviz/configs/specviz/tests/test_helper.py +++ b/jdaviz/configs/specviz/tests/test_helper.py @@ -23,7 +23,7 @@ def setup_class(self, specviz_helper, spectrum1d, multi_order_spectrum_list): self.multi_order_spectrum_list = multi_order_spectrum_list self.label = "Test 1D Spectrum" - self.spec_app.load_spectrum(spectrum1d, data_label=self.label) + self.spec_app.load_data(spectrum1d, data_label=self.label) def test_load_spectrum1d(self): # starts with a single loaded spectrum1d object: @@ -38,7 +38,7 @@ def test_load_spectrum1d(self): def test_load_spectrum_list_no_labels(self): # now load three more spectra from a SpectrumList, without labels - self.spec_app.load_spectrum(self.spec_list) + self.spec_app.load_data(self.spec_list) assert len(self.spec_app.app.data_collection) == 4 for i in (1, 2, 3): assert "specviz_data" in self.spec_app.app.data_collection[i].label @@ -46,24 +46,24 @@ def test_load_spectrum_list_no_labels(self): def test_load_spectrum_list_with_labels(self): # now load three more spectra from a SpectrumList, with labels: labels = ["List test 1", "List test 2", "List test 3"] - self.spec_app.load_spectrum(self.spec_list, data_label=labels) + self.spec_app.load_data(self.spec_list, data_label=labels) assert len(self.spec_app.app.data_collection) == 4 def test_load_multi_order_spectrum_list(self): assert len(self.spec_app.app.data_collection) == 1 # now load ten spectral orders from a SpectrumList: - self.spec_app.load_spectrum(self.multi_order_spectrum_list) + self.spec_app.load_data(self.multi_order_spectrum_list) assert len(self.spec_app.app.data_collection) == 11 def test_mismatched_label_length(self): with pytest.raises(ValueError, match='Length'): labels = ["List test 1", "List test 2"] - self.spec_app.load_spectrum(self.spec_list, data_label=labels) + self.spec_app.load_data(self.spec_list, data_label=labels) def test_load_spectrum_collection(self): with pytest.raises(TypeError): collection = SpectrumCollection([1]*u.AA) - self.spec_app.load_spectrum(collection) + self.spec_app.load_data(collection) def test_get_spectra(self): with pytest.warns(UserWarning, match='Applying the value from the redshift slider'): @@ -245,15 +245,15 @@ def test_get_spectra_no_spectra_label_redshift_error(specviz_helper, spectrum1d) def test_add_spectrum_after_subset(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") specviz_helper.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(6200, 7000)) new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9 - specviz_helper.load_spectrum(new_spec, data_label="test2") + specviz_helper.load_data(new_spec, data_label="test2") def test_get_spectral_regions_unit(specviz_helper, spectrum1d): # Ensure units we put in are the same as the units we get out - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) specviz_helper.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(6200, 7000)) subsets = specviz_helper.get_spectral_regions() @@ -275,7 +275,7 @@ def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d): # If the reference (visible) data changes via unit conversion, # check that the region's units convert too - specviz_helper.load_spectrum(spectrum1d) # Originally Angstrom + specviz_helper.load_data(spectrum1d) # Originally Angstrom # Also check coordinates info panel. # x=0 -> 6000 A, x=1 -> 6222.222 A @@ -322,7 +322,7 @@ def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d): def test_subset_default_thickness(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) sv = specviz_helper.app.get_viewer('spectrum-viewer') sv.toolbar.active_tool = sv.toolbar.tools['bqplot:xrange'] @@ -350,7 +350,7 @@ def test_load_spectrum_list_directory(tmpdir, specviz_helper): # Load two NIRISS x1d files from FITS. They have 19 and 20 EXTRACT1D # extensions per file, for a total of 39 spectra to load: with pytest.warns(UserWarning, match='SRCTYPE is missing or UNKNOWN in JWST x1d loader'): - specviz_helper.load_spectrum(data_path) + specviz_helper.load_data(data_path) # NOTE: the length was 3 before specutils 1.9 (https://github.com/astropy/specutils/pull/982) expected_len = 39 @@ -377,12 +377,12 @@ def test_load_spectrum_list_directory_concat(tmpdir, specviz_helper): # spectra common to each file into one "Combined" spectrum to load per file. # Now the total is (19 EXTRACT 1D + 1 Combined) + (20 EXTRACT 1D + 1 Combined) = 41. with pytest.warns(UserWarning, match='SRCTYPE is missing or UNKNOWN in JWST x1d loader'): - specviz_helper.load_spectrum(data_path, concat_by_file=True) + specviz_helper.load_data(data_path, concat_by_file=True) assert len(specviz_helper.app.data_collection) == 41 def test_plot_uncertainties(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) specviz_viewer = specviz_helper.app.get_viewer("spectrum-viewer") @@ -416,7 +416,7 @@ def test_plugin_user_apis(specviz_helper): def test_data_label_as_posarg(specviz_helper, spectrum1d): # Passing in data_label keyword as posarg. - specviz_helper.load_spectrum(spectrum1d, 'my_spec') + specviz_helper.load_data(spectrum1d, 'my_spec') assert specviz_helper.app.data_collection[0].label == 'my_spec' @@ -431,8 +431,8 @@ def test_spectra_partial_overlap(specviz_helper): flux_2 = ([60] * wave_2.size) * u.nJy sp_2 = Spectrum1D(flux=flux_2, spectral_axis=wave_2) - specviz_helper.load_spectrum(sp_1, data_label='left') - specviz_helper.load_spectrum(sp_2, data_label='right') + specviz_helper.load_data(sp_1, data_label='left') + specviz_helper.load_data(sp_2, data_label='right') # Test mouseover outside of left but in range for right. # Should show right spectrum even when mouse is near left flux. diff --git a/jdaviz/core/data_formats.py b/jdaviz/core/data_formats.py index c26104c663..6ede5130a9 100644 --- a/jdaviz/core/data_formats.py +++ b/jdaviz/core/data_formats.py @@ -293,7 +293,7 @@ def open(filename, show=True, **kwargs): show : bool Determines whether to immediately show the application - All other arguments are interpreted as load_data/load_spectrum arguments for + All other arguments are interpreted as load_data arguments for the autoidentified configuration class Returns @@ -312,10 +312,7 @@ def open(filename, show=True, **kwargs): # Load data data = hdul if (hdul is not None) else filename - if helper_str == "specviz": - viz_helper.load_spectrum(data, **kwargs) - else: - viz_helper.load_data(data, **kwargs) + viz_helper.load_data(data, **kwargs) # Display app if show: diff --git a/jdaviz/core/tests/test_data_menu.py b/jdaviz/core/tests/test_data_menu.py index f092c2994c..40cf1c0c41 100644 --- a/jdaviz/core/tests/test_data_menu.py +++ b/jdaviz/core/tests/test_data_menu.py @@ -29,11 +29,11 @@ def test_data_menu_toggles(specviz_helper, spectrum1d): # load 2 data entries - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") app = specviz_helper.app sv = app.get_viewer('spectrum-viewer') new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9 - specviz_helper.load_spectrum(new_spec, data_label="test2") + specviz_helper.load_data(new_spec, data_label="test2") # check that both are enabled in the data menu selected_data_items = app._viewer_item_by_id('specviz-0')['selected_data_items'] diff --git a/jdaviz/core/tests/test_helpers.py b/jdaviz/core/tests/test_helpers.py index 6b32860f30..d651a2260c 100644 --- a/jdaviz/core/tests/test_helpers.py +++ b/jdaviz/core/tests/test_helpers.py @@ -37,8 +37,8 @@ def setup_class(self, specviz_helper, spectrum1d, multi_order_spectrum_list): self.spec2 = spectrum1d._copy(spectral_axis=spectrum1d.spectral_axis+1000*u.AA) self.label2 = "Test 1D Spectrum 2" - self.spec_app.load_spectrum(spectrum1d, data_label=self.label) - self.spec_app.load_spectrum(self.spec2, data_label=self.label2) + self.spec_app.load_data(spectrum1d, data_label=self.label) + self.spec_app.load_data(self.spec2, data_label=self.label2) # Add 3 subsets to cover different parts of spec and spec2 self.spec_app.app.get_viewer("spectrum-viewer").apply_roi(XRangeROI(6000, 6500)) diff --git a/jdaviz/core/tests/test_template_mixin.py b/jdaviz/core/tests/test_template_mixin.py index 3523896965..3e251f2d82 100644 --- a/jdaviz/core/tests/test_template_mixin.py +++ b/jdaviz/core/tests/test_template_mixin.py @@ -11,7 +11,7 @@ def test_spectralsubsetselect(specviz_helper, spectrum1d): mask = spectrum1d.flux < spectrum1d.flux.mean() spectrum1d.mask = mask - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) sv = specviz_helper.app.get_viewer('spectrum-viewer') # create a "Subset 1" entry sv.apply_roi(XRangeROI(6500, 7400)) diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py index c72d4fee27..612c108fe4 100644 --- a/jdaviz/tests/test_app.py +++ b/jdaviz/tests/test_app.py @@ -53,26 +53,26 @@ class Customviz(Specviz): viz = Customviz() assert viz.app.get_viewer_reference_names() == ['h', 'k'] - viz.load_spectrum(spectrum1d, data_label='example label') + viz.load_data(spectrum1d, data_label='example label') with pytest.raises(ValueError): viz.get_data("non-existent label") def test_duplicate_data_labels(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test") - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test") dc = specviz_helper.app.data_collection assert dc[0].label == "test" assert dc[1].label == "test (1)" - specviz_helper.load_spectrum(spectrum1d, data_label="test_1") - specviz_helper.load_spectrum(spectrum1d, data_label="test") + specviz_helper.load_data(spectrum1d, data_label="test_1") + specviz_helper.load_data(spectrum1d, data_label="test") assert dc[2].label == "test_1" assert dc[3].label == "test (2)" def test_duplicate_data_labels_with_brackets(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") - specviz_helper.load_spectrum(spectrum1d, data_label="test[test]") + specviz_helper.load_data(spectrum1d, data_label="test[test]") + specviz_helper.load_data(spectrum1d, data_label="test[test]") dc = specviz_helper.app.data_collection assert len(dc) == 2 assert dc[0].label == "test[test]" @@ -106,7 +106,7 @@ def test_unique_name_variations(specviz_helper, spectrum1d): data_label = specviz_helper.app.return_unique_name(None) assert data_label == "Unknown" - specviz_helper.load_spectrum(spectrum1d, data_label="test[flux]") + specviz_helper.load_data(spectrum1d, data_label="test[flux]") data_label = specviz_helper.app.return_data_label("test[flux]", ext="flux") assert data_label == "test[flux][flux]" @@ -115,8 +115,8 @@ def test_unique_name_variations(specviz_helper, spectrum1d): def test_substring_in_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="M31") - specviz_helper.load_spectrum(spectrum1d, data_label="M32") + specviz_helper.load_data(spectrum1d, data_label="M31") + specviz_helper.load_data(spectrum1d, data_label="M32") data_label = specviz_helper.app.return_data_label("M") assert data_label == "M" @@ -127,17 +127,17 @@ def test_substring_in_label(specviz_helper, spectrum1d): def test_edge_cases(specviz_helper, spectrum1d, data_label): dc = specviz_helper.app.data_collection - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + specviz_helper.load_data(spectrum1d, data_label=data_label) + specviz_helper.load_data(spectrum1d, data_label=data_label) assert dc[1].label == f"{data_label} (1)" - specviz_helper.load_spectrum(spectrum1d, data_label=data_label) + specviz_helper.load_data(spectrum1d, data_label=data_label) assert dc[2].label == f"{data_label} (2)" def test_case_that_used_to_break_return_label(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break (1)") - specviz_helper.load_spectrum(spectrum1d, data_label="this used to break") + specviz_helper.load_data(spectrum1d, data_label="this used to break (1)") + specviz_helper.load_data(spectrum1d, data_label="this used to break") dc = specviz_helper.app.data_collection assert dc[0].label == "this used to break (1)" assert dc[1].label == "this used to break (2)" diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index 3d06573f56..2c9acbcd80 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -459,7 +459,7 @@ def test_with_invalid_subset_name(cubeviz_helper): def test_composite_region_from_subset_2d(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6000, 7000)) reg = specviz_helper.app.get_subsets("Subset 1", simplify_spectral=False) @@ -505,7 +505,7 @@ def test_composite_region_from_subset_2d(specviz_helper, spectrum1d): def test_edit_composite_spectral_subset(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6200, 6800)) @@ -554,7 +554,7 @@ def test_edit_composite_spectral_subset(specviz_helper, spectrum1d): def test_edit_composite_spectral_with_xor(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6400, 6600)) @@ -574,7 +574,7 @@ def test_edit_composite_spectral_with_xor(specviz_helper, spectrum1d): def test_overlapping_spectral_regions(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6400, 7400)) @@ -593,7 +593,7 @@ def test_overlapping_spectral_regions(specviz_helper, spectrum1d): def test_only_overlapping_spectral_regions(specviz_helper, spectrum1d): - specviz_helper.load_spectrum(spectrum1d) + specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) viewer.apply_roi(XRangeROI(6400, 6600)) From 91d6978b7f15f187f124777d0f7a34110e221fa1 Mon Sep 17 00:00:00 2001 From: Duy Tuong Nguyen Date: Mon, 3 Jul 2023 10:12:05 -0400 Subject: [PATCH 61/82] Docstring suggestions Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com> --- jdaviz/configs/specviz/helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdaviz/configs/specviz/helper.py b/jdaviz/configs/specviz/helper.py index 54d5b0cda7..e2066a9435 100644 --- a/jdaviz/configs/specviz/helper.py +++ b/jdaviz/configs/specviz/helper.py @@ -41,7 +41,7 @@ def __init__(self, *args, **kwargs): self.app.hub.subscribe(self, RedshiftMessage, handler=self._redshift_listener) - @deprecated(since="3.6", alternative="specviz.load_data") + @deprecated(since="3.6", alternative="load_data") def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, concat_by_file=False): """ @@ -68,7 +68,7 @@ def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True, def load_data(self, data, data_label=None, format=None, show_in_viewer=True, concat_by_file=False): """ - Loads a data file or `~specutils.Spectrum1D` object into Specviz. + Load data into Specviz. Parameters ---------- From 40fd9015bd349467f7ce387c2507cef554be7354 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 3 Jul 2023 10:20:20 -0400 Subject: [PATCH 62/82] Changelog --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3b6639f105..ef8590659e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,8 @@ New Features - Plots within plugins can now be popped-out into their own windows. [#2254] +- Replace specviz.load_spectrum with specviz.load_data. [#2273] + Cubeviz ^^^^^^^ From a569123d924f32d1d8b0868cc27c2e0fa57768ec Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 3 Jul 2023 10:25:37 -0400 Subject: [PATCH 63/82] Changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ef8590659e..2ac6ecbfc2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,7 +11,7 @@ New Features - Plots within plugins can now be popped-out into their own windows. [#2254] -- Replace specviz.load_spectrum with specviz.load_data. [#2273] +- The ``specviz.load_spectrum`` method is deprecated; use ``specviz.load_data`` instead. [#2273] Cubeviz ^^^^^^^ From 24ff31420bd3181e54b97318730173b0b019766a Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Mon, 3 Jul 2023 07:43:28 -0700 Subject: [PATCH 64/82] Button to export Cubeviz movie (#2264) * WIP: Backend API to write movie file but got stuck with bqplot not cooperating after first frame. [ci skip] [rtd skip] * fix: bqplot stuck after the first frame of the movie We have to run the loop outside the main thread; otherwise, the processing of messages from the frontend is blocked, causing the message with the first image to never be received. The "save_image" method can only save the next image after the previous image is received. * Fix typo, this works now from the API. [ci skip] [rtd skip] * Add frontend and tests * Add user doc * DOC: Add note about standalone app saving file into weird places. * Address some review comments * Expose FPS and fix test * DOC: Baby Shark roundtrip as promised. * DOC: Ellie said more shark! * Vue.js style improvements [ci skip] [rtd skip] Co-authored-by: Kyle Conroy * Display pls install msg in plugin * Address review comments * Fix test * Fix path resolution in standalone app * Improve frontend validation and access. Co-authored-by: Kyle Conroy * Disable video for spectrum-viewer in GUI and add comment to Slice. * Add tooltip to kill switch but it only shows when activated * Rename kill with something less scary * Hide stop button since disabling is not obvious enough, also moar tooltip * Disable the whole movie menu for spectrum viewer in Cubeviz [ci skip] [rtd skip] Co-authored-by: Ricky O'Steen <39831871+rosteen@users.noreply.github.com> * Fix RTD warnings about invalid prop and remove unnecessary frontend check now that Ricky's suggestion is accepted. --------- Co-authored-by: Mario Buikhuizen Co-authored-by: Kyle Conroy Co-authored-by: Ricky O'Steen <39831871+rosteen@users.noreply.github.com> --- CHANGES.rst | 6 + docs/cubeviz/plugins.rst | 25 ++ .../plugins/moment_maps/moment_maps.py | 23 +- .../moment_maps/tests/test_moment_maps.py | 4 + jdaviz/configs/cubeviz/plugins/slice/slice.py | 2 +- .../plugins/tests/test_export_plots.py | 93 ++++++++ .../plugins/export_plot/export_plot.py | 225 ++++++++++++++++++ .../plugins/export_plot/export_plot.vue | 100 ++++++++ jdaviz/conftest.py | 2 +- notebooks/concepts/cubeviz_ndarray_gif.ipynb | 173 +++++++++++++- pyproject.toml | 3 + tox.ini | 3 +- 12 files changed, 641 insertions(+), 18 deletions(-) create mode 100644 jdaviz/configs/cubeviz/plugins/tests/test_export_plots.py diff --git a/CHANGES.rst b/CHANGES.rst index 3b6639f105..2e25db76e4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,9 @@ New Features Cubeviz ^^^^^^^ +- Added the ability to export cube slices to video. User will need to install + ``opencv-python`` separately or use ``[all]`` specifier when installing Jdaviz. [#2264] + Imviz ^^^^^ @@ -77,6 +80,9 @@ Bug Fixes Cubeviz ^^^^^^^ +- Moment Map plugin now writes FITS file to working directory if no path provided + in standalone mode. [#2264] + Imviz ^^^^^ diff --git a/docs/cubeviz/plugins.rst b/docs/cubeviz/plugins.rst index abea089de8..174a2079b4 100644 --- a/docs/cubeviz/plugins.rst +++ b/docs/cubeviz/plugins.rst @@ -274,3 +274,28 @@ Export Plot =========== This plugin allows exporting the plot in a given viewer to various image formats. + +.. _cubeviz-export-video: + +Movie +----- + +.. note:: + + For MPEG-4, this feature needs ``opencv-python`` to be installed; + see [opencv-python on PyPI](https://pypi.org/project/opencv-python/). + +Expand the "Export to video" section, then enter the desired starting and +ending slice indices (inclusive), the frame rate in frames per second (FPS), +and the filename. +If a path is not given, the file will be saved to current working +directory. Any existing file with the same name will be silently replaced. + +When you are ready, click the :guilabel:`Export to MP4` button. +The movie will be recorded at the given FPS. While recording is in progress, +it is highly recommended that you leave the app alone until it is done. + +While recording, there is an option to interrupt the recording when something +goes wrong (e.g., it is taking too long or you realized you entered the wrong inputs). +Click on the stop icon next to the :guilabel:`Export to MP4` button to interrupt it. +Doing so will result in no output video. diff --git a/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py b/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py index 48c9e88675..4cfb013839 100644 --- a/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py +++ b/jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py @@ -137,20 +137,29 @@ def _write_moment_to_fits(self, overwrite=False, *args): if self.moment is None or not self.filename: # pragma: no cover return - path = Path(self.filename).resolve() - if path.exists(): + # Make sure file does not end up in weird places in standalone mode. + path = os.path.dirname(self.filename) + if path and not os.path.exists(path): + raise ValueError(f"Invalid path={path}") + elif (not path or path.startswith("..")) and os.environ.get("JDAVIZ_START_DIR", ""): # noqa: E501 # pragma: no cover + filename = Path(os.environ["JDAVIZ_START_DIR"]) / self.filename + else: + filename = Path(self.filename).resolve() + + if filename.exists(): if overwrite: # Try to delete the file - path.unlink() - if path.exists(): + filename.unlink() + if filename.exists(): # Warn the user if the file still exists - raise FileExistsError(f"Unable to delete {path}. Check user permissions.") + raise FileExistsError(f"Unable to delete {filename}. Check user permissions.") else: self.overwrite_warn = True return - self.moment.write(str(path)) + filename = str(filename) + self.moment.write(filename) # Let the user know where we saved the file. self.hub.broadcast(SnackbarMessage( - f"Moment map saved to {os.path.abspath(self.filename)}", sender=self, color="success")) + f"Moment map saved to {os.path.abspath(filename)}", sender=self, color="success")) diff --git a/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py b/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py index 4408d6f065..8ad373d3e4 100644 --- a/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py +++ b/jdaviz/configs/cubeviz/plugins/moment_maps/tests/test_moment_maps.py @@ -131,3 +131,7 @@ def test_write_momentmap(cubeviz_helper, spectrum1d_cube, tmp_path): sky = w.pixel_to_world(0, 0) assert_allclose(sky.ra.deg, 204.9998877673) assert_allclose(sky.dec.deg, 27.0001) + + plugin._obj.filename = "fake_path/test_file.fits" + with pytest.raises(ValueError, match="Invalid path"): + plugin._obj.vue_save_as_fits() diff --git a/jdaviz/configs/cubeviz/plugins/slice/slice.py b/jdaviz/configs/cubeviz/plugins/slice/slice.py index 9eb559b028..17b912691c 100644 --- a/jdaviz/configs/cubeviz/plugins/slice/slice.py +++ b/jdaviz/configs/cubeviz/plugins/slice/slice.py @@ -122,7 +122,7 @@ def _watch_viewer(self, viewer, watch=True): def _on_data_added(self, msg): if isinstance(msg.viewer, BqplotImageView): if len(msg.data.shape) == 3: - self.max_value = msg.data.shape[-1] - 1 + self.max_value = msg.data.shape[-1] - 1 # Same as i_end in Export Plot plugin self._watch_viewer(msg.viewer, True) msg.viewer.state.slices = (0, 0, int(self.slice)) diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_export_plots.py b/jdaviz/configs/cubeviz/plugins/tests/test_export_plots.py new file mode 100644 index 0000000000..31426e1831 --- /dev/null +++ b/jdaviz/configs/cubeviz/plugins/tests/test_export_plots.py @@ -0,0 +1,93 @@ +import os + +import pytest + +from jdaviz.configs.default.plugins.export_plot.export_plot import HAS_OPENCV + + +# TODO: Remove skip when https://github.com/bqplot/bqplot/pull/1397/files#r726500097 is resolved. +@pytest.mark.skip(reason="Cannot test due to async JS callback") +# @pytest.mark.skipif(not HAS_OPENCV, reason="opencv-python is not installed") +def test_export_movie(cubeviz_helper, spectrum1d_cube, tmp_path): + orig_path = os.getcwd() + os.chdir(tmp_path) + try: + cubeviz_helper.load_data(spectrum1d_cube, data_label="test") + plugin = cubeviz_helper.plugins["Export Plot"] + assert plugin.i_start == 0 + assert plugin.i_end == 1 + assert plugin.movie_filename == "mymovie.mp4" + + plugin._obj.vue_save_movie("mp4") + assert os.path.isfile("mymovie.mp4"), tmp_path + finally: + os.chdir(orig_path) + + +@pytest.mark.skipif(HAS_OPENCV, reason="opencv-python is installed") +def test_no_opencv(cubeviz_helper, spectrum1d_cube): + cubeviz_helper.load_data(spectrum1d_cube, data_label="test") + plugin = cubeviz_helper.plugins["Export Plot"] + assert plugin._obj.movie_msg != "" + with pytest.raises(ImportError, match="Please install opencv-python"): + plugin.save_movie() + + +@pytest.mark.skipif(not HAS_OPENCV, reason="opencv-python is not installed") +def test_export_movie_not_cubeviz(imviz_helper): + plugin = imviz_helper.plugins["Export Plot"] + + with pytest.raises(NotImplementedError, match="save_movie is not available for config"): + plugin._obj.save_movie() + + # Also not available via plugin public API. + with pytest.raises(AttributeError): + plugin.save_movie() + + +@pytest.mark.skipif(not HAS_OPENCV, reason="opencv-python is not installed") +def test_export_movie_cubeviz_exceptions(cubeviz_helper, spectrum1d_cube): + cubeviz_helper.load_data(spectrum1d_cube, data_label="test") + cubeviz_helper.default_viewer.shape = (100, 100) + cubeviz_helper.app.get_viewer("uncert-viewer").shape = (100, 100) + plugin = cubeviz_helper.plugins["Export Plot"] + assert plugin._obj.movie_msg == "" + assert plugin.i_start == 0 + assert plugin.i_end == 1 + assert plugin.movie_filename == "mymovie.mp4" + + with pytest.raises(NotImplementedError, match="filetype"): + plugin.save_movie(filetype="gif") + + with pytest.raises(NotImplementedError, match="filetype"): + plugin.save_movie(filename="mymovie.gif", filetype=None) + + with pytest.raises(ValueError, match="No frames to write"): + plugin.save_movie(i_start=0, i_end=0) + + with pytest.raises(ValueError, match="Invalid frame rate"): + plugin.save_movie(fps=0) + + plugin.movie_filename = "fake_path/mymovie.mp4" + with pytest.raises(ValueError, match="Invalid path"): + plugin.save_movie() + + plugin.movie_filename = "mymovie.mp4" + plugin.viewer = 'spectrum-viewer' + with pytest.raises(TypeError, match=r"Movie for.*is not supported"): + plugin.save_movie() + + plugin.movie_filename = "" + plugin.viewer = 'uncert-viewer' + with pytest.raises(ValueError, match="Invalid filename"): + plugin.save_movie() + + +@pytest.mark.skipif(not HAS_OPENCV, reason="opencv-python is not installed") +def test_export_movie_cubeviz_empty(cubeviz_helper): + plugin = cubeviz_helper.plugins["Export Plot"] + assert plugin.i_start == 0 + assert plugin.i_end == 0 + + with pytest.raises(ValueError, match="Selected viewer has no display shape"): + plugin.save_movie(i_start=0, i_end=1) diff --git a/jdaviz/configs/default/plugins/export_plot/export_plot.py b/jdaviz/configs/default/plugins/export_plot/export_plot.py index 3b1522e907..4c1572ff43 100644 --- a/jdaviz/configs/default/plugins/export_plot/export_plot.py +++ b/jdaviz/configs/default/plugins/export_plot/export_plot.py @@ -1,9 +1,23 @@ import os +from glue_jupyter.bqplot.image import BqplotImageView +from traitlets import Any, Bool, Unicode + +from jdaviz.core.custom_traitlets import FloatHandleEmpty, IntHandleEmpty +from jdaviz.core.events import AddDataMessage, SnackbarMessage from jdaviz.core.registries import tray_registry from jdaviz.core.template_mixin import PluginTemplateMixin, ViewerSelectMixin from jdaviz.core.user_api import PluginUserApi +try: + import cv2 +except ImportError: + HAS_OPENCV = False +else: + import threading + import time + HAS_OPENCV = True + __all__ = ['ExportViewer'] @@ -20,16 +34,46 @@ class ExportViewer(PluginTemplateMixin, ViewerSelectMixin): * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show` * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` * :meth:`save_figure` + * :meth:`save_movie` (Cubeviz only) + * `i_start` (Cubeviz only) + * `i_end` (Cubeviz only) + * `movie_fps` (Cubeviz only) + * `movie_filename` (Cubeviz only) """ template_file = __file__, "export_plot.vue" + # For Cubeviz movie. + i_start = IntHandleEmpty(0).tag(sync=True) + i_end = IntHandleEmpty(0).tag(sync=True) + movie_fps = FloatHandleEmpty(5.0).tag(sync=True) + movie_filename = Any("mymovie.mp4").tag(sync=True) + movie_msg = Unicode("").tag(sync=True) + movie_recording = Bool(False).tag(sync=True) + movie_interrupt = Bool(False).tag(sync=True) + @property def user_api(self): + if self.config == "cubeviz": + return PluginUserApi(self, expose=('viewer', 'save_figure', 'save_movie', 'i_start', + 'i_end', 'movie_fps', 'movie_filename')) return PluginUserApi(self, expose=('viewer', 'save_figure')) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + if self.config == "cubeviz": + if HAS_OPENCV: + self.session.hub.subscribe(self, AddDataMessage, handler=self._on_cubeviz_data_added) # noqa: E501 + else: + # NOTE: HTML tags do not work here. + self.movie_msg = 'Please install opencv-python to use this feature.' + + def _on_cubeviz_data_added(self, msg): + # NOTE: This needs revising if we allow loading more than one cube. + if isinstance(msg.viewer, BqplotImageView): + if len(msg.data.shape) == 3: + self.i_end = msg.data.shape[-1] - 1 # Same as max_value in Slice plugin + def save_figure(self, filename=None, filetype=None): """ Save the figure to an image with a provided filename or through an interactive save dialog. @@ -74,3 +118,184 @@ def vue_save_figure(self, filetype): the bqplot.Figure save methods. """ self.save_figure(filetype=filetype) + + def _save_movie(self, i_start, i_end, fps, filename, rm_temp_files): + # NOTE: All the stuff here has to be in the same thread but + # separate from main app thread to work. + + viewer = self.viewer.selected_obj + slice_plg = self.app._jdaviz_helper.plugins["Slice"]._obj + orig_slice = slice_plg.slice + temp_png_files = [] + i = i_start + video = None + + # TODO: Expose to users? + i_step = 1 # Need n_frames check if we allow tweaking + + try: + self.movie_recording = True + + while i <= i_end: + if self.movie_interrupt: + break + + slice_plg._on_slider_updated({'new': i}) + cur_pngfile = f"._cubeviz_movie_frame_{i}.png" + self.save_figure(filename=cur_pngfile, filetype="png") + temp_png_files.append(cur_pngfile) + i += i_step + + # Wait for the roundtrip to the frontend to complete. + while viewer.figure._upload_png_callback is not None: + time.sleep(0.05) + + if not self.movie_interrupt: + # Grab frame size. + frame_shape = cv2.imread(temp_png_files[0]).shape + frame_size = (frame_shape[1], frame_shape[0]) + + video = cv2.VideoWriter(filename, cv2.VideoWriter_fourcc(*'mp4v'), fps, frame_size, True) # noqa: E501 + for cur_pngfile in temp_png_files: + video.write(cv2.imread(cur_pngfile)) + finally: + cv2.destroyAllWindows() + if video: + video.release() + slice_plg._on_slider_updated({'new': orig_slice}) + self.movie_recording = False + + if rm_temp_files or self.movie_interrupt: + for cur_pngfile in temp_png_files: + if os.path.exists(cur_pngfile): + os.remove(cur_pngfile) + + if self.movie_interrupt: + if os.path.exists(filename): + os.remove(filename) + self.movie_interrupt = False + + def save_movie(self, i_start=None, i_end=None, fps=None, filename=None, filetype=None, + rm_temp_files=True): + """Save selected slices as a movie. + + This method creates a PNG file per frame (``._cubeviz_movie_frame_.png``) + in the working directory before stitching all the frames into a movie. + Please make sure you have sufficient memory for this operation. + PNG files are deleted after the movie is created unless otherwise specified. + If another PNG file with the same name already exists, it will be silently replaced. + + Parameters + ---------- + i_start, i_end : int or `None` + Slices to record; each slice will be a frame in the movie. + If not given, it is obtained from plugin inputs. + Unlike Python indexing, ``i_end`` is inclusive. + Wrapping and reverse indexing are not supported. + + fps : float or `None` + Frame rate in frames per second (FPS). + If not given, it is obtained from plugin inputs. + + filename : str or `None` + Filename for the movie to be recorded. Include path if necessary. + If not given, it is obtained from plugin inputs. + If another file with the same name already exists, it will be silently replaced. + + filetype : {'mp4', `None`} + Currently only MPEG-4 is supported. This keyword is reserved for future support + of other format(s). + + rm_temp_files : bool + Remove temporary PNG files after movie creation. Default is `True`. + + Returns + ------- + out_filename : str + The absolute path to the actual output file. + + """ + if self.config != "cubeviz": + raise NotImplementedError(f"save_movie is not available for config={self.config}") + + if not HAS_OPENCV: + raise ImportError("Please install opencv-python to save cube as movie.") + + if filetype is None: + if filename is not None and '.' in filename: + filetype = filename.split('.')[-1] + else: + # default to MPEG-4 + filetype = "mp4" + + if filetype != "mp4": + raise NotImplementedError(f"filetype={filetype} not supported") + + viewer = self.viewer.selected_obj + if not isinstance(viewer, BqplotImageView): # Profile viewer in glue-jupyter cannot do this + raise TypeError(f"Movie for {viewer.__class__.__name__} is not supported.") + if viewer.shape is None: + raise ValueError("Selected viewer has no display shape.") + + if fps is None: + fps = float(self.movie_fps) + if fps <= 0: + raise ValueError("Invalid frame rate, must be positive non-zero value.") + + if filename is None: + if self.movie_filename: + filename = self.movie_filename + else: + raise ValueError("Invalid filename.") + + # Make sure file does not end up in weird places in standalone mode. + path = os.path.dirname(filename) + if path and not os.path.exists(path): + raise ValueError(f"Invalid path={path}") + elif (not path or path.startswith("..")) and os.environ.get("JDAVIZ_START_DIR", ""): # noqa: E501 # pragma: no cover + filename = os.path.join(os.environ["JDAVIZ_START_DIR"], filename) + + if i_start is None: + i_start = int(self.i_start) + + if i_end is None: + i_end = int(self.i_end) + + # No wrapping. Forward only. + slice_plg = self.app._jdaviz_helper.plugins["Slice"]._obj + if i_start < 0: # pragma: no cover + i_start = 0 + if i_end > slice_plg.max_value: # pragma: no cover + i_end = slice_plg.max_value + if i_end <= i_start: + raise ValueError(f"No frames to write: i_start={i_start}, i_end={i_end}") + + threading.Thread( + target=lambda: self._save_movie(i_start, i_end, fps, filename, rm_temp_files) + ).start() + + return os.path.abspath(filename) + + def vue_save_movie(self, filetype): # pragma: no cover + """ + Callback for save movie events in the front end viewer toolbars. Uses + the bqplot.Figure save methods. + """ + try: + filename = self.save_movie(filetype=filetype) + except Exception as err: # pragma: no cover + self.hub.broadcast(SnackbarMessage( + f"Error saving {self.movie_filename}: {err!r}", sender=self, color="error")) + else: + # Let the user know where we saved the file. + # NOTE: Because of threading, this will be emitted even as movie as recording. + self.hub.broadcast(SnackbarMessage( + f"Movie being saved to {filename} for slices {self.i_start} to {self.i_end}, " + f"inclusive, at {self.movie_fps} FPS.", + sender=self, color="success")) + + def vue_interrupt_recording(self, *args): # pragma: no cover + self.movie_interrupt = True + self.hub.broadcast(SnackbarMessage( + f"Movie recording interrupted by user, {self.movie_filename} will be deleted.", + sender=self, color="warning")) diff --git a/jdaviz/configs/default/plugins/export_plot/export_plot.vue b/jdaviz/configs/default/plugins/export_plot/export_plot.vue index 065242935a..67408e4451 100644 --- a/jdaviz/configs/default/plugins/export_plot/export_plot.vue +++ b/jdaviz/configs/default/plugins/export_plot/export_plot.vue @@ -17,6 +17,7 @@ Export to PNG @@ -25,11 +26,110 @@ Export to SVG + + + + + + Export to Video + + + + + {{ movie_msg }} + + + + + + + + + + + + + + + + + + + + + + + + Start movie recording + +
+ + + Interrupt recording and delete movie file + +
+
+
+
+
+
diff --git a/jdaviz/conftest.py b/jdaviz/conftest.py index d56ecf77c0..3f5b9d7f2e 100644 --- a/jdaviz/conftest.py +++ b/jdaviz/conftest.py @@ -180,7 +180,7 @@ def multi_order_spectrum_list(spectrum1d, spectral_orders=10): def _create_spectrum1d_cube_with_fluxunit(fluxunit=u.Jy, shape=(2, 2, 4), with_uncerts=False): - + # nz=2 nx=2 ny=4 flux = np.arange(np.prod(shape)).reshape(shape) * fluxunit wcs_dict = {"CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN", "CTYPE3": "WAVE-LOG", "CRVAL1": 205, "CRVAL2": 27, "CRVAL3": 4.622e-7, diff --git a/notebooks/concepts/cubeviz_ndarray_gif.ipynb b/notebooks/concepts/cubeviz_ndarray_gif.ipynb index f8dfc86423..2ea0432750 100644 --- a/notebooks/concepts/cubeviz_ndarray_gif.ipynb +++ b/notebooks/concepts/cubeviz_ndarray_gif.ipynb @@ -17,7 +17,9 @@ "cell_type": "code", "execution_count": null, "id": "0adf0be6", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from jdaviz import Cubeviz" @@ -141,7 +143,9 @@ "cell_type": "code", "execution_count": null, "id": "fab04442", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "filename = 'baby_shark.gif'" @@ -151,7 +155,9 @@ "cell_type": "code", "execution_count": null, "id": "c89c8d5f", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "cubeviz2 = Cubeviz()" @@ -161,7 +167,9 @@ "cell_type": "code", "execution_count": null, "id": "137fdde0", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "cubeviz2.load_data(filename)" @@ -172,17 +180,168 @@ "execution_count": null, "id": "ab4eca75", "metadata": { - "scrolled": false + "tags": [] }, "outputs": [], "source": [ "cubeviz2.show()" ] }, + { + "cell_type": "markdown", + "id": "b288fe1c", + "metadata": {}, + "source": [ + "### But what about roundtripping?\n", + "\n", + "Well, sort of..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22902812", + "metadata": {}, + "outputs": [], + "source": [ + "export_plg = cubeviz2.plugins[\"Export Plot\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b78bbf5a", + "metadata": {}, + "outputs": [], + "source": [ + "# Or you can use the GUI.\n", + "export_plg.save_movie(0, 131, fps=10, filename=\"baby_shark_roundtrip.mp4\")" + ] + }, + { + "cell_type": "markdown", + "id": "4c00ffed", + "metadata": {}, + "source": [ + "See the rendered movie at https://www.youtube.com/watch?v=n8czt1ZQUNk" + ] + }, + { + "cell_type": "markdown", + "id": "baec2fe1", + "metadata": {}, + "source": [ + "### More shark!\n", + "\n", + "Ellie says “more shark”! See the demo at https://www.youtube.com/watch?v=ZTHJfSdmnBA" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c68bed4b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "cubeviz2.app.add_data_to_viewer(\"uncert-viewer\", \"baby_shark\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1cd9ecd1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_plg = cubeviz2.plugins[\"Plot Options\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e34310f2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_plg.viewer = \"flux-viewer\"\n", + "plot_plg.image_color_mode = \"Monochromatic\"\n", + "plot_plg.image_color = \"Red\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "252fe8be", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_plg.viewer = \"uncert-viewer\"\n", + "plot_plg.image_color_mode = \"Monochromatic\"\n", + "plot_plg.image_color = \"Blue\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82111003", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slice_plg = cubeviz2.plugins[\"Slice\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41de986e-c051-4021-a51e-bbdb6b478505", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slice_plg.slice = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7cf924c0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# More shark!\n", + "slice_plg._obj.vue_play_start_stop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b212b4fd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Run again to stop.\n", + "slice_plg._obj.vue_play_start_stop()" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "4d1af33f", + "id": "03f27eb1-531f-4509-ba7c-e5a3661a58eb", "metadata": {}, "outputs": [], "source": [] @@ -204,7 +363,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index 08a722738e..246618afa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,9 @@ mosviz = "jdaviz.configs.mosviz" imviz = "jdaviz.configs.imviz" [project.optional-dependencies] +all = [ + "opencv-python", +] test = [ "pytest", "pytest-astropy", diff --git a/tox.ini b/tox.ini index d212680cbd..2ee43453a7 100644 --- a/tox.ini +++ b/tox.ini @@ -65,8 +65,7 @@ deps = extras = test romandeps: roman - # Uncomment when we have all again in setup.cfg - #alldeps: all + alldeps: all commands = devdeps: pip install -U -i https://pypi.anaconda.org/astropy/simple astropy --pre From d3d251374039582b84119e8a7f757241907d574f Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 15 Jun 2023 13:30:57 -0400 Subject: [PATCH 65/82] Add functional but ugly launcher --- jdaviz/core/launcher.py | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 jdaviz/core/launcher.py diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py new file mode 100644 index 0000000000..8878408b42 --- /dev/null +++ b/jdaviz/core/launcher.py @@ -0,0 +1,53 @@ +import ipyvuetify as v +from ipywidgets import jslink +from traitlets import Dict + +from jdaviz import configs as jdaviz_configs +from jdaviz.core.data_formats import open as jdaviz_open + + +def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d']): + main = v.Sheet() + main.add_traits(_metadata=Dict(default_value={'mount_id': 'content'}).tag(sync=True)) + main.children = [] + + # Create Intro Row + intro_row = v.Row() + welcome_text = v.Html(tag='h1', attributes={'title': 'a title'}, + children=['Welcome to Jdaviz']) + #links = + intro_row.children = [welcome_text] + + # Filepath row + filepath_row = v.Row() + text_field = v.TextField(label="URI or File Path", v_model=None) + + def load_file(filepath): + print(filepath) + if filepath: + helper = jdaviz_open(filepath, show=False) + main.children = [helper.app] + + open_data_btn = v.Btn(class_="ma-2", outlined=True, color="primary", children=[v.Icon(children=["mdi-upload"])]) + open_data_btn.on_event('click', lambda btn, event, data: load_file(btn.value)) + jslink((text_field, 'v_model'), (open_data_btn, 'value')) + + filepath_row.children = [text_field, open_data_btn] + + # Config buttons + def create_config(config): + viz_class = getattr(jdaviz_configs, config.capitalize()) + main.children = [viz_class().app] + + btns = [] + for config in configs: + config_btn = v.Btn(class_="ma-2", outlined=True, color="primary", children=[config.capitalize()]) + config_btn.on_event('click', lambda btn, event, data: create_config(btn.children[0])) + btns.append(config_btn) + + # Create button row + btn_row = v.Row() + btn_row.children = btns + main.children = [intro_row, filepath_row, btn_row] + + return main \ No newline at end of file From cefb4863fb74c83ced370e837faac654f45698e9 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 15 Jun 2023 13:31:28 -0400 Subject: [PATCH 66/82] Use launcher notebook if neither config nor filepath is specified --- jdaviz/cli.py | 7 +++++- jdaviz/jdaviz_cli_launcher.ipynb | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 jdaviz/jdaviz_cli_launcher.ipynb diff --git a/jdaviz/cli.py b/jdaviz/cli.py index 4f569e7f00..fd400adc4d 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -60,7 +60,12 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', else: file_list = [] - with open(JDAVIZ_DIR / "jdaviz_cli.ipynb") as f: + if not filepaths and not layout: + notebook = "jdaviz_cli_launcher.ipynb" + else: + notebook = "jdaviz_cli.ipynb" + + with open(JDAVIZ_DIR / notebook) as f: notebook_template = f.read() start_dir = os.path.abspath('.') diff --git a/jdaviz/jdaviz_cli_launcher.ipynb b/jdaviz/jdaviz_cli_launcher.ipynb new file mode 100644 index 0000000000..f847be1796 --- /dev/null +++ b/jdaviz/jdaviz_cli_launcher.ipynb @@ -0,0 +1,41 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from jdaviz.core import show_launcher\n", + "\n", + "show_launcher()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.10 ('envmain': venv)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" + }, + "vscode": { + "interpreter": { + "hash": "f917e879dca01012f092e44ceeb72fc316d3b188a12a493299dc2bd49905dadb" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From e652be8ed5c4bd3c5848c72f69f51baef344c56e Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 15 Jun 2023 13:36:26 -0400 Subject: [PATCH 67/82] Codestyle --- jdaviz/core/launcher.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index 8878408b42..8b4a05c0ba 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -15,7 +15,6 @@ def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d'] intro_row = v.Row() welcome_text = v.Html(tag='h1', attributes={'title': 'a title'}, children=['Welcome to Jdaviz']) - #links = intro_row.children = [welcome_text] # Filepath row @@ -27,11 +26,12 @@ def load_file(filepath): if filepath: helper = jdaviz_open(filepath, show=False) main.children = [helper.app] - - open_data_btn = v.Btn(class_="ma-2", outlined=True, color="primary", children=[v.Icon(children=["mdi-upload"])]) + + open_data_btn = v.Btn(class_="ma-2", outlined=True, color="primary", + children=[v.Icon(children=["mdi-upload"])]) open_data_btn.on_event('click', lambda btn, event, data: load_file(btn.value)) jslink((text_field, 'v_model'), (open_data_btn, 'value')) - + filepath_row.children = [text_field, open_data_btn] # Config buttons @@ -41,7 +41,8 @@ def create_config(config): btns = [] for config in configs: - config_btn = v.Btn(class_="ma-2", outlined=True, color="primary", children=[config.capitalize()]) + config_btn = v.Btn(class_="ma-2", outlined=True, color="primary", + children=[config.capitalize()]) config_btn.on_event('click', lambda btn, event, data: create_config(btn.children[0])) btns.append(config_btn) @@ -50,4 +51,4 @@ def create_config(config): btn_row.children = btns main.children = [intro_row, filepath_row, btn_row] - return main \ No newline at end of file + return main From f3bb66db33543fc77a6cc6cfdb249550df1fa976 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Thu, 15 Jun 2023 13:51:06 -0400 Subject: [PATCH 68/82] Add margins on left and right to avoid cutoff in notebook --- jdaviz/core/launcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index 8b4a05c0ba..66fa74c2fa 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -7,7 +7,7 @@ def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d']): - main = v.Sheet() + main = v.Sheet(class_="mx-4") main.add_traits(_metadata=Dict(default_value={'mount_id': 'content'}).tag(sync=True)) main.children = [] From dff234be3c0b913cef057605acd14ef4f79e39ad Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 16 Jun 2023 09:29:49 -0400 Subject: [PATCH 69/82] Support launcher from cli --- jdaviz/cli.py | 7 ++++--- jdaviz/jdaviz_cli_launcher.ipynb | 2 +- pyproject.toml | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/jdaviz/cli.py b/jdaviz/cli.py index fd400adc4d..614587b065 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -60,7 +60,8 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', else: file_list = [] - if not filepaths and not layout: + print(f'{file_list}, {layout}') + if len(file_list) == 0 and layout is '': notebook = "jdaviz_cli_launcher.ipynb" else: notebook = "jdaviz_cli.ipynb" @@ -116,8 +117,8 @@ def _main(config=None): 'loaded from FILENAME.') filepaths_nargs = '*' if config is None: - parser.add_argument('layout', choices=['cubeviz', 'specviz', 'specviz2d', - 'mosviz', 'imviz'], + parser.add_argument('--layout', default='', choices=['cubeviz', 'specviz', 'specviz2d', + 'mosviz', 'imviz'], help='Configuration to use.') if (config == "mosviz") or ("mosviz" in sys.argv): filepaths_nargs = 1 diff --git a/jdaviz/jdaviz_cli_launcher.ipynb b/jdaviz/jdaviz_cli_launcher.ipynb index f847be1796..a13d733066 100644 --- a/jdaviz/jdaviz_cli_launcher.ipynb +++ b/jdaviz/jdaviz_cli_launcher.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "from jdaviz.core import show_launcher\n", + "from jdaviz.core.launcher import show_launcher\n", "\n", "show_launcher()" ] diff --git a/pyproject.toml b/pyproject.toml index 246618afa1..585361e9c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,6 +109,7 @@ jdaviz = [ "configs/*/*/*/*.vue", "configs/*/*.yaml", "jdaviz_cli.ipynb", + "jdaviz_cli_launcher.ipynb", ] "jdaviz.configs.imviz.tests" = [ "data/*", From ceb79b0a3a06ac3bbbca09b1e58bd6b2f0156bb0 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 16 Jun 2023 09:35:06 -0400 Subject: [PATCH 70/82] Codestyle --- jdaviz/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdaviz/cli.py b/jdaviz/cli.py index 614587b065..4dd79709f6 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -61,7 +61,7 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', file_list = [] print(f'{file_list}, {layout}') - if len(file_list) == 0 and layout is '': + if len(file_list) == 0 and layout == '': notebook = "jdaviz_cli_launcher.ipynb" else: notebook = "jdaviz_cli.ipynb" From b980750ba74c5cadfe65b055f10b9a548a828585 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 16 Jun 2023 09:52:56 -0400 Subject: [PATCH 71/82] Remove URI from path text until implemented --- jdaviz/cli.py | 1 - jdaviz/core/launcher.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/jdaviz/cli.py b/jdaviz/cli.py index 4dd79709f6..b088cf3522 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -60,7 +60,6 @@ def main(filepaths=None, layout='default', instrument=None, browser='default', else: file_list = [] - print(f'{file_list}, {layout}') if len(file_list) == 0 and layout == '': notebook = "jdaviz_cli_launcher.ipynb" else: diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index 66fa74c2fa..cb8a965fcc 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -19,10 +19,9 @@ def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d'] # Filepath row filepath_row = v.Row() - text_field = v.TextField(label="URI or File Path", v_model=None) + text_field = v.TextField(label="File Path", v_model=None) def load_file(filepath): - print(filepath) if filepath: helper = jdaviz_open(filepath, show=False) main.children = [helper.app] From bfa8a418545881402a7b834a5e0cdea695936ce9 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 16 Jun 2023 10:52:00 -0400 Subject: [PATCH 72/82] Fix standalone bug --- jdaviz/core/launcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index cb8a965fcc..7b6188687b 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -7,8 +7,7 @@ def show_launcher(configs=['imviz', 'specviz', 'mosviz', 'cubeviz', 'specviz2d']): - main = v.Sheet(class_="mx-4") - main.add_traits(_metadata=Dict(default_value={'mount_id': 'content'}).tag(sync=True)) + main = v.Sheet(class_="mx-4", _metadata={'mount_id': 'content'}) main.children = [] # Create Intro Row From 73c4b6fe3ab4fd7e7565deaef61eb2aeca765932 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 26 Jun 2023 15:57:22 -0400 Subject: [PATCH 73/82] Specify --layout= as new required cli syntax --- docs/cubeviz/import_data.rst | 2 +- docs/imviz/import_data.rst | 2 +- docs/mosviz/import_data.rst | 4 ++-- docs/mosviz/index.rst | 2 +- docs/specviz/import_data.rst | 2 +- docs/specviz2d/import_data.rst | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/cubeviz/import_data.rst b/docs/cubeviz/import_data.rst index b421dd3199..555ae61e84 100644 --- a/docs/cubeviz/import_data.rst +++ b/docs/cubeviz/import_data.rst @@ -50,7 +50,7 @@ a data product is optional: .. code-block:: bash - jdaviz cubeviz /my/directory/cube.fits + jdaviz --layout=cubeviz /my/directory/cube.fits .. _cubeviz-import-gui: diff --git a/docs/imviz/import_data.rst b/docs/imviz/import_data.rst index 6d23609109..0ad3995623 100644 --- a/docs/imviz/import_data.rst +++ b/docs/imviz/import_data.rst @@ -25,7 +25,7 @@ Multiple data files may be provided: .. code-block:: bash - jdaviz imviz /my/image/data1.fits /my/image/data2.fits + jdaviz --layout=imviz /my/image/data1.fits /my/image/data2.fits .. _imviz-import-gui: diff --git a/docs/mosviz/import_data.rst b/docs/mosviz/import_data.rst index ed0f513af2..fd0b7ab026 100644 --- a/docs/mosviz/import_data.rst +++ b/docs/mosviz/import_data.rst @@ -41,13 +41,13 @@ Similarly, an instrument keyword can be specified by the command line. For NIRSp .. code-block:: bash - jdaviz mosviz /path/to/my/data --instrument=nirspec + jdaviz --layout=mosviz /path/to/my/data --instrument=nirspec and for NIRISS: .. code-block:: bash - jdaviz mosviz /path/to/my/data --instrument=niriss + jdaviz --layout=mosviz /path/to/my/data --instrument=niriss Specifying a data directory and an instrument are required to start Mosviz from the command line. If a directory is entered without specifying an instrument, Mosviz will diff --git a/docs/mosviz/index.rst b/docs/mosviz/index.rst index 64adb21405..58d747f8bb 100644 --- a/docs/mosviz/index.rst +++ b/docs/mosviz/index.rst @@ -30,7 +30,7 @@ To load a sample `NIRISS Nirspec Data Set Date: Mon, 26 Jun 2023 15:59:10 -0400 Subject: [PATCH 74/82] Remove unused import Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com> --- jdaviz/core/launcher.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jdaviz/core/launcher.py b/jdaviz/core/launcher.py index 7b6188687b..f079b72582 100644 --- a/jdaviz/core/launcher.py +++ b/jdaviz/core/launcher.py @@ -1,6 +1,5 @@ import ipyvuetify as v from ipywidgets import jslink -from traitlets import Dict from jdaviz import configs as jdaviz_configs from jdaviz.core.data_formats import open as jdaviz_open From 507a52866b16d38ae220a5666c3d2875ca77f5bf Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 26 Jun 2023 16:00:14 -0400 Subject: [PATCH 75/82] Update readme to show required layout flag --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4719445abb..3a6ccd47ed 100644 --- a/README.rst +++ b/README.rst @@ -80,7 +80,7 @@ from a terminal, type: .. code-block:: bash jdaviz --help - jdaviz specviz /path/to/data/spectral_file + jdaviz --layout=specviz /path/to/data/spectral_file For more information on the command line interfaces for each tool, see the `Jdaviz docs `_. From 47b50a544934551210002b08a1db1e18c482c3b8 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 3 Jul 2023 10:08:55 -0400 Subject: [PATCH 76/82] Changelog --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d1191cabec..74598f7998 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,8 @@ New Features - The ``specviz.load_spectrum`` method is deprecated; use ``specviz.load_data`` instead. [#2273] +- Add launcher to select config and require --layout argument when launching standalone [#2257] + Cubeviz ^^^^^^^ From 0cd7a8084a9eee7c9be01f91cdd0207bce33f91d Mon Sep 17 00:00:00 2001 From: Duy Tuong Nguyen Date: Mon, 3 Jul 2023 10:32:43 -0400 Subject: [PATCH 77/82] Changelog Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com> --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 74598f7998..55bd5b9c69 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,7 +13,7 @@ New Features - The ``specviz.load_spectrum`` method is deprecated; use ``specviz.load_data`` instead. [#2273] -- Add launcher to select config and require --layout argument when launching standalone [#2257] +- Add launcher to select config and require --layout argument when launching standalone. [#2257] Cubeviz ^^^^^^^ From 294c8d67ba38d9df3c0f0915e988374032b8a90d Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 3 Jul 2023 10:44:26 -0400 Subject: [PATCH 78/82] Changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 55bd5b9c69..929526c549 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,7 +13,7 @@ New Features - The ``specviz.load_spectrum`` method is deprecated; use ``specviz.load_data`` instead. [#2273] -- Add launcher to select config and require --layout argument when launching standalone. [#2257] +- Add first-pass launcher to select config and auto-identify data. [#2257] Cubeviz ^^^^^^^ From 42075d89f3d223a53fa379eb7eb226c37265f14b Mon Sep 17 00:00:00 2001 From: Ricky O'Steen <39831871+rosteen@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:08:12 -0400 Subject: [PATCH 79/82] Update .github/workflows/standalone.yml Co-authored-by: P. L. Lim <2090236+pllim@users.noreply.github.com> --- .github/workflows/standalone.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index 1abeb02f1a..e0b6e5ed78 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -8,7 +8,6 @@ on: - 'pyinstaller_v2' tags: - 'v*' - pull_request: defaults: run: From d421571e8a0d6aa3fa1b8195dde30662812050ff Mon Sep 17 00:00:00 2001 From: Ricky O'Steen <39831871+rosteen@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:08:58 -0400 Subject: [PATCH 80/82] Update .github/workflows/standalone.yml --- .github/workflows/standalone.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml index e0b6e5ed78..3368e4e7f5 100644 --- a/.github/workflows/standalone.yml +++ b/.github/workflows/standalone.yml @@ -5,7 +5,6 @@ on: branches: - main - 'v*' - - 'pyinstaller_v2' tags: - 'v*' From 9e2c85785baba0381fca2ea46bc0773e7eb53008 Mon Sep 17 00:00:00 2001 From: Ricky O'Steen <39831871+rosteen@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:36:32 -0400 Subject: [PATCH 81/82] Update to use config launcher --- standalone/jdaviz-cli-entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standalone/jdaviz-cli-entrypoint.py b/standalone/jdaviz-cli-entrypoint.py index ade6986813..f77f124eef 100644 --- a/standalone/jdaviz-cli-entrypoint.py +++ b/standalone/jdaviz-cli-entrypoint.py @@ -21,4 +21,4 @@ def start_as_kernel(): else: import jdaviz.cli # should change this to _main, but now it doesn't need arguments - jdaviz.cli._imviz() + jdaviz.cli.main(layout="") From 946780b9a9fdf20f593629f2aae69cb7a927acaa Mon Sep 17 00:00:00 2001 From: Ricky O'Steen Date: Wed, 5 Jul 2023 16:55:08 -0400 Subject: [PATCH 82/82] Moved changelog entry to new section --- CHANGES.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3f2d48abd4..2593d792d8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -105,6 +105,10 @@ Other Changes and Additions - Gaussian smooth plugin excludes results from the gaussian smooth plugin from the input dataset dropdown. [#2239] +- CLI launchers no longer require data to be specified [#1960] + +- Added direct launchers for each config (e.g. ``specviz``) [#1960] + 3.5.1 (unreleased) ================== @@ -458,10 +462,6 @@ New Features - Model fitting: API and UI to re-estimate model parameters based on current data/subset selection. [#1952] -- CLI launchers no longer require data to be specified [#1960] - -- Added direct launchers for each config (e.g. ``specviz``) [#1960] - Cubeviz ^^^^^^^