diff --git a/.travis.yml b/.travis.yml index 64c3494..8143411 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: - 2.7 - - 3.5 + - 3.6 # Setting sudo to false opts in to Travis-CI container-based builds. sudo: false @@ -33,7 +33,7 @@ env: # For this package-template, we include examples of Cython modules, # so Cython is required for testing. If your package does not include # Cython code, you can set CONDA_DEPENDENCIES='' - - CONDA_DEPENDENCIES='' + - CONDA_DEPENDENCIES='numpy scipy astropy ginga' # If there are matplotlib or other GUI tests, uncomment the following # line to use the X virtual framebuffer. @@ -42,32 +42,29 @@ env: matrix: # Make sure that egg_info works without dependencies - SETUP_CMD='egg_info' - # DISABLED UNTIL WE HAVE REAL TESTS - # Try all python versions with the latest numpy - #- SETUP_CMD='test' + # Run actual tests + - SETUP_CMD='test' matrix: include: - # DISABLED FOR NOW - # Do a coverage test in Python 3. - #- python: 3.5 - # env: SETUP_CMD='test --coverage' - # Check for sphinx doc build warnings - we do this first because it # may run for a long time - - python: 3.5 + - python: 3.6 env: SETUP_CMD='build_docs -w' - PIP_DEPENDENCIES='ginga pandoc nbconvert tornado scipy' # DISABLED FOR NOW - # Try Astropy development version + # Do a coverage test in Python 3. #- python: 3.6 - # env: ASTROPY_VERSION=development + # env: SETUP_CMD='test --coverage' + + # Try Astropy development version + - python: 3.6 + env: ASTROPY_VERSION=development - # Do a PEP8 test with pycodestyle - - python: 3.5 - env: MAIN_CMD='pycodestyle stginga --count' SETUP_CMD='' + # Do a PEP8 test with flake8 + - python: 3.6 + env: MAIN_CMD='flake8 stginga --count' SETUP_CMD='' install: diff --git a/CHANGES.rst b/CHANGES.rst index 85f6c2b..24b93b6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,19 +1,30 @@ 0.3 (unreleased) ---------------- +This is the last release to support Python 2. + New Features ^^^^^^^^^^^^ -- Use new notification system for ChangeHistory that will go with Ginga 2.7. +- Use new notification system for ``ChangeHistory`` that goes with Ginga 2.7. [#147,#149,ejeschke/ginga#621] -- Added example to auto-start MultiDim in ginga_config.py. [#144] +- Added example to auto-start ``MultiDim`` in ``ginga_config.py``. [#144] + +API changes +^^^^^^^^^^^ +- ``MIPick``, ``MultiImage``, and ``Smoothing`` plugins are moved to + "experimental" folder. So is the custom layout that goes with ``MultiImage`` + and ``MIPick``. These are no longer actively supported. [#152] +- ``WBrowser`` now supports docstring rendering while offline when Internet + connection is unavalable. [#152] Bug fixes ^^^^^^^^^ -- Use toolkit-agnostic treeview deselection for MosaicAuto. [#142] +- Use toolkit-agnostic treeview deselection for ``MosaicAuto``. [#142] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- Updated astropy-helpers to v2.0.2. +- Updated ``astropy-helpers`` to v2.0.4. [#152] +- Deprecated Jupyter notebook support is removed. [#152] 0.2.1 (2017-07-20) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index 104ba14..1e317d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,6 +11,7 @@ recursive-include docs * recursive-include licenses * recursive-include cextern * recursive-include scripts * +recursive-include experimental * prune build prune docs/_build diff --git a/README.rst b/README.rst index 3055e7e..d48a997 100644 --- a/README.rst +++ b/README.rst @@ -1,18 +1,21 @@ stginga ======= -.. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat - :target: http://www.astropy.org - :alt: Powered by Astropy Badge +.. image:: https://readthedocs.org/projects/stginga/badge/?version=latest + :target: https://stginga.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status .. image:: https://travis-ci.org/spacetelescope/stginga.svg?branch=master :target: https://travis-ci.org/spacetelescope/stginga :alt: Travis CI results -.. image:: https://readthedocs.org/projects/stginga/badge/?version=latest - :target: https://stginga.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status +.. image:: https://ci.appveyor.com/api/projects/status/ulg6cmh15xpyyvp5?svg=true + :target: https://ci.appveyor.com/project/pllim/stginga + :alt: Appveyor CI results +.. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat + :target: http://www.astropy.org + :alt: Powered by Astropy Badge `Ginga `_ products specific to STScI data analysis. diff --git a/appveyor.yml b/appveyor.yml index 7a4ab1c..132b424 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,7 @@ environment: # so Cython is required for testing. If your package does not include # Cython code, you can set CONDA_DEPENDENCIES='' #CONDA_DEPENDENCIES: "Cython" - CONDA_DEPENDENCIES: "" + CONDA_DEPENDENCIES: "numpy scipy astropy ginga" matrix: diff --git a/astropy_helpers b/astropy_helpers index d23a53f..41a6072 160000 --- a/astropy_helpers +++ b/astropy_helpers @@ -1 +1 @@ -Subproject commit d23a53f46dd1c3703e5eee63dca3f53bd18a4e8b +Subproject commit 41a607235bdc335c9c125f828bdd35502a09aff9 diff --git a/docs/_templates/autosummary/base.rst b/docs/_templates/autosummary/base.rst deleted file mode 100644 index 9cabaf5..0000000 --- a/docs/_templates/autosummary/base.rst +++ /dev/null @@ -1,2 +0,0 @@ -{% extends "autosummary_core/base.rst" %} -{# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #} \ No newline at end of file diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 100644 index 6b214a5..0000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1,2 +0,0 @@ -{% extends "autosummary_core/class.rst" %} -{# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #} \ No newline at end of file diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst deleted file mode 100644 index f38315b..0000000 --- a/docs/_templates/autosummary/module.rst +++ /dev/null @@ -1,2 +0,0 @@ -{% extends "autosummary_core/module.rst" %} -{# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 5b097b9..680af74 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -173,42 +173,3 @@ edit_on_github_source_root = "" edit_on_github_doc_root = "docs" - -# -- Extra stuff specific to stginga ------------------------------------------- - -# a simple/non-configurable extension that generates the rst files for ipython -# notebooks -def notebooks_to_rst(app): - from glob import glob - - # post "big-split", nbconvert is a separate namespace - from nbconvert.nbconvertapp import NbConvertApp - from nbconvert.writers import FilesWriter - - class OrphanizerWriter(FilesWriter): - def write(self, output, resources, **kwargs): - output = ':orphan:\n\n' + output - FilesWriter.write(self, output, resources, **kwargs) - - olddir = os.path.abspath(os.curdir) - try: - srcdir = os.path.abspath(os.path.split(__file__)[0]) - os.chdir(os.path.join(srcdir, 'stginga', 'notebooks')) - nbs = glob('*.ipynb') - - app = NbConvertApp() - app.initialize(argv=[]) - app.writer = OrphanizerWriter() - - app.export_format = 'rst' - app.notebooks = nbs - - app.start() - except: - pass - finally: - os.chdir(olddir) - - -def setup(app): - app.connect('builder-inited', notebooks_to_rst) diff --git a/docs/index.rst b/docs/index.rst index 82ef31a..857a2dd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,5 +27,4 @@ Using ``stginga`` stginga/run stginga/plugins_manual/index - stginga/ipynb stginga/ref_api diff --git a/docs/rtd-pip-requirements b/docs/rtd-pip-requirements index ebfba0f..1692894 100644 --- a/docs/rtd-pip-requirements +++ b/docs/rtd-pip-requirements @@ -1,10 +1,5 @@ numpy -matplotlib -Cython scipy>=0.18 astropy-helpers -astropy -tornado -jupyter_client -nbconvert -ginga>=2.6.2 +astropy>=2 +ginga>=2.7 diff --git a/docs/stginga/install.rst b/docs/stginga/install.rst index ede9ef5..d70e8d7 100644 --- a/docs/stginga/install.rst +++ b/docs/stginga/install.rst @@ -5,16 +5,20 @@ Installation ``stginga`` requires: -* Astropy 1.1 or later. -* SciPy 0.16 or later. -* Ginga 2.6 or later, available from +* Astropy 2.0 or later. +* SciPy 0.18 or later. +* Ginga 2.7 or later, available from `Ginga's GitHub page `_. -* The latest version of ``stginga`` available from - `stginga's GitHub page `_. -We suggest using `Anaconda `_ as a -Python distribution that is known to work with ``stginga``. +We suggest using `Anaconda `_ as a +Python distribution that is known to work with ``stginga``:: -To install ``stginga`` from source:: + conda install stginga -c http://ssb.stsci.edu/astroconda - python setup.py install [--prefix=/my/install/path] +Alternately, ``stginga`` 0.3 and beyond is also available on PyPI:: + + pip install stginga + +If you wish to install the development version instead:: + + pip install git+https://github.com/spacetelescope/stginga.git@master diff --git a/docs/stginga/ipynb.rst b/docs/stginga/ipynb.rst deleted file mode 100644 index e29a70c..0000000 --- a/docs/stginga/ipynb.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _stginga-ipynb: - -Running stginga with Jupyter/IPython Notebooks -============================================== - -.. warning:: - - :mod:`~stginga.nbinteract` is deprecated and will be removed in a future - release. - Please see `this notebook example provided by Ginga `_ instead. - -:mod:`stginga` includes the :mod:`~stginga.nbinteract`, a module to simplify the use of -the ginga viewer inside, or in close association with, -`Jupyter (formerly IPython) notebooks `_. The module is -primarily intended to provide a convenience interface to the HTML canvas -backend for ginga in a way that allows interactivity between the viewer and -the python session running in a notebook. - -The current functionality in :mod:`~stginga.nbinteract` is focused around setting -up a viewer context and loading data into it. To see a usage example -demonstrating the current functionality, see -:doc:`This example notebook ` -(which you can get in notebook form from -`the github repository `_). -Future improvements are planned to add more convenience features like accessing -ginga regions or marking up images with only a few lines of code. diff --git a/docs/stginga/notebooks/ginga_nbinteract.ipynb b/docs/stginga/notebooks/ginga_nbinteract.ipynb deleted file mode 100644 index 1529d9f..0000000 --- a/docs/stginga/notebooks/ginga_nbinteract.ipynb +++ /dev/null @@ -1,181 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example of nbinteract usage " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from __future__ import print_function\n", - "\n", - "import logging\n", - "import webbrowser\n", - "from cStringIO import StringIO\n", - "\n", - "import numpy as np\n", - "\n", - "from astropy.io import fits\n", - "from stginga import nbinteract\n", - "\n", - "from IPython.html.widgets import interact\n", - "from IPython import display" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "server = nbinteract.GingaServer()\n", - "server.start()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "server.get_viewer_urls()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "display.IFrame(server.get_viewer_urls()['Main Viewer'], 625, 625)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The next cell will open a new window with the same view as above" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "webbrowser.open(server.get_viewer_urls()['Main Viewer'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that this next cell may take some time to download the first time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "f = fits.open('https://archive.stsci.edu/pub/hlsp/angst/acs/hlsp_angst_hst_acs-wfc_10210-ugc8760_f814w_v1_ref.fits')\n", - "server.load_fits(f)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You'll need to download some ACS image for this dataset to run the next cell. A convenient one (to match the above) might be: https://archive.stsci.edu/cgi-bin/mastpreview?mission=hst&dataid=J8YY05021" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "fi = fits.open('j8yy05kbq_drc.fits')\n", - "\n", - "@interact(hdunum=(1,3))\n", - "def switch_hdu(hdunum=1):\n", - " server.load_fits(fi[hdunum])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "v = server.viewers['Main Viewer']\n", - "aim = v.fitsimage.get_image()\n", - "\n", - "for r in v.canvas.get_objects()[1:]:\n", - " corners = np.array(r.get_points())\n", - " xmin, xmax = np.min(corners[:, 0]), np.max(corners[:, 0])\n", - " ymin, ymax = np.min(corners[:, 1]), np.max(corners[:, 1])\n", - " data_square = aim.get_data()[ymin:ymax, xmin:xmax]\n", - " flux = np.sum(data_square)\n", - " print('Rectangle centered on', r.get_center_pt(), 'has flux', flux)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# clear all rectangles\n", - "v = server.viewers['Main Viewer']\n", - "v.canvas.deleteObjects(v.canvas.get_objects()[1:])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.10" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/docs/stginga/plugins_manual/backgroundsub.rst b/docs/stginga/plugins_manual/backgroundsub.rst index a8e19ea..d8ecaa6 100644 --- a/docs/stginga/plugins_manual/backgroundsub.rst +++ b/docs/stginga/plugins_manual/backgroundsub.rst @@ -5,11 +5,9 @@ BackgroundSub .. image:: images/backgroundsub_screenshot.png :width: 800px + :align: center :alt: BackgroundSub plugin -This local plugin is used to calculate and subtract background value. Currently, -it only handles constant background. Subtraction parameters can be saved to a -JSON file, which then can be reloaded as well. The subtracted image can be saved -using :ref:`ginga:sec-plugins-global-saveimage`. - -.. automodule:: stginga.plugins.BackgroundSub +.. automodapi:: stginga.plugins.BackgroundSub + :no-heading: + :skip: BackgroundSub diff --git a/docs/stginga/plugins_manual/badpixcorr.rst b/docs/stginga/plugins_manual/badpixcorr.rst index 0a54a44..1e8d1a0 100644 --- a/docs/stginga/plugins_manual/badpixcorr.rst +++ b/docs/stginga/plugins_manual/badpixcorr.rst @@ -5,13 +5,9 @@ BadPixCorr .. image:: images/badpixcorr_screenshot.png :width: 800px + :align: center :alt: BadPixCorr plugin -This local plugin is used to fix bad pixels. Currently, it only handles fixing -a single bad pixel or bad pixels within a circular region. The corresponding -DQ flags will also be set to the given new flag value (default is zero). -Correction parameters can be saved to a JSON file, which then can be reloaded -as well. The corrected image can be saved using -:ref:`ginga:sec-plugins-global-saveimage`. - -.. automodule:: stginga.plugins.BadPixCorr +.. automodapi:: stginga.plugins.BadPixCorr + :no-heading: + :skip: BadPixCorr diff --git a/docs/stginga/plugins_manual/dqinspect.rst b/docs/stginga/plugins_manual/dqinspect.rst index 6b30d71..4776a56 100644 --- a/docs/stginga/plugins_manual/dqinspect.rst +++ b/docs/stginga/plugins_manual/dqinspect.rst @@ -5,10 +5,9 @@ DQInspect .. image:: images/dqinspect_screenshot.png :width: 800px + :align: center :alt: DQInspect plugin -This local plugin is used to inspect the associated DQ array of a given image. -It shows the different DQ flags that went into a given pixel (middle right) -and also the overall mask of the selected DQ flag(s) (bottom right). - -.. automodule:: stginga.plugins.DQInspect +.. automodapi:: stginga.plugins.DQInspect + :no-heading: + :skip: DQInspect diff --git a/docs/stginga/plugins_manual/index.rst b/docs/stginga/plugins_manual/index.rst index c0f5085..51633a2 100644 --- a/docs/stginga/plugins_manual/index.rst +++ b/docs/stginga/plugins_manual/index.rst @@ -8,19 +8,12 @@ available, in addition to the ones that already come with Ginga. Some are customizable via plugin configuration files, which are available in the `stginga/examples/configs `_ directory. + .. _stginga-local-plugins: Local Plugins ------------- -These plugins work together to display the same object across multiple images: - -.. toctree:: - :maxdepth: 2 - - multiimage - mipick - These plugins behave like a regular Ginga plugin: .. toctree:: @@ -40,6 +33,16 @@ one way is to use the ``--plugins`` option along with ``stginga`` command :maxdepth: 2 mosaicauto + +These plugins are not actively maintained. If you really, really want to use +them, see instructions at +`experimental/README.rst `_: + +.. toctree:: + :maxdepth: 2 + + mipick + multiimage smoothing diff --git a/docs/stginga/plugins_manual/mipick.rst b/docs/stginga/plugins_manual/mipick.rst index f6dcfcd..56c058d 100644 --- a/docs/stginga/plugins_manual/mipick.rst +++ b/docs/stginga/plugins_manual/mipick.rst @@ -3,6 +3,8 @@ MIPick ====== +.. warning:: This is experimental; not actively maintain and might not work. + .. image:: images/mipick_screenshot.png :width: 800px :alt: MIPick plugin diff --git a/docs/stginga/plugins_manual/mosaicauto.rst b/docs/stginga/plugins_manual/mosaicauto.rst index 7a9bdfb..e0936cb 100644 --- a/docs/stginga/plugins_manual/mosaicauto.rst +++ b/docs/stginga/plugins_manual/mosaicauto.rst @@ -5,27 +5,9 @@ MosaicAuto .. image:: images/mosaicauto_screenshot.png :width: 800px + :align: center :alt: MosaicAuto plugin -.. warning:: This can be very memory intensive. - -This local plugin is used to automatically create a mosaic of all currently -loaded images in the channel. The position of an image on the mosaic is -determined by its WCS without distortion correction. This is meant as a -quick-look tool, not an -`AstroDrizzle `_ -replacement. Currently, such a mosaic can only be created once per Ginga -session. - -Once the mosaic is successfully created, user can select the desired -image name(s) to highlight associated footprint(s) on the mosaic. -Alternately, user can also click directly on the displayed mosaic to -highlight a footprint; Clicking on a highlighted footprint again will -un-highlight it. - -A shortlist of only the selected image names are displayed under the "Selected" -tab. This list can be saved to a file by clicking "Save Selection" button. -Optionally, the mosaic itself can be saved using -:ref:`ginga:sec-plugins-global-saveimage`. - -.. automodule:: stginga.plugins.MosaicAuto +.. automodapi:: stginga.plugins.MosaicAuto + :no-heading: + :skip: MosaicAuto diff --git a/docs/stginga/plugins_manual/multiimage.rst b/docs/stginga/plugins_manual/multiimage.rst index 90ed88e..3eef4ff 100644 --- a/docs/stginga/plugins_manual/multiimage.rst +++ b/docs/stginga/plugins_manual/multiimage.rst @@ -3,6 +3,8 @@ MultiImage ========== +.. warning:: This is experimental; not actively maintain and might not work. + .. image:: images/multiimage_screenshot.png :width: 800px :alt: MultiImage plugin diff --git a/docs/stginga/plugins_manual/smoothing.rst b/docs/stginga/plugins_manual/smoothing.rst index b382f6c..9dc3397 100644 --- a/docs/stginga/plugins_manual/smoothing.rst +++ b/docs/stginga/plugins_manual/smoothing.rst @@ -3,6 +3,8 @@ Smoothing --------- +.. warning:: This is experimental; not actively maintain and might not work. + .. image:: images/smoothing_before.png :width: 800px :alt: Smoothing plugin, before @@ -31,5 +33,3 @@ The smoothed image is inserted into Ginga as a new image, leaving the original image untouched. Details on the smoothing performed can be viewed using :ref:`ginga:sec-plugins-changehistory`. In addition, it can be saved using :ref:`ginga:sec-plugins-global-saveimage`. - -.. automodule:: stginga.plugins.Smoothing diff --git a/docs/stginga/plugins_manual/snrcalc.rst b/docs/stginga/plugins_manual/snrcalc.rst index 77d2109..1bfcecc 100644 --- a/docs/stginga/plugins_manual/snrcalc.rst +++ b/docs/stginga/plugins_manual/snrcalc.rst @@ -5,46 +5,9 @@ SNRCalc .. image:: images/snrcalc_screenshot.png :width: 800px + :align: center :alt: SNRCalc plugin -This local plugin is used to calculate the surface-to-background ratio (SBR) -and the signal-to-noise ratio (SNR), as follow. - -SBR is as defined by `Ball `_, *"Take the median -value of the pixels within the image. In the case of a defocused spot, this is -just the median value within the 'top hat' portion of the image. Next, take the -standard deviation of the pixels that are clearly in the background, that is, -have no incident photons on them. Take the ratio of these two quantities, and -you have the signal-to-background ratio."* - -Given selected science (:math:`S`) and background (:math:`B`) regions: - -.. math:: - - \mathrm{SBR} = \frac{\mathrm{MEDIAN}(S)}{\mathrm{STDEV}(B)} - -For the science region above, as long as the image has an accompanying error -array (e.g., the ``ERR`` extension), its SNR can also be calculated: - -.. math:: - - a = \frac{S}{\mathrm{ERR}} - - \mathrm{SNR}_{\mathrm{min}} = \mathrm{MIN}(a) - - \mathrm{SNR}_{\mathrm{max}} = \mathrm{MAX}(a) - - \overline{\mathrm{SNR}} = \mathrm{MEAN}(a) - -While SNR is more popular, SBR is useful for images without existing or -reliable errors. User can also define a minimum limit for SBR check, so that -the GUI can provide a quick visual indication on whether the image achieves the -desired SBR or not. As part of the statistics, mean background value is also -provided albeit not used in SBR nor SNR calculations. - -User can save the calculated values in the image header using the "Update HDR" -button. Calculation parameters can be saved to a JSON file, which then can be -reloaded as well. The image with updated header can be saved using -:ref:`ginga:sec-plugins-global-saveimage`. - -.. automodule:: stginga.plugins.SNRCalc +.. automodapi:: stginga.plugins.SNRCalc + :no-heading: + :skip: SNRCalc diff --git a/docs/stginga/ref_api.rst b/docs/stginga/ref_api.rst index fbeea60..051314d 100644 --- a/docs/stginga/ref_api.rst +++ b/docs/stginga/ref_api.rst @@ -5,9 +5,6 @@ Reference/API .. automodapi:: stginga.gingawrapper -.. automodapi:: stginga.nbinteract - :no-inheritance-diagram: - .. automodapi:: stginga.plugin_info .. automodapi:: stginga.utils diff --git a/docs/stginga/run.rst b/docs/stginga/run.rst index 176f782..83be8bf 100644 --- a/docs/stginga/run.rst +++ b/docs/stginga/run.rst @@ -3,10 +3,10 @@ Running Ginga With stginga Plugins ================================== -``stginga`` includes additional plugins to beyond those provided by Ginga -itself that add functionality. There are a few different ways to start -Ginga in a way that will make it recognize those plugins; Only use *one* of the -following options: +``stginga`` includes plugins beyond those provided by Ginga itself that add +functionalities specific to STScI needs. There are a few different ways to +start Ginga in a way that will make it recognize these plugins; Only use *one* +of the following options: #. :ref:`stginga-run-script` #. :ref:`stginga-run-gingaconfig` @@ -18,7 +18,7 @@ following options: The stginga Script ------------------ -The simplest way is to simply use a script packaged with ``stginga`` that knows +The simplest way is to use a script packaged with ``stginga`` that knows how to preload the :ref:`STScI plugins `:: stginga [args] diff --git a/experimental/README.rst b/experimental/README.rst new file mode 100644 index 0000000..37755e6 --- /dev/null +++ b/experimental/README.rst @@ -0,0 +1,7 @@ +The plugins in this folder were developed during JWST DADF sprints and +are not actively maintained. There is no guarantee that they still work. + +To use them anyway, copy them to ``~/.ginga/plugins`` and then use the +``--plugins`` option when starting Ginga or ``stginga`` from the command line. +For ``MultiImage`` and ``MIPick``, you also need to copy the +``ginga_config.py`` file here to your ``~/.ginga`` directory. diff --git a/stginga/examples/configs/plugin_Smoothing.cfg b/experimental/configs/plugin_Smoothing.cfg similarity index 100% rename from stginga/examples/configs/plugin_Smoothing.cfg rename to experimental/configs/plugin_Smoothing.cfg diff --git a/experimental/ginga_config.py b/experimental/ginga_config.py new file mode 100644 index 0000000..686130d --- /dev/null +++ b/experimental/ginga_config.py @@ -0,0 +1,80 @@ +multiimage_layout = ['seq', {}, [ + 'vbox', {'name': 'top', 'width': 1520, 'height': 900}, + {'row': ['hbox', {'name': 'menu'}], 'stretch': 0}, + {'row': [ + 'vpanel', {}, [ + 'vbox', {}, + {'row': [ + 'hpanel', {'name': 'hpnl'}, [ + 'ws', {'name': 'left', 'width': 300, 'group': 2}, [ + ('Info', [ + 'vpanel', {}, [ + 'ws', {'name': 'uleft', 'height': 300, + 'show_tabs': False, 'group': 3} + ], + [ + 'ws', {'name': 'lleft', 'height': 430, + 'show_tabs': True, 'group': 3} + ] + ]) + ] + ], + [ + 'vbox', {'name': 'main', 'width': 700}, + {'row': [ + 'ws', {'wstype': 'tabs', 'name': 'channels', + 'group': 1, 'use_toolbar': True} + ], + 'stretch': 1 + }, + {'row': [ + 'ws', {'wstype': 'stack', 'name': 'cbar', + 'group': 99} + ], + 'stretch': 0 + }, + {'row': [ + 'ws', {'wstype': 'stack', 'name': 'readout', + 'group': 99} + ], + 'stretch': 0 + }, + {'row': [ + 'ws', {'wstype': 'stack', 'name': 'operations', + 'group': 99} + ], + 'stretch': 0 + } + ], + [ + 'ws', {'name': 'right', 'width': 430, 'group': 2}, [ + ('Dialogs', [ + 'ws', {'name': 'dialogs', 'group': 2} + ]) + ] + ] + ], + 'stretch': 1}, [ + 'ws', {'name': 'toolbar', 'height': 40, + 'show_tabs': False, 'group': 2} + ] + ], + [ + 'hbox', {'name': 'pstamps'} + ], + ]}, + {'row': [ + 'hbox', {'name': 'status'} + ], + 'stretch': 0 + } +]] + + +def pre_gui_config(ginga): + # This is needed for MultiImage and MIPick + ginga.set_layout(multiimage_layout) + + +def post_gui_config(ginga): + pass diff --git a/stginga/plugins/MIPick.py b/experimental/plugins/MIPick.py similarity index 97% rename from stginga/plugins/MIPick.py rename to experimental/plugins/MIPick.py index f5e4aa3..18e72f3 100644 --- a/stginga/plugins/MIPick.py +++ b/experimental/plugins/MIPick.py @@ -1,9 +1,4 @@ -# -# MIPick.py -- Multi-Image Pick plugin for Ginga reference viewer -# -# This is open-source software licensed under a BSD license. -# Please see the file LICENSE.txt for details. -# +"""Multi-Image Pick plugin for Ginga reference viewer.""" # GINGA from ginga.rv.plugins.Pick import Pick diff --git a/stginga/plugins/MultiImage.py b/experimental/plugins/MultiImage.py similarity index 100% rename from stginga/plugins/MultiImage.py rename to experimental/plugins/MultiImage.py diff --git a/stginga/plugins/Smoothing.py b/experimental/plugins/Smoothing.py similarity index 96% rename from stginga/plugins/Smoothing.py rename to experimental/plugins/Smoothing.py index 5c04543..1728580 100644 --- a/stginga/plugins/Smoothing.py +++ b/experimental/plugins/Smoothing.py @@ -1,4 +1,4 @@ -"""Image smoothing local plugin for Ginga.""" +"""Smoothing on an image.""" from __future__ import absolute_import, division, print_function # STDLIB @@ -14,7 +14,6 @@ from ginga.AstroImage import AstroImage from ginga.GingaPlugin import LocalPlugin from ginga.gw import Widgets -from ginga.util.toolbox import generate_cfg_example # STGINGA from stginga.plugins.local_plugin_mixin import HelpMixin, ParamMixin @@ -22,8 +21,10 @@ __all__ = [] +# TODO: If this plugin becomes active again, need modernize doc rendering. +# See https://github.com/spacetelescope/stginga/issues/134 class Smoothing(HelpMixin, LocalPlugin, ParamMixin): - """Smoothing on an image.""" + def __init__(self, fv, fitsimage): # superclass defines some variables for us, like logger super(Smoothing, self).__init__(fv, fitsimage) @@ -336,9 +337,3 @@ def __str__(self): name of the plugin. """ return 'smoothing' - - -# Replace module docstring with config doc for auto insert by Sphinx. -# In the future, if we need the real docstring, we can append instead of -# overwrite. -__doc__ = generate_cfg_example('plugin_Smoothing', package='stginga') diff --git a/setup.cfg b/setup.cfg index da28563..3e2bd57 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,8 +14,8 @@ norecursedirs = build docs/_build [ah_bootstrap] auto_use = True -[pycodestyle] -exclude = extern,sphinx +[flake8] +exclude = setup.py,conf.py,conftest.py,__init__.py,_astropy_init.py [metadata] package_name = stginga @@ -29,11 +29,11 @@ url = https://github.com/spacetelescope/stginga edit_on_github = False github_project = spacetelescope/stginga classifiers = - Intended Audience :: Science/Research, - License :: OSI Approved :: BSD License, - Operating System :: OS Independent, - Programming Language :: Python, - Topic :: Scientific/Engineering :: Astronomy, + Intended Audience :: Science/Research + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Topic :: Scientific/Engineering :: Astronomy Topic :: Software Development :: Libraries :: Python Modules [entry_points] diff --git a/setup.py b/setup.py index 06d6621..6d0088e 100755 --- a/setup.py +++ b/setup.py @@ -106,7 +106,7 @@ version=VERSION, description=DESCRIPTION, scripts=scripts, - install_requires=['astropy', 'ginga>=2.6', 'scipy'], + install_requires=['astropy>=2', 'ginga>=2.7', 'scipy>=0.18'], author=AUTHOR, author_email=AUTHOR_EMAIL, license=LICENSE, @@ -114,7 +114,7 @@ long_description=LONG_DESCRIPTION, cmdclass=cmdclassd, zip_safe=False, - use_2to3=True, + use_2to3=False, entry_points=entry_points, **package_info ) diff --git a/stginga/gingawrapper.py b/stginga/gingawrapper.py index 4d05eb5..95d3ecf 100644 --- a/stginga/gingawrapper.py +++ b/stginga/gingawrapper.py @@ -13,79 +13,6 @@ __all__ = ['run_stginga'] -# Manage the default layout. Yes, OMG, hacky hacky -gmain.default_layout = ['seq', {}, [ - 'vbox', {'name': 'top', 'width': 1520, 'height': 900}, - {'row': ['hbox', {'name': 'menu'}], 'stretch': 0}, - {'row': [ - 'vpanel', {}, [ - 'vbox', {}, - {'row': [ - 'hpanel', {'name': 'hpnl'}, [ - 'ws', {'name': 'left', 'width': 300, 'group': 2}, [ - ('Info', [ - 'vpanel', {}, [ - 'ws', {'name': 'uleft', 'height': 300, - 'show_tabs': False, 'group': 3} - ], - [ - 'ws', {'name': 'lleft', 'height': 430, - 'show_tabs': True, 'group': 3} - ] - ]) - ] - ], - [ - 'vbox', {'name': 'main', 'width': 700}, - {'row': [ - 'ws', {'wstype': 'tabs', 'name': 'channels', - 'group': 1, 'use_toolbar': True} - ], - 'stretch': 1 - }, - {'row': [ - 'ws', {'wstype': 'stack', 'name': 'cbar', - 'group': 99} - ], - 'stretch': 0 - }, - {'row': [ - 'ws', {'wstype': 'stack', 'name': 'readout', - 'group': 99} - ], - 'stretch': 0 - }, - {'row': [ - 'ws', {'wstype': 'stack', 'name': 'operations', - 'group': 99} - ], - 'stretch': 0 - } - ], - [ - 'ws', {'name': 'right', 'width': 430, 'group': 2}, [ - ('Dialogs', [ - 'ws', {'name': 'dialogs', 'group': 2} - ]) - ] - ] - ], - 'stretch': 1}, [ - 'ws', {'name': 'toolbar', 'height': 40, - 'show_tabs': False, 'group': 2} - ] - ], - [ - 'hbox', {'name': 'pstamps'} - ], - ]}, - {'row': [ - 'hbox', {'name': 'status'} - ], - 'stretch': 0 - } -]] - def run_stginga(sys_argv): """Run this from command line. @@ -110,16 +37,14 @@ def run_stginga(sys_argv): # Note: Unable to get this to work from within ginga_config.py # Example: # glb_plg_to_remove = ['WBrowser', 'RC', 'SAMP', 'IRAF'] - glb_plg_to_remove = [] - lcl_plg_to_remove = [] - _remove_plugins(glb_plg_to_remove, gmain.global_plugins) - _remove_plugins(lcl_plg_to_remove, gmain.local_plugins) + plg_to_remove = [] + _remove_plugins(plg_to_remove, gmain.plugins) # Add custom plugins. # If we use this, we do not have to use ginga_config.py stglobal_plugins, stlocal_plugins = _get_stginga_plugins() - gmain.global_plugins += stglobal_plugins - gmain.local_plugins += stlocal_plugins + gmain.plugins += stglobal_plugins + gmain.plugins += stlocal_plugins # Enforce Qt (--toolkit or -t) -- DISABLED # new_argv = ['--toolkit=qt' if 'toolkit' in s else s for s in sys_argv] @@ -128,7 +53,7 @@ def run_stginga(sys_argv): # Auto start core global plugins for gplgname in ('ChangeHistory', ): - gplg = _locate_plugin(gmain.global_plugins, gplgname) + gplg = _locate_plugin(gmain.plugins, gplgname) gplg.start = True # Start Ginga diff --git a/stginga/nbinteract.py b/stginga/nbinteract.py deleted file mode 100644 index 44993de..0000000 --- a/stginga/nbinteract.py +++ /dev/null @@ -1,201 +0,0 @@ -"""Wrapper script to run Ginga optimized for STScI data.""" -from __future__ import absolute_import, division, print_function - -import os - -from astropy.io import fits -from astropy.utils import isiterable -from astropy.utils.decorators import deprecated - -import tornado.httpserver -import tornado.web -import tornado.ioloop - -from ginga.misc import log, Task -from ginga.AstroImage import AstroImage -from ginga.web.pgw import Widgets, js, PgHelp, ipg - -__all__ = ['GingaServer'] - - -@deprecated('0.2.2', alternative="See Ginga's example notebook") -class GingaServer(object): - """IPython Notebook server for ``stginga``. - - Parameters - ---------- - host : str - Host name or IP address. - - port : int - Port number. - - logger - Python logger. This uses ``nbinteract_server`` logger by default. - - numthreads : int - Number of multiprocessing threads to use. - - Attributes - ---------- - host, port, logger - Same as inputs - - thread_pool - Thread pool for the given number of threads. - - tornado_app - Tornado web application. - - app - Ginga widget application. - - viewers : dict - Maps viewer name to Ginga image viewer. - - """ - def __init__(self, host='localhost', port=9909, logger=None, numthreads=5): - self.tornado_app = None - self.viewers = {} - self.host = host - self.port = port - - if logger is None: - logger = log.get_logger("nbinteract_server", null=True) - self.logger = logger - - self.thread_pool = Task.ThreadPool(numthreads, logger) - self.app = Widgets.Application(logger=self.logger, - base_url=self.base_url) - - @property - def base_url(self): - """Base URL for this server.""" - return "http://{0}:{1}/app".format(self.host, self.port) - - def __repr__(self): - repr_str = object.__repr__(self) - if self.url: - urlstr = 'not started' - else: - urlstr = 'URL={0}'.format(self.url) - return repr_str.replace('object at', urlstr + ' at') - - def start(self, create_main_window=True): - """Start server. - - Parameters - ---------- - create_main_window : bool - Create new Ginga image viewer. - - """ - self.thread_pool.startall() - - # TODO: DONT DO THIS. Use package data instead - js_path = os.path.dirname(js.__file__) - - self.tornado_app = tornado.web.Application([ - (r"/js/(.*\.js)", tornado.web.StaticFileHandler, - {"path": js_path}), - (r"/app", PgHelp.WindowHandler, - dict(name='Application', url='/app', app=self.app)), - (r"/app/socket", PgHelp.ApplicationHandler, - dict(name='Ginga', app=self.app)), - ], logger=self.logger) - - self.tornado_server = tornado.httpserver.HTTPServer(self.tornado_app) - self.tornado_server.listen(self.port, self.host) - - if create_main_window: - self.new_viewer('Main Viewer') - - def stop(self): - """Stop server (**NOT IMPLEMENTED**).""" - raise NotImplementedError - - def new_viewer(self, viewer_name): - """Create a new viewer with the given name. - - Parameters - ---------- - viewer_name : str - Name of the new Ginga image viewer. - - Returns - ------- - viewer - Ginga image viewer. - - Raises - ------ - ValueError - Viewer name already exists. - - """ - if viewer_name in self.viewers: - raise ValueError('Viewer {} already exists'.format(viewer_name)) - - # our own viewer object, customized with methods (see above) - self.viewers[viewer_name] = ipg.ImageViewer( - self.logger, self.app.make_window(viewer_name)) - - return self.viewers[viewer_name] - - def get_viewer_urls(self): - """Get viewer URLs. - - Returns - ------- - urls : dict - Maps viewer name to corresponding URL. - - """ - return {name: viewer.top.url for name, viewer in self.viewers.items()} - - def load_fits(self, fileorhdu, viewer_name='Main Viewer'): - """Load FITS image into the desired Ginga image viewer. - - Parameters - ---------- - fileorhdu - File or HDU list object. - - viewer_name : str - Name of Ginga image viewer to display to. - - Raises - ------ - KeyError - Viewer name does not exist. - - ValueError - Invalid file or HDU list object, or HDU list does not contain any - image. - - """ - if isinstance(fileorhdu, file): - fileorhdu = fits.HDUList.fromfile(fileorhdu) - - if isiterable(fileorhdu): - for hdui in fileorhdu: - if hasattr(hdui, 'is_image') and hdui.is_image: - hdu = hdui - break - else: - raise ValueError( - 'fileorhdu was iterable but did not contain any ' - 'image HDUs') - elif hasattr(fileorhdu, 'data') and hasattr(fileorhdu, 'header'): - # quacks like an HDU - give it a shot - hdu = fileorhdu - else: - raise ValueError('fileorhdu was not a fits file or HDU-ish thing') - - viewer = self.viewers[viewer_name] - if viewer.fitsimage.get_image() is None: - aim = AstroImage(logger=self.logger) - aim.load_hdu(hdu) - viewer.fitsimage.set_image(aim) - else: - viewer.fitsimage.get_image().load_hdu(hdu) diff --git a/stginga/plugin_info.py b/stginga/plugin_info.py index 14fa307..907aa52 100644 --- a/stginga/plugin_info.py +++ b/stginga/plugin_info.py @@ -44,12 +44,14 @@ def _get_stginga_plugins(): global_plugins = [] local_plugins = [ - Bunch(module='MultiImage', ws='dialogs', pfx=gpfx), - Bunch(module='MIPick', ws='dialogs', pfx=gpfx), - Bunch(module='BackgroundSub', ws='dialogs', pfx=gpfx), - Bunch(module='BadPixCorr', ws='dialogs', pfx=gpfx), - Bunch(module='DQInspect', ws='dialogs', pfx=gpfx), - Bunch(module='SNRCalc', ws='dialogs', pfx=gpfx), + Bunch(module='BackgroundSub', workspace='dialogs', pfx=gpfx, + category='Custom', ptype='local'), + Bunch(module='BadPixCorr', workspace='dialogs', pfx=gpfx, + category='Custom', ptype='local'), + Bunch(module='DQInspect', workspace='dialogs', pfx=gpfx, + category='Custom', ptype='local'), + Bunch(module='SNRCalc', workspace='dialogs', pfx=gpfx, + category='Custom', ptype='local'), ] return global_plugins, local_plugins diff --git a/stginga/plugins/BackgroundSub.py b/stginga/plugins/BackgroundSub.py index 3487d77..e2ed9ac 100644 --- a/stginga/plugins/BackgroundSub.py +++ b/stginga/plugins/BackgroundSub.py @@ -1,4 +1,19 @@ -"""Background subtraction local plugin for Ginga.""" +""" +Background subtraction on an image. + +**Plugin Type: Local** + +``BackgroundSub`` is a local plugin, which means it is associated with a +channel. An instance can be opened for each channel. + +**Usage** + +This plugin is used to calculate and subtract background value. Currently, +it only handles constant background. Subtraction parameters can be saved to a +JSON file, which then can be reloaded as well. The subtracted image can be +saved using :ref:`ginga:sec-plugins-global-saveimage`. + +""" from __future__ import absolute_import, division, print_function # STDLIB @@ -10,17 +25,16 @@ # GINGA from ginga.GingaPlugin import LocalPlugin from ginga.gw import Widgets -from ginga.util.toolbox import generate_cfg_example # STGINGA from stginga import utils from stginga.plugins.local_plugin_mixin import HelpMixin, MEFMixin, ParamMixin -__all__ = [] +__all__ = ['BackgroundSub'] class BackgroundSub(HelpMixin, LocalPlugin, MEFMixin, ParamMixin): - """Background subtraction on an image.""" + def __init__(self, fv, fitsimage): # superclass defines some variables for us, like logger super(BackgroundSub, self).__init__(fv, fitsimage) @@ -866,4 +880,6 @@ def __str__(self): # Replace module docstring with config doc for auto insert by Sphinx. # In the future, if we need the real docstring, we can append instead of # overwrite. -__doc__ = generate_cfg_example('plugin_BackgroundSub', package='stginga') +from ginga.util.toolbox import generate_cfg_example # noqa +if __doc__ is not None: + __doc__ += generate_cfg_example('plugin_BackgroundSub', package='stginga') diff --git a/stginga/plugins/BadPixCorr.py b/stginga/plugins/BadPixCorr.py index d56daca..73a0ecd 100644 --- a/stginga/plugins/BadPixCorr.py +++ b/stginga/plugins/BadPixCorr.py @@ -1,4 +1,21 @@ -"""Bad pixel correction local plugin for Ginga.""" +""" +Bad pixel correction on an image. + +**Plugin Type: Local** + +``BadPixCorr`` is a local plugin, which means it is associated with a +channel. An instance can be opened for each channel. + +**Usage** + +This plugin is used to fix bad pixels. Currently, it only handles fixing +a single bad pixel or bad pixels within a circular region. The corresponding +DQ flags will also be set to the given new flag value (default is zero). +Correction parameters can be saved to a JSON file, which then can be reloaded +as well. The corrected image can be saved using +:ref:`ginga:sec-plugins-global-saveimage`. + +""" from __future__ import absolute_import, division, print_function # STDLIB @@ -10,17 +27,16 @@ # GINGA from ginga.GingaPlugin import LocalPlugin from ginga.gw import Widgets -from ginga.util.toolbox import generate_cfg_example # STGINGA from stginga import utils from stginga.plugins.local_plugin_mixin import HelpMixin, MEFMixin, ParamMixin -__all__ = [] +__all__ = ['BadPixCorr'] class BadPixCorr(HelpMixin, LocalPlugin, MEFMixin, ParamMixin): - """Bad pixel correction on an image.""" + def __init__(self, fv, fitsimage): # superclass defines some variables for us, like logger super(BadPixCorr, self).__init__(fv, fitsimage) @@ -1027,4 +1043,6 @@ def __str__(self): # Replace module docstring with config doc for auto insert by Sphinx. # In the future, if we need the real docstring, we can append instead of # overwrite. -__doc__ = generate_cfg_example('plugin_BadPixCorr', package='stginga') +from ginga.util.toolbox import generate_cfg_example # noqa +if __doc__ is not None: + __doc__ += generate_cfg_example('plugin_BadPixCorr', package='stginga') diff --git a/stginga/plugins/DQInspect.py b/stginga/plugins/DQInspect.py index a862074..d484f3c 100644 --- a/stginga/plugins/DQInspect.py +++ b/stginga/plugins/DQInspect.py @@ -1,4 +1,18 @@ -"""DQ flag inspection local plugin for Ginga.""" +""" +DQ inspection on an image. + +**Plugin Type: Local** + +``DQInspect`` is a local plugin, which means it is associated with a +channel. An instance can be opened for each channel. + +**Usage** + +This local plugin is used to inspect the associated DQ array of a given image. +It shows the different DQ flags that went into a given pixel (middle right) +and also the overall mask of the selected DQ flag(s) (bottom right). + +""" from __future__ import absolute_import, division, print_function # STDLIB @@ -13,13 +27,12 @@ from ginga.gw import Widgets from ginga.misc import Bunch from ginga.util.dp import masktorgb -from ginga.util.toolbox import generate_cfg_example # STGINGA from stginga import utils from stginga.plugins.local_plugin_mixin import HelpMixin, MEFMixin -__all__ = [] +__all__ = ['DQInspect'] # Default DQ flags (HST) _def_tab = """# TELESCOPE = HST @@ -46,7 +59,7 @@ class DQInspect(HelpMixin, LocalPlugin, MEFMixin): - """DQ inspection on an image.""" + def __init__(self, fv, fitsimage): # superclass defines some variables for us, like logger super(DQInspect, self).__init__(fv, fitsimage) @@ -683,4 +696,6 @@ def __str__(self): # Replace module docstring with config doc for auto insert by Sphinx. # In the future, if we need the real docstring, we can append instead of # overwrite. -__doc__ = generate_cfg_example('plugin_DQInspect', package='stginga') +from ginga.util.toolbox import generate_cfg_example # noqa +if __doc__ is not None: + __doc__ += generate_cfg_example('plugin_DQInspect', package='stginga') diff --git a/stginga/plugins/MosaicAuto.py b/stginga/plugins/MosaicAuto.py index 167ca94..7db73f5 100644 --- a/stginga/plugins/MosaicAuto.py +++ b/stginga/plugins/MosaicAuto.py @@ -1,4 +1,34 @@ -"""Automatic mosaic local plugin for Ginga.""" +""" +Mosaic with option to highlight individual component. + +**Plugin Type: Local** + +``MosaicAuto`` is a local plugin, which means it is associated with a +channel. An instance can be opened for each channel. + +**Usage** + +.. warning:: This can be very memory intensive. + +This plugin is used to automatically create a mosaic of all currently +loaded images in the channel. The position of an image on the mosaic is +determined by its WCS without distortion correction. This is meant as a +quick-look tool, not an `AstroDrizzle `_ +replacement. Currently, such a mosaic can only be created once per Ginga +session. + +Once the mosaic is successfully created, user can select the desired +image name(s) to highlight associated footprint(s) on the mosaic. +Alternately, user can also click directly on the displayed mosaic to +highlight a footprint; Clicking on a highlighted footprint again will +un-highlight it. + +A shortlist of only the selected image names are displayed under the "Selected" +tab. This list can be saved to a file by clicking "Save Selection" button. +Optionally, the mosaic itself can be saved using +:ref:`ginga:sec-plugins-global-saveimage`. + +""" from __future__ import absolute_import, division, print_function from ginga.util import six @@ -14,16 +44,15 @@ from ginga.gw import Widgets from ginga.misc import Bunch from ginga.rv.plugins.Mosaic import Mosaic -from ginga.util.toolbox import generate_cfg_example # STGINGA from stginga.plugins.local_plugin_mixin import HelpMixin -__all__ = [] +__all__ = ['MosaicAuto'] class MosaicAuto(HelpMixin, Mosaic): - """Mosaic with option to highlight individual component.""" + def __init__(self, fv, fitsimage): super(MosaicAuto, self).__init__(fv, fitsimage) @@ -523,4 +552,6 @@ def __str__(self): # Replace module docstring with config doc for auto insert by Sphinx. # In the future, if we need the real docstring, we can append instead of # overwrite. -__doc__ = generate_cfg_example('plugin_Mosaic', package='stginga') +from ginga.util.toolbox import generate_cfg_example # noqa +if __doc__ is not None: + __doc__ += generate_cfg_example('plugin_Mosaic', package='stginga') diff --git a/stginga/plugins/SNRCalc.py b/stginga/plugins/SNRCalc.py index 01d06dc..29cdce7 100644 --- a/stginga/plugins/SNRCalc.py +++ b/stginga/plugins/SNRCalc.py @@ -1,5 +1,53 @@ -"""SNR and Surface background ratio (SBR) calculation local plugin for -Ginga. +""" +Signal-to-Noise Ratio (SNR) and Surface Background Ratio (SBR) +calculations on an image. + +**Plugin Type: Local** + +``SNRCalc`` is a local plugin, which means it is associated with a +channel. An instance can be opened for each channel. + +**Usage** + +This plugin is used to calculate SBR and SNR, as follow. + +SBR is as defined by `Ball `_, *"Take the median +value of the pixels within the image. In the case of a defocused spot, this is +just the median value within the 'top hat' portion of the image. Next, take the +standard deviation of the pixels that are clearly in the background, that is, +have no incident photons on them. Take the ratio of these two quantities, and +you have the signal-to-background ratio."* + +Given selected science (:math:`S`) and background (:math:`B`) regions: + +.. math:: + + \\mathrm{SBR} = \\frac{\\mathrm{MEDIAN}(S)}{\\mathrm{STDEV}(B)} + +For the science region above, as long as the image has an accompanying error +array (e.g., the ``ERR`` extension), its SNR can also be calculated: + +.. math:: + + a = \\frac{S}{\\mathrm{ERR}} + + \\mathrm{SNR}_{\\mathrm{min}} = \\mathrm{MIN}(a) + + \\mathrm{SNR}_{\\mathrm{max}} = \\mathrm{MAX}(a) + + \\overline{\\mathrm{SNR}} = \\mathrm{MEAN}(a) + +While SNR is more popular, SBR is useful for images without existing or +reliable errors. User can also define a minimum limit for SBR check, so that +the GUI can provide a quick visual indication on whether the image achieves the +desired SBR or not. As part of the statistics, mean background value is also +provided albeit not used in SBR nor SNR calculations. + +User can save the calculated values in the image header using the "Update HDR" +button. Calculation parameters can be saved to a JSON file, which then can be +reloaded as well. The image with updated header can be saved using +:ref:`ginga:sec-plugins-global-saveimage`. + """ from __future__ import absolute_import, division, print_function @@ -12,17 +60,16 @@ # GINGA from ginga.GingaPlugin import LocalPlugin from ginga.gw import Widgets -from ginga.util.toolbox import generate_cfg_example # STGINGA from stginga import utils from stginga.plugins.local_plugin_mixin import HelpMixin, MEFMixin, ParamMixin -__all__ = [] +__all__ = ['SNRCalc'] class SNRCalc(HelpMixin, LocalPlugin, MEFMixin, ParamMixin): - """SNR and SBR calculations on an image.""" + def __init__(self, fv, fitsimage): # superclass defines some variables for us, like logger super(SNRCalc, self).__init__(fv, fitsimage) @@ -1111,4 +1158,6 @@ def __str__(self): # Replace module docstring with config doc for auto insert by Sphinx. # In the future, if we need the real docstring, we can append instead of # overwrite. -__doc__ = generate_cfg_example('plugin_SNRCalc', package='stginga') +from ginga.util.toolbox import generate_cfg_example # noqa +if __doc__ is not None: + __doc__ += generate_cfg_example('plugin_SNRCalc', package='stginga') diff --git a/stginga/tests/setup_package.py b/stginga/tests/setup_package.py index f2fd9ed..63d7195 100644 --- a/stginga/tests/setup_package.py +++ b/stginga/tests/setup_package.py @@ -1,3 +1,3 @@ def get_package_data(): return { - _ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc']} + 'stginga.tests': ['coveragerc']} diff --git a/stginga/tests/test_utils.py b/stginga/tests/test_utils.py new file mode 100644 index 0000000..7524be1 --- /dev/null +++ b/stginga/tests/test_utils.py @@ -0,0 +1,113 @@ +"""Tests for ``utils.py``.""" + +import numpy as np +import pytest +from astropy.io import fits +from astropy.utils.data import get_pkg_data_filename +from numpy.testing import assert_allclose, assert_array_equal + +from ..utils import (calc_stat, interpolate_badpix, find_ext, DQParser, + scale_image) + + +class TestCalcStat(object): + def setup_class(self): + rng = np.random.RandomState(1234) + self.array = rng.randn(10, 10) + + @pytest.mark.parametrize( + ('algo', 'ans'), + [('mean', 0.22538589848374507), + ('median', 0.21188338677770105), + ('mode', 0.22139729237840572), + ('stddev', 0.4925049855366562)]) + def test_algo(self, algo, ans): + result = calc_stat(self.array, algorithm=algo) + assert_allclose(result, ans) + + def test_no_support(self): + assert calc_stat([]) == 0 + + with pytest.raises(ValueError): + calc_stat(self.array, algorithm='foo') + + +class TestInterpBadPix(object): + def setup_class(self): + self.image = np.array([[1, 2, 3], + [4, 0, 6], + [7, 8, 9]], dtype=np.float) + self.basis_mask = np.array([[True, True, True], + [True, False, True], + [True, True, True]]) + self.badpix_mask = ~self.basis_mask + + @pytest.mark.parametrize( + ('algo', 'ans'), + [('nearest', 2), + ('linear', 5), + ('cubic', 5.00000013)]) + def test_algo(self, algo, ans): + im = self.image.copy() # Probably redundant but just to be safe + interpolate_badpix( + im, self.badpix_mask, self.basis_mask, method=algo) + assert_array_equal(im[self.basis_mask], self.image[self.basis_mask]) + assert_allclose(im[self.badpix_mask], ans) + + def test_wrong_inputs(self): + with pytest.raises(ValueError): + interpolate_badpix( + self.image, self.badpix_mask, self.basis_mask, method='foo') + + with pytest.raises(ValueError): + interpolate_badpix(self.image, self.badpix_mask, []) + + +class TestStuffWithFITS(object): + @pytest.fixture(autouse=True) + def setup_class(self, tmpdir): + self.filename = str(tmpdir.join('test.fits')) + hdulist = fits.HDUList() + hduhdr = fits.PrimaryHDU() + hduhdr.header['INSTRUME'] = 'ACS' + hdulist.append(hduhdr) + hduimg = fits.ImageHDU(np.arange(100).reshape(10, 10), name='SCI') + hdulist.append(hduimg) + hdulist.writeto(self.filename) + + def test_find_ext(self): + for extname in ('PRIMARY', 'SCI'): + assert find_ext(self.filename, extname) + + assert not find_ext(self.filename, 'FOO') + assert not find_ext(None, 'SCI') + + def test_scale_image(self): + """WCS handling is not tested.""" + outfile = self.filename.replace('test.fits', 'out.fits') + scale_image(self.filename, outfile, 0.5, ext='SCI') + ans = [[0, 2, 4, 7, 9], + [22, 25, 27, 29, 31], + [45, 47, 49, 52, 54], + [68, 70, 72, 74, 77], + [90, 92, 95, 97, 99]] + with fits.open(outfile) as pf: + assert pf[0].header['INSTRUME'] == 'ACS' + assert_allclose(pf[0].data, ans) + + +# https://github.com/spacetelescope/reftools/blob/master/reftools/tests/test_interpretdq.py +def test_dq_parser(): + parsedq = DQParser(get_pkg_data_filename('../data/dqflags_acs.txt')) + + # One pixel + dqs = parsedq.interpret_dqval(16658) + assert sorted(dqs['DQFLAG']) == [2, 16, 256, 16384] + + # Array + dqs = parsedq.interpret_array([1, 1, 16658, 0]) + assert_array_equal(dqs[1][0], [0, 1]) + for i in [2, 16, 256, 16384]: + assert_array_equal(dqs[i][0], [2]) + for i in [4, 8, 32, 64, 128, 512, 1024, 2048, 4096, 8192, 32768]: + assert len(dqs[i][0]) == 0 diff --git a/stginga/utils.py b/stginga/utils.py index a1b84d4..3c94cd7 100644 --- a/stginga/utils.py +++ b/stginga/utils.py @@ -8,13 +8,11 @@ # THIRD-PARTY import numpy as np -import astropy from astropy import wcs from astropy.io import ascii, fits from astropy.stats import biweight_location from astropy.stats import sigma_clip from astropy.utils.exceptions import AstropyUserWarning -from astropy.utils.introspection import minversion from scipy.interpolate import griddata from scipy.ndimage.interpolation import zoom @@ -55,13 +53,6 @@ def calc_stat(data, sigma=1.8, niter=10, algorithm='median'): if len(arr) < 1: return 0.0 - # NOTE: Now requires Astropy 1.1 or later, so this check is not needed. - # from astropy import version as astropy_version - # if ((astropy_version.major==1 and astropy_version.minor==0) or - # (astropy_version.major < 1)): - # arr_masked = sigma_clip(arr, sig=sigma, iters=niter) - # else: - # arr_masked = sigma_clip(arr, sigma=sigma, iters=niter) arr_masked = sigma_clip(arr, sigma=sigma, iters=niter) arr = arr_masked.data[~arr_masked.mask] @@ -311,7 +302,7 @@ def scale_image(infile, outfile, zoom_factor, ext=('SCI', 1), clobber=False, Unsupported number of dimension or invalid WCS. """ - if not clobber and os.path.exists(outfile): + if not clobber and os.path.exists(outfile): # pragma: no cover if debug: warnings.warn('{0} already exists'.format(outfile), AstropyUserWarning) @@ -329,7 +320,7 @@ def scale_image(infile, outfile, zoom_factor, ext=('SCI', 1), clobber=False, continue hdr[key] = prihdr[key] - if data.ndim != 2: + if data.ndim != 2: # pragma: no cover raise ValueError('Unsupported ndim={0}'.format(data.ndim)) # Scale the data. @@ -375,7 +366,7 @@ def scale_image(infile, outfile, zoom_factor, ext=('SCI', 1), clobber=False, # Update header if 'XTENSION' in hdr: del hdr['XTENSION'] - if 'SIMPLE' in hdr: + if 'SIMPLE' in hdr: # pragma: no cover hdr['SIMPLE'] = True else: hdr.insert(0, ('SIMPLE', True)) @@ -389,7 +380,4 @@ def scale_image(infile, outfile, zoom_factor, ext=('SCI', 1), clobber=False, # Write to output file hdu = fits.PrimaryHDU(data) hdu.header = hdr - if minversion(astropy, '1.3'): - hdu.writeto(outfile, overwrite=clobber) - else: - hdu.writeto(outfile, clobber=clobber) + hdu.writeto(outfile, overwrite=clobber)