From ea64213db5916fd5e61b830e7d204fbd5daf7b87 Mon Sep 17 00:00:00 2001 From: Pey Lian Lim <2090236+pllim@users.noreply.github.com> Date: Fri, 22 Jan 2021 15:42:09 -0500 Subject: [PATCH 1/6] WIP: Initial layout --- README.rst | 19 +- docs/imviz/index.rst | 30 ++ docs/index.rst | 1 + docs/quickstart.rst | 2 +- jdaviz/__init__.py | 1 + jdaviz/cli.py | 4 +- jdaviz/configs/__init__.py | 1 + jdaviz/configs/imviz/__init__.py | 2 + jdaviz/configs/imviz/helper.py | 12 + jdaviz/configs/imviz/imviz.ipynb | 38 ++ jdaviz/configs/imviz/imviz.yaml | 20 + jdaviz/configs/imviz/plugins/__init__.py | 2 + jdaviz/configs/imviz/plugins/parsers.py | 28 + jdaviz/configs/imviz/plugins/viewers.py | 61 +++ jdaviz/configs/imviz/tests/__init__.py | 1 + jdaviz/core/config.py | 2 + .../concepts/Imviz concept notebook.ipynb | 69 +++ ...grammatic_viewers_from_blank_default.ipynb | 505 +++--------------- setup.cfg | 2 + 19 files changed, 350 insertions(+), 450 deletions(-) create mode 100644 docs/imviz/index.rst create mode 100644 jdaviz/configs/imviz/__init__.py create mode 100644 jdaviz/configs/imviz/helper.py create mode 100644 jdaviz/configs/imviz/imviz.ipynb create mode 100644 jdaviz/configs/imviz/imviz.yaml create mode 100644 jdaviz/configs/imviz/plugins/__init__.py create mode 100644 jdaviz/configs/imviz/plugins/parsers.py create mode 100644 jdaviz/configs/imviz/plugins/viewers.py create mode 100644 jdaviz/configs/imviz/tests/__init__.py create mode 100644 notebooks/concepts/Imviz concept notebook.ipynb diff --git a/README.rst b/README.rst index aef4e0188a..7a09305471 100644 --- a/README.rst +++ b/README.rst @@ -25,14 +25,16 @@ repository. ``jdaviz`` provides data viewers and analysis plugins that can be flexibly combined as desired to create interactive applications that fit your workflow. -Three named preset configurations for common use cases are provided. **Specviz** -is a tool for visualization and quick-look analysis of 1D astronomical spectra. -**MOSviz** is a visualization tool for many astronomical spectra, -typically the output of a multi-object spectrograph (e.g., JWST -NIRSpec), and includes viewers for 1D and 2D spectra as well as -contextual information like on-sky views of the spectrograph slit. -**Cubeviz** provides a view of spectroscopic data cubes (like those to be -produced by JWST MIRI), along with 1D spectra extracted from the cube. +Several named preset configurations for common use cases are provided: + +* **Specviz**: Visualization and quick-look analysis of 1D astronomical spectra. +* **MOSviz**: Visualization tool for many astronomical spectra, + typically the output of a multi-object spectrograph (e.g., JWST + NIRSpec), and includes viewers for 1D and 2D spectra as well as + contextual information like on-sky views of the spectrograph slit. +* **Cubeviz**: View of spectroscopic data cubes (like those to be + produced by JWST MIRI), along with 1D spectra extracted from the cube. +* **Imviz**: Visualization and quick-look analysis of 2D astronomical images. Installing @@ -41,7 +43,6 @@ For details on installing and using JDAViz, see the `Jdaviz documentation `_. - License ------- diff --git a/docs/imviz/index.rst b/docs/imviz/index.rst new file mode 100644 index 0000000000..7888565620 --- /dev/null +++ b/docs/imviz/index.rst @@ -0,0 +1,30 @@ +(TODO: Nice logo here.) + +.. _imviz: + +##### +Imviz +##### + +Imviz is a tool for visualization and quick-look analysis of 2D astronomical +images. Like the rest of `jdaviz`, it is written in the Python programming +language, and therefore can be run anywhere Python is supported +(see :doc:`../installation`). Imviz is built on top of the +`astrowidgets `_ using +`Ginga `_ backend, providing a visual, +interactive interface to the analysis capabilities in that library. + +Imviz allows images to be easily displayed and examined. It supports WCS, +GWCS, ASDF, and so on. (TODO: Add content.) + + +Using Imviz +----------- + +To run Imviz in a notebook:: + + from jdaviz import Imviz + imviz = Imviz() + imviz.app + +(TODO: Add content.) diff --git a/docs/index.rst b/docs/index.rst index f0be8c39ac..09bf661584 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -38,6 +38,7 @@ Using Jdaviz specviz/index.rst cubeviz/index.rst mosviz/index.rst + imviz/index.rst Reference/API ============= diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1a3dccfb35..2b3b0fab38 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -40,4 +40,4 @@ or simply start a new Jupyter notebook and run the following in a cell:: app To learn more about the various ``jdaviz`` application configurations and loading data, see the :ref:`cubeviz`, -:ref:`specviz`, or :ref:`mosviz` tools. +:ref:`specviz`, :ref:`mosviz`, or :ref:`imviz` tools. diff --git a/jdaviz/__init__.py b/jdaviz/__init__.py index 3dfdf818a4..c22aa5bce1 100644 --- a/jdaviz/__init__.py +++ b/jdaviz/__init__.py @@ -12,3 +12,4 @@ from jdaviz.configs.specviz2d import Specviz2d # noqa from jdaviz.configs.mosviz import MosViz # noqa from jdaviz.configs.cubeviz import CubeViz # noqa +from jdaviz.configs.imviz import Imviz # noqa diff --git a/jdaviz/cli.py b/jdaviz/cli.py index 8901a0ec2b..34541dd49d 100644 --- a/jdaviz/cli.py +++ b/jdaviz/cli.py @@ -18,8 +18,8 @@ default='default', nargs=1, show_default=True, - type=click.Choice(['default', 'cubeviz', 'specviz', 'mosviz'], - case_sensitive=False), + type=click.Choice(['default', 'cubeviz', 'specviz', 'mosviz', + 'imviz'], case_sensitive=False), help="Configuration to use on application startup") def main(filename, layout='default'): """ diff --git a/jdaviz/configs/__init__.py b/jdaviz/configs/__init__.py index 0870d2aedc..957cbeb45f 100644 --- a/jdaviz/configs/__init__.py +++ b/jdaviz/configs/__init__.py @@ -2,3 +2,4 @@ from .specviz import * # noqa from .default import * # noqa from .mosviz import * # noqa +from .imviz import * # noqa diff --git a/jdaviz/configs/imviz/__init__.py b/jdaviz/configs/imviz/__init__.py new file mode 100644 index 0000000000..1abedfa2a4 --- /dev/null +++ b/jdaviz/configs/imviz/__init__.py @@ -0,0 +1,2 @@ +from .plugins import * # noqa +from .helper import Imviz # noqa diff --git a/jdaviz/configs/imviz/helper.py b/jdaviz/configs/imviz/helper.py new file mode 100644 index 0000000000..18524e62f7 --- /dev/null +++ b/jdaviz/configs/imviz/helper.py @@ -0,0 +1,12 @@ +from jdaviz.core.helpers import ConfigHelper + +__all__ = ['Imviz'] + + +class Imviz(ConfigHelper): + """Imviz helper class.""" + + _default_configuration = "imviz" + + def show(self): + self.app diff --git a/jdaviz/configs/imviz/imviz.ipynb b/jdaviz/configs/imviz/imviz.ipynb new file mode 100644 index 0000000000..4c31883657 --- /dev/null +++ b/jdaviz/configs/imviz/imviz.ipynb @@ -0,0 +1,38 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from jdaviz import Imviz\n", + "\n", + "imviz = Imviz()\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.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jdaviz/configs/imviz/imviz.yaml b/jdaviz/configs/imviz/imviz.yaml new file mode 100644 index 0000000000..15aeee4091 --- /dev/null +++ b/jdaviz/configs/imviz/imviz.yaml @@ -0,0 +1,20 @@ +settings: + configuration: imviz + data: + parser: imviz-image-parser + visible: + menu_bar: false + toolbar: false + tray: false + tab_headers: false + context: + notebook: + max_height: 600px +viewer_area: + - container: col + children: + - container: row + viewers: + - name: Image + plot: imviz-image-viewer + reference: image-viewer diff --git a/jdaviz/configs/imviz/plugins/__init__.py b/jdaviz/configs/imviz/plugins/__init__.py new file mode 100644 index 0000000000..641aaeb153 --- /dev/null +++ b/jdaviz/configs/imviz/plugins/__init__.py @@ -0,0 +1,2 @@ +from .viewers import * # noqa +from .parsers import * # noqa diff --git a/jdaviz/configs/imviz/plugins/parsers.py b/jdaviz/configs/imviz/plugins/parsers.py new file mode 100644 index 0000000000..a4aee0eecf --- /dev/null +++ b/jdaviz/configs/imviz/plugins/parsers.py @@ -0,0 +1,28 @@ +import base64 +import pathlib +import uuid + +from jdaviz.core.registries import data_parser_registry + +__all__ = ["imviz_image_parser"] + + +@data_parser_registry("imviz-image-parser") +def imviz_image_parser(app, data, data_label=None, show_in_viewer=True): + """Loads an image into Imviz""" + # If no data label is assigned, give it a unique identifier + if not data_label: + data_label = "imviz_data|" + str( + base64.b85encode(uuid.uuid4().bytes), "utf-8") + + path = pathlib.Path(data) + if path.is_file(): + # TODO: Support other image formats + from astropy.io import fits + data = fits.getdata(path) + else: + raise FileNotFoundError(f"No such file: {path}") + + app.add_data(data, data_label) + if show_in_viewer: + app.add_data_to_viewer("image-viewer", data_label) diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py new file mode 100644 index 0000000000..ef09ea1d9d --- /dev/null +++ b/jdaviz/configs/imviz/plugins/viewers.py @@ -0,0 +1,61 @@ +from astrowidgets.core import ImageWidget +from ginga.misc.log import get_logger + +from jdaviz.core.registries import viewer_registry + +__all__ = ['ImvizImageView'] + + +class DummyLayout: + def __init__(self): + self.height = '' + self.width = '' + + +class DummyModelID: + def __init__(self): + self.model_id = '1234' + self.layout = DummyLayout() + + +class ImvizImageWidget(ImageWidget): + """Image widget for Imviz.""" + + # session is a glue thing + def __init__(self, session, *args, **kwargs): + # logger needs special handling because using default logger of None + # will crash Ginga internals. + kwargs['logger'] = get_logger('imviz', log_stderr=True, log_file=None, + level=30) + super().__init__(*args, **kwargs) + + # More glue things + + def register_to_hub(self, *args, **kwargs): + pass + + @property + def toolbar_selection_tools(self): + class Dummy(DummyModelID): + def __init__(self): + super().__init__() + self.borderless = False + + return Dummy() + + @property + def figure_widget(self): + return DummyModelID() + + @property + def layer_options(self): + return DummyModelID() + + @property + def viewer_options(self): + return DummyModelID() + + +@viewer_registry("imviz-image-viewer", label="Image 2D (Imviz)") +class ImvizImageView(ImvizImageWidget): + default_class = None diff --git a/jdaviz/configs/imviz/tests/__init__.py b/jdaviz/configs/imviz/tests/__init__.py new file mode 100644 index 0000000000..6bd08d5bf6 --- /dev/null +++ b/jdaviz/configs/imviz/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for Imviz image viewer.""" diff --git a/jdaviz/core/config.py b/jdaviz/core/config.py index d92484f340..ce45df7808 100644 --- a/jdaviz/core/config.py +++ b/jdaviz/core/config.py @@ -39,6 +39,8 @@ def read_configuration(path=None): path = default_path / "mosviz" / "mosviz.yaml" elif path == 'specviz2d': path = default_path / "specviz2d" / "specviz2d.yaml" + elif path == 'imviz': + path = default_path / "imviz" / "imviz.yaml" elif not os.path.isfile(path): raise ValueError("Configuration must be path to a .yaml file.") diff --git a/notebooks/concepts/Imviz concept notebook.ipynb b/notebooks/concepts/Imviz concept notebook.ipynb new file mode 100644 index 0000000000..268977614a --- /dev/null +++ b/notebooks/concepts/Imviz concept notebook.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "with warnings.catch_warnings():\n", + " warnings.simplefilter('ignore')\n", + "\n", + " from jdaviz import Imviz\n", + " imviz = Imviz()\n", + "\n", + "imviz.app" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.utils.data import download_file\n", + "\n", + "filename = download_file('http://data.astropy.org/photometry/spitzer_example_image.fits', cache=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "imviz.load_data(filename)" + ] + }, + { + "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.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/concepts/concept_programmatic_viewers_from_blank_default.ipynb b/notebooks/concepts/concept_programmatic_viewers_from_blank_default.ipynb index e20da93081..80f8c82978 100644 --- a/notebooks/concepts/concept_programmatic_viewers_from_blank_default.ipynb +++ b/notebooks/concepts/concept_programmatic_viewers_from_blank_default.ipynb @@ -8,7 +8,7 @@ "Start with a blank default application and programmatically add views and data to those views\n", "\n", "### Use Case\n", - "Progammatically load a single FITS file, e.g. for 2d spectroscopic data, into an \"image\" viewer. Or a 1d spectrum into a spectrum viewer. For cases that do not fit into a given pre-made configuration. \n", + "Programmatically load a single FITS file, e.g. for 2d spectroscopic data, into an \"image\" viewer. Or a 1d spectrum into a spectrum viewer. For cases that do not fit into a given pre-made configuration. \n", "\n", "### MAST Use Case\n", "MAST auto-generates notebooks that when users run, needs to download the data, create the relevant jdaviz application / viewers, and load the data by default. One click run gets the user back to where they were on the web." @@ -16,22 +16,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from IPython.core.display import display, HTML\n", "display(HTML(\"\"))" @@ -39,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -48,7 +35,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -66,26 +53,11 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b7949170270f421ba9429cab2477b53d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Application(components={'g-viewer-tab': '