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..3dae4002a3 --- /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 +`Glupyter `_ 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 +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..d0033d6939 --- /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: imviz-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..e260835698 --- /dev/null +++ b/jdaviz/configs/imviz/plugins/parsers.py @@ -0,0 +1,27 @@ +import pathlib + +from astropy.nddata import CCDData + +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: + from astrowidgets.glupyter import _gen_random_label + data_label = _gen_random_label(prefix="imviz_data|") + + path = pathlib.Path(data) + if path.is_file(): + # TODO: Support other image formats + data = CCDData.read(path) + else: + raise FileNotFoundError(f"No such file: {path}") + + app.add_data(data, data_label) + if show_in_viewer: + app.add_data_to_viewer("imviz-image-viewer", data_label, clear_other_data=True) diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py new file mode 100644 index 0000000000..7e3de5b368 --- /dev/null +++ b/jdaviz/configs/imviz/plugins/viewers.py @@ -0,0 +1,68 @@ +from astrowidgets.glupyter import ImageWidget +from glue.core import BaseData + +from jdaviz.core.registries import viewer_registry + +__all__ = ['ImvizImageView'] + + +@viewer_registry("imviz-image-viewer", label="Image 2D (Imviz)") +class ImvizImageView(ImageWidget): + """Image widget for Imviz.""" + + default_class = None + + # session is a glue thing + def __init__(self, session, *args, **kwargs): + + super().__init__(*args, **kwargs) + self._viewer._session = session + + # More glue things + + def register_to_hub(self, *args, **kwargs): + self._viewer.register_to_hub(*args, **kwargs) + + def add_data(self, data): + self._viewer.add_data(data) + self._link_image_to_cb() + + def remove_data(self, data): + self._viewer.remove_data(data) + self.viewer.interaction = None + + @property + def toolbar_selection_tools(self): + return self._viewer.toolbar_selection_tools + + @property + def figure_widget(self): + return self + + @property + def layer_options(self): + return self._viewer.layer_options + + @property + def viewer_options(self): + return self._viewer.viewer_options + + @property + def state(self): + return self._viewer.state + + def set_plot_axes(self): + self.viewer.axes[1].tick_format = None + self.viewer.axes[0].tick_format = None + + self.viewer.axes[1].label = "y: pixels" + self.viewer.axes[0].label = "x: pixels" + + # Make it so y axis label is not covering tick numbers. + self.viewer.axes[1].label_offset = "-50" + + def data(self, cls=None): + return [layer_state.layer + for layer_state in self._viewer.state.layers + if hasattr(layer_state, 'layer') and + isinstance(layer_state.layer, BaseData)] 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..a5f48ae05e --- /dev/null +++ b/notebooks/concepts/Imviz concept notebook.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Imviz\n", + "\n", + "Special software requirements:\n", + "\n", + "* Dev version of `glue-jupyter`\n", + "* Latest `bqplot-image-gl` might work though I used dev version too\n", + "* `astrowidgets` from https://github.com/astropy/astrowidgets/pull/131\n", + "\n", + "Known issues:\n", + "\n", + "* Mouse interaction for cursor info panel breaks when you use scroll/pan GUI from the toolbar.\n", + "* To regain mouse interaction, you need to go into Data layer to uncheck/check the data name, which resets everything." + ] + }, + { + "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": "markdown", + "metadata": {}, + "source": [ + "This exposes the underlying `astrowidgets` API." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "w = imviz.app.get_viewer('imviz-image-viewer')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "w.set_colormap('viridis')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "w.cuts = (0, 100)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "w.stretch = 'arcsinh'" + ] + }, + { + "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': '